你的 VPS 上跑著 Docker,UFW 設定了 deny 規則,ufw status 看起來一切正常。但如果有人從外部對你的容器 port 做掃描,他們可能能直接連進去——不管 UFW 怎麼說。
這不是 UFW 的 bug,也不是 Docker 的 bug。這是兩個工具在 Linux 封包過濾層的設計衝突,而且它靜悄悄地存在,不會留下任何警告。
根本原因:UFW 和 Docker 守著不同的門
Linux 的封包過濾框架(iptables)有幾條不同的 chain,封包會按照固定順序經過它們:PREROUTING → FORWARD → INPUT(終點是主機本身)或直接進入 FORWARD(轉發給其他主機/容器)。
UFW 的規則設定在 INPUT chain,也就是說它只過濾「要到達這臺主機本身」的流量。
Docker 用了完全不同的路徑。當你用 -p 8080:80 發布容器 port,Docker 會在 iptables 的 nat table PREROUTING chain 插入 NAT 規則,把目的地 8080 的封包在進入 INPUT chain 之前就轉向到容器。封包走的是 FORWARD path,UFW 完全看不到它。
1 | 封包進入 → PREROUTING (Docker NAT 規則在此攔截並轉向) |
結果就是:ufw deny 8080 沒有任何作用。只要你用 -p 8080:80 啟動容器,這個 port 就對全世界開放。
先確認你有沒有這個問題
從另一臺機器(或用 VPN 出口)對你的 VPS 做 port 掃描:
1 | nmap -p 8080 你的伺服器IP |
如果你有跑一個對外映射 8080 的容器,而且 nmap 顯示 open,你就中招了。光看 ufw status 是不夠的,因為 UFW 永遠都會顯示 deny——它不知道 Docker 已經繞過它。
修法一:把 port 只綁定到 localhost(最推薦)
如果你本來就打算讓所有外部流量都經過 Nginx 或其他反向代理,根本不需要把容器 port 直接暴露到外網介面。
1 | services: |
這樣一來,外部流量完全連不到 8080,只有本機的 Nginx 可以透過 proxy_pass http://127.0.0.1:8080 到達。這是最乾淨、最不容易出錯的方式,強烈建議作為預設習慣。
缺點是無法用於需要直接對外提供服務的容器,例如某些遊戲伺服器或特定協定的服務。
修法二:使用 DOCKER-USER chain
Docker 官方在設計時留了一個後門給管理員:DOCKER-USER chain。這個 chain 在 Docker 自己的 DOCKER chain 之前執行,放在這裡的規則不會被 Docker 的自動更新覆蓋。
1 | # 先阻擋所有轉發到 Docker 的流量 |
規則順序很關鍵:iptables -I 是插入到最前面,所以最後寫的規則會先執行。確認規則順序正確:
1 | sudo iptables -L DOCKER-USER -n -v --line-numbers |
這些規則在機器重開機後會消失。要持久化,安裝 iptables-persistent:
1 | sudo apt install iptables-persistent |
修法三:ufw-docker 工具(適合複雜場景)
如果你有很多個容器,每個需要不同的存取控制規則,手動管理 DOCKER-USER chain 會很繁瑣。ufw-docker 是一個讓 UFW 語法能套用到 Docker 容器的工具:
1 | sudo wget -O /usr/local/bin/ufw-docker \ |
安裝後可以用類似 UFW 的語法控制容器存取:
1 | # 允許特定 IP 存取容器的 80 port |
這個工具本質上是在 /etc/ufw/after.rules 插入規則來整合兩套系統,對於需要細粒度控制的場景很方便,但引入了額外的工具依賴,升級時要注意相容性。
千萬不要做的事:"iptables": false
網路上有些文章建議在 /etc/docker/daemon.json 設定:
1 | { |
這確實可以阻止 Docker 操作 iptables,讓 UFW 重新掌控一切。但代價是容器的網路功能嚴重損毀:port mapping 失效、容器無法存取外部網路(除非你手動設定 NAT masquerade),DNS 可能也會出問題。除非你有非常特殊的網路需求並且知道自己在做什麼,否則不要碰這個選項。
優先順序建議
對大多數使用反向代理的 web 應用部署,localhost 綁定是最乾淨的解法,也完全不需要處理 iptables。只要在 compose.yaml 裡所有的 ports 都寫成 127.0.0.1:hostport:containerport,問題就解決了。
對於需要直接對外提供服務的容器,用 DOCKER-USER chain 加上 iptables-persistent 是最可靠的方式。ufw-docker 工具在規則很多的情況下能幫你省不少管理時間,但增加了一層工具依賴。
不管用哪種方法,設定完之後記得從外部用 nmap 驗證,不要只看 ufw status。
NCSE Network 的 VPS 服務提供臺灣本地機房,適合需要精細網路控制的伺服器部署場景。如果你正在考慮建置安全的自管基礎設施,歡迎參考 ncse.tw 的方案。