SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

FTP 登入成功但目錄列表卡住?防火牆設定

FTP 可正常登入,卻在目錄列表停滯並逾時,通常是資料通道被防火牆阻擋。本文說明如何指定被動模式連接埠範圍,並建立對應規則。

FTP 可登入,但目錄列表載入時停滯的原因

FTP 被防火牆阻擋時,被動模式會失效,因為 FTP 使用 2 條 TCP 連線,而不是 1 條。連往 port 21 的連線負責登入與傳送命令,因此使用者名稱和密碼會被接受,防火牆看起來也設定正確。接著,第一個 ls 需要透過另一個連接埠建立第 2 條連線。防火牆沒有允許這條連線,因此用戶端會持續等待,直到連線逾時。

解決方法是為這些資料連線指定固定的連接埠範圍,並建立允許相同範圍的防火牆規則。位於 NAT(network address translation)後方的伺服器還需要額外設定,才能公告正確的位址。過去,connection tracking helper 會自動處理這項工作。現在已不再如此;在複製舊指南前,應先了解原因。

控制通道與資料通道

FTP(檔案傳輸協定)定義於 RFC 959,早於 NAT 與具狀態防火牆。工作階段會建立一條至 TCP 連接埠 21 的控制連線,並在整個工作階段期間保持連線。命令會以純文字傳送。回覆則由 3 位數代碼與一行文字組成。這條連線不會傳送檔案內容。

每筆資料都會使用各自的 TCP 連線:目錄清單使用一條連線(LIST)、每次下載使用一條連線(RETR)、每次上傳使用一條連線(STOR)。連線建立後只使用一次,隨即關閉。驗證完全在控制通道上進行,因此資料路徑中斷時,通常會呈現相同現象:登入成功,接著停滯不前。如果用戶端顯示 230 回覆,然後在取得目錄清單時停止,問題出在資料通道,而不是認證資訊。

主動模式:伺服器回連用戶端

在主動模式中,用戶端選擇一個連接埠,在該連接埠上監聽,並告知伺服器要連線到哪裡:

PORT 192,168,1,50,195,80

前 4 個數字是用戶端的 IP 位址。最後 2 個數字是連接埠,編碼為 2 個位元組:195 * 256 + 80 = 50000。接著,伺服器會從自己的連接埠 20 開啟資料連線,連到用戶端的連接埠 50000。

對用戶端而言,這是從外部發起且未經請求的輸入連線,因此用戶端防火牆會將其丟棄。如果用戶端位於家用路由器後方,PORT 命令中的位址是私有位址,伺服器根本無法連線到該位址。FTP 無法正常運作的名聲,就是由主動模式造成的。

被動模式:用戶端開啟兩條連線

被動模式會反轉資料連線的方向。用戶端傳送 PASV,伺服器則回應自身的位址與連接埠:

227 Entering Passive Mode (203,0,113,10,195,80)

編碼方式相同,因此用戶端會連線至 203.0.113.10 的 50000 埠。此時兩條連線都由用戶端開啟,這也是被動模式能通過用戶端 NAT 的原因,以及目前所有用戶端都會優先要求使用被動模式的原因。

問題只是轉移,並未消失。未經請求的輸入連線現在會抵達伺服器,使用每次傳輸都會變更的高位連接埠。防火牆由你管理,因此這也成了你的問題。

EPSV(延伸被動模式,RFC 2428)採用相同概念,但回應格式更簡潔:

229 Entering Extended Passive Mode (|||50000|)

其中不包含位址。用戶端會重複使用控制連線已使用的位址,因此能在 IPv6 上運作,並消除一整類 NAT 錯誤。curl 手冊指出,curl 通常會先嘗試 EPSV,再嘗試 PASV。連接埠仍會在執行階段選取,因此 EPSV 不會改變任何防火牆規則。

為什麼一般防火牆規則無法允許資料通道

因為你撰寫規則時,該連接埠號碼尚不存在。伺服器會針對每次傳輸選擇連接埠。vsftpd 保持預設值時,會將 pasv_min_port 和 pasv_max_port 記載為 0,也就是「使用任何連接埠」,因此資料連線可能從任何大於 1023 的連接埠送入。sudo ufw allow 21/tcp 只允許控制通道,不允許其他內容;這正是能成功登入但目錄列表無法運作的設定。如果你仍不清楚服務監聽單一固定連接埠的概念,Linux 中的連接埠與監聽 socket 如何運作可提供背景說明。

狀態式防火牆確實會追蹤連線,核心也能將新連線視為現有連線的 RELATED。但對 FTP 而言,必須有元件讀取控制資料流,並從 227 或 PORT 行中擷取連接埠。預設不會有任何元件執行這項工作。

為什麼 FTP 連線追蹤 helper 已不再是解決方案

舊版指南會建議使用核心模組 nf_conntrack_ftp。它會讀取純文字控制通道,找出通告的連接埠並建立 expectation,因此資料連線不必透過指定連接埠的規則即可通過。自這些指南撰寫後,已有 4 項變化。

自動指派 helper 已停用,且在目前的核心中無法重新啟用。核心在 2016 年預設停用此功能。Pablo Neira Ayuso 的提交「netfilter: remove nf_conntrack_helper sysctl and modparam toggles」(2022 年 8 月)接著完全移除了此切換選項,並表示「使用者現在必須透過 ruleset 中的明確 conntrack helper 設定來處理」。nf_conntrack_helper sysctl 存在於 Linux 5.19,在 Linux 6.0 之後已移除;核心的 conntrack sysctl 文件也不再列出此項目。單獨載入模組不會產生任何作用。

你可以檢查核心的類型。執行 sysctl net.netfilter.nf_conntrack_helper。若回覆為 sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack_helper: No such file or directory,表示核心已沒有可重新啟用的自動 helper 指派功能。若回傳數字,表示你使用的是早於 6.0 的核心,該切換選項仍存在,且預設值為 0。

防火牆前端也已將此功能標記為 deprecated。Ubuntu 24.04 的 man ufw-framework 對 /etc/default/ufw 中的 IPT_MODULES 行表示:「以此方式無條件載入連線追蹤模組(nf_conntrack_*)已 deprecated」,並補充說明 helper 規則「必須在 RULES FILES 中管理」。firewalld 在 firewalld.conf 中將 AutomaticHelpers 文件列為「Deprecated。此選項會被忽略,且不再使用。」現在附加 helper 必須手動撰寫含有 CT target 的明確規則。這比下方的修正方式更繁瑣,而且一旦啟用 TLS 就會失效。Ubuntu 上的 iptables 與 nftables 說明這些規則實際位於何處。

TLS 會終止這種作法。helper 必須讀取文字形式的控制通道才能運作。將該通道加密後,helper 只能看到密文,因此無法找出連接埠。這沒有修正方式,而且也不應該有:能夠讀取控制通道的中間設備,也就能讀取你的密碼。

在伺服器上宣告被動連接埠範圍

所有 FTP 伺服器都可以設定,讓它從指定的範圍選擇被動連接埠。選項名稱各不相同,因此請查閱實際執行之伺服器的說明文件。

vsftpd 的設定位於 /etc/vsftpd.conf:

pasv_enable=YES
pasv_min_port=30000
pasv_max_port=30099

pasv_enable 的預設值已是 YES。兩個連接埠選項的預設值為 0,也就是前文所述的「使用任意連接埠」行為。使用 sudo systemctl restart vsftpd 套用設定,然後以 systemctl status vsftpd 確認服務已重新啟動。vsftpd 遇到無法解析的設定行時會拒絕啟動,不會直接忽略該行。因此,若重新啟動失敗,請查看 journalctl -u vsftpd -n 20,找出指出你剛輸入選項的 500 OOPS: 行。

ProFTPD 的設定位於 proftpd.conf:

PassivePorts 30000 30099

ProFTPD 沒有記載這裡的預設值:未設定此指示詞時,連接埠會由核心選擇。其說明文件也指出,當指定範圍內沒有可用連接埠時,伺服器會改用核心指派的連接埠,並寫入訊息。範圍過小因此會偶爾失敗,而不是明確地失敗,這會大幅增加診斷難度。請使用非特權連接埠,也就是 1024 以上的連接埠。

Pure-FTPd 使用旗標 -p first:last。man pure-ftpd 將其說明為「僅優先使用從 first 到 last(含首尾)的範圍內連接埠進行被動模式下載」,並指出這「能讓 pure-ftpd 與封包過濾器更相容」。套件版本通常會在設定檔中包裝此旗標,因此請查閱發行版自己的說明文件以確認檔名,不要自行猜測。

你需要多少連接埠?每個進行中的資料連線需要 1 個。TCP 連接埠關閉後會停留在 TIME_WAIT 狀態幾分鐘,之後才能重複使用,因此請配置預期尖峰需求數倍的範圍。100 個連接埠足以應付少數使用者;繁忙的公開伺服器則需要多得多。vsftpd 在這方面比 ProFTPD 更不寬容:其原始碼會在範圍內嘗試少量隨機連接埠;若每個連接埠都已被占用,便會以 500 OOPS: vsf_sysutil_bind 結束工作階段,而不是借用範圍外的連接埠。無論使用哪一台伺服器,範圍過小都會在最繁忙的時刻中斷傳輸。

範圍應該放在哪裡?先執行 sysctl net.ipv4.ip_local_port_range。在標準 Ubuntu 主機上,其輸出為 32768 60999,也就是核心分配給傳出連線的連接埠範圍。若將被動連接埠範圍放在這個區間內,可能會與已被傳出連線占用的連接埠衝突,因此請將該範圍放在此區間以下。30000 到 30099 在預設主機上沒有衝突。請檢查你自己的主機,不要直接採用這個數值。

在防火牆中開放相同的連接埠範圍

ufw 使用冒號表示範圍,其手冊指出,範圍或清單「也可用來指定多個連接埠,此時必須指定通訊協定」:

sudo ufw allow 21/tcp
sudo ufw allow 30000:30099/tcp
sudo ufw status verbose

現在,ufw status verbose 應列出兩個項目。省略 /tcp 而執行相同命令會被拒絕,並顯示必須指定 tcp 或 udp 的錯誤,因為 ufw 不會自行猜測。VPS 上的 ufw 規則語法涵蓋其餘內容。

firewalld 使用連字號表示範圍,且需要重新載入:

sudo firewall-cmd --permanent --add-service=ftp
sudo firewall-cmd --permanent --add-port=30000-30099/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

--add-service=ftp 會開放 21/tcp,並要求使用 ftp helper;這是隨附服務定義所指定的名稱。它不會開放被動模式的連接埠範圍,因此單獨執行時,問題仍與原先相同。VPS 上的 firewalld 區域與服務說明更完整的內容。

直接使用 nftables,在 input chain 中加入:

tcp dport { 21, 30000-30099 } accept

還有一個需要注意的防火牆:大多數供應商會在控制面板中執行網路防火牆,位於作業系統之外。如果伺服器上的規則看似正確,但封包仍完全無法抵達,也要在該處開放相同的連接埠範圍。

伺服器位於 NAT 後方時,告知伺服器其公開位址

在伺服器上執行 ip -4 addr show。如果介面上的位址就是用戶端連線的位址,請略過本節。如果介面使用私有位址(10.x、172.16 到 172.31.x、192.168.x),而平台將公開位址映射到該私有位址,伺服器就不知道自己的公開位址。vsftpd 文件指出,pasv_address 的預設值是「從傳入的連線 socket 取得位址」,因此 227 回覆會帶有私有位址,接著將用戶端導向無法連線的位置。

FileZilla 對這個問題使用下列名稱:

Server sent passive reply with unroutable address. Using server address instead.

FileZilla 會修正這個問題並繼續傳輸。許多其他用戶端則不會。它們會連線到 10.0.0.5,然後停滯不動。

如果你使用 curl 進行測試,curl 也會隱藏這個問題。其手冊指出,--ftp-skip-pasv-ip「預設啟用(於 7.74.0 新增)」,因此 curl 會忽略 227 回覆中的位址,改用控制連線的位址。使用 curl 能成功的傳輸,仍可能因為這個原因在圖形化用戶端中失敗。

請明確設定位址。vsftpd 使用 pasv_address=203.0.113.10;如果偏好指定主機名稱,也可以使用 pasv_addr_resolve=YES(預設值為 NO)。ProFTPD 使用 MasqueradeAddress,可接受位址、DNS 名稱或介面名稱。Pure-FTPd 使用 -P,其文件說明這適用於「伺服器位於 masquerading(NAT)盒後方」的情況。EPSV 可避免整個問題,因為其回覆不含位址欄位;但不能依賴這項特性,因為要傳送哪個命令是由用戶端決定的。

TLS 變更內容

FTPS 是透過 TLS(傳輸層安全性)的 FTP。用戶端照常連線到 21 埠,傳送 AUTH TLS 以保護控制通道,接著傳送 PROT P,同時加密資料通道。純 FTP 會將密碼以可讀文字傳送到網路上,因此如果必須繼續使用 FTP,請使用 FTPS。vsftpd 隨附的 ssl_enable 預設為 NO。

這會帶來兩個結果。任何連線追蹤 helper 都無法運作;這就是上一節所述問題的另一面。sudo tcpdump -nAi any 'tcp port 21' 也不會再顯示 227 回應。因此,需要確認伺服器公告的位址與連接埠時,請讀取伺服器自己的日誌,而不要查看網路封包。

從外部測試變更

請從另一台機器執行以下測試。從伺服器本機測試會略過你要修正的防火牆。

sudo ss -ltnp | grep :21
curl -v --disable-epsv --user ftpuser:secret ftp://example.com/
nc -vz example.com 30000

ss 應顯示 FTP daemon 正在監聽連接埠 21。伺服器閒置時,被動連接埠範圍內不會有任何監聽中的連接埠,因為這些 socket 會在傳輸開始時建立,並在傳輸結束後關閉。

--disable-epsv 會強制 curl 使用 PASV 路徑。這是會暴露位址問題的路徑。追蹤輸出會顯示伺服器的回覆,接著列出 curl 要連線的位址與連接埠:

< 227 Entering Passive Mode (203,0,113,10,117,52)

117 * 256 + 52 = 30004,位於宣告的範圍內。該行若顯示私有位址,表示未設定 pasv_address。若連接埠超出你的範圍,表示伺服器從未讀取設定變更。請確認你編輯的是執行中服務所使用的檔案。

nc 可單獨回答防火牆問題。立即出現 Connection refused 表示封包已抵達伺服器,但找不到任何監聽中的服務。對閒置的被動連接埠而言,這是正確結果,表示規則有效。若連線持續等待,直到 nc 放棄,表示有元件靜默丟棄封包。這可能是主機上的防火牆,也可能是供應商控制面板中的防火牆。這項區別與 SSH 連線遭拒與逾時的差異 中的說明相同,且適用於每個連接埠。

還應該繼續使用 FTP 嗎?

如果是新工作,不應該。SFTP(SSH file transfer protocol)會在 port 22 上,透過單一 SSH 連線運作。不需要第二個通道、被動模式連接埠範圍、NAT 設定或額外的 daemon 需要保護,因為 OpenSSH 已經提供這些功能。sftp user@example.com 可在完全未設定檔案傳輸服務的主機上運作。若只要提供檔案而不開放其他功能,sshd_config 會將 ForceCommand internal-sftp 與 ChrootDirectory 搭配使用。該目錄必須由 root 擁有,且使用者不得具備寫入權限,否則 sshd 會拒絕工作階段,並記錄一行 bad ownership or modes for chroot directory。

當對方無法變更時,FTP 仍有使用價值。掃描器與多功能事務機出廠時內建的韌體可能只支援 FTP。實驗室與工業設備通常使用固定映像檔,沒有人會重新進行認證。業務合作夥伴可能提供 FTPS drop,且不會為單一供應商新增其他通訊協定。在這些情況下,被動模式連接埠範圍及相符的防火牆規則就是全部工作;應執行 FTPS,而不是未加密的 FTP。雙通道設計源自 1985 年的決策,如今卻在當年未預期的環境中運作;檔案傳輸通訊協定的歷史介紹了這段發展。

FAQ

為什麼 FTP 可以登入,但目錄列表會停住?

登入只使用連接埠 21 上的控制連線,而防火牆允許這個連線。目錄列表需要在其他連接埠上建立第二條 TCP 連線,但這條連線遭到封鎖。在 FTP 伺服器上宣告被動模式連接埠範圍,並在防火牆開放相同範圍,目錄列表就能正常完成。目錄列表停住是資料通道問題,不是密碼問題。

FTP 被動模式需要開放哪些連接埠?

控制通道需要連接埠 21,此外還需要開放你為被動資料連線設定的連接埠範圍。這沒有標準範圍,因為範圍由你自行選擇。例如 30000 到 30099 即可:範圍大小應配合同時傳輸的尖峰數量,並避開核心的對外連接埠範圍;你可以使用 sysctl net.ipv4.ip_local_port_range 讀取該範圍。如果供應商的控制面板提供網路防火牆,也要在那裡開放相同範圍。

我還需要 nf_conntrack_ftp 嗎?

不需要,而且在目前的核心上不能依賴它。自動指派 helper 預設已停用;自 Linux 6.0 起,net.netfilter.nf_conntrack_helper 開關也已移除,因此 sysctl 會回報檔案不存在。ufw 手冊已將無條件載入這些模組列為過時做法,而 firewalld 會完全忽略 AutomaticHelpers。helper 還必須讀取純文字的控制通道,因此啟用 FTPS 後就會停止運作。請改為宣告被動模式連接埠範圍。

為什麼 FTP 用戶端顯示被動模式回應包含無法路由的位址?

伺服器以自身介面看到的位址回應 PASV,而該位址是私有位址。當平台將公開位址對映至私有位址時,就會發生這種情況。請明確設定公開位址:在 vsftpd 中使用 pasv_address,在 ProFTPD 中使用 MasqueradeAddress,或在 Pure-FTPd 中使用 -P。FileZilla 會改用原本連線的位址,並記錄「Using server address instead」,因此有些用戶端能避開這項設定錯誤,其他用戶端則會停住。

我應該使用 FTPS 還是 SFTP?

如果兩端都由你控制,請使用 SFTP:它在連接埠 22 上透過 SSH 建立單一連線,不需要開放資料通道,而且通常已經在執行。FTPS 是透過 TLS 傳輸的 FTP,因此仍保留雙通道設計,以及由此產生的所有防火牆問題。只有在對方不支援其他協定時,才選擇 FTPS。不要在網際網路上使用未加密的 FTP,因為密碼會以可讀文字在網路上傳送。