VPS Docker 防火牆 nftables 系統管理 容器

Docker 29 把 firewall backend 換成 nftables 實驗支援:DOCKER-USER 這條 chain 沒了、bridge-accept-fwmark 才是新的鉤子

Docker 29 除了抬升 API 版本、把 containerd 換成預設,還多開了一條 nftables firewall backend 路線。啟用之後 DOCKER-USER 這條沿用十年的自訂 chain 直接消失,改用 base chain priority 加 fwmark 才是官方認可的插點。這篇整理該怎麼遷移、哪些設定會撞牆、Swarm 環境為何暫時不能啟用。

Docker 29 在 2026 年 3 月釋出時,release notes 上把「nftables backend 進入實驗支援」寫成一句話,位置排在最低 API 版本抬升跟 containerd image store 變預設這兩則之後。半年過去,operator 圈子的反應集中在同一件事上:這條路線一啟用,DOCKER-USER 這條從 Docker 17 就存在、幾乎每一份「Docker 主機防火牆」教學都會教的 iptables chain 直接不見了。想繼續掛自訂規則,唯一的官方替代是新加的 --bridge-accept-fwmark 加自建 nftables base chain。

這件事跟 Docker 之前那些「舊命令會 deprecated 但還會活五年」的節奏完全不一樣。切換到 nftables backend 是明示行為,iptables-nft 那條相容層路徑其實已經跑得夠久了,但只要 daemon 走 iptables 就會沿用舊 chain。真正踩雷的是把 firewall-backend 手動改成 nftables 之後——iptables 那份規則會被清掉一部分、DOCKER-USER 的跳轉還會保留到重開機為止,之後就再也不加了。既有的 UFW 規則、iptables -I DOCKER-USER 開機腳本、部分基礎設施防火牆自動化工具,全部在這個切點失效。

為什麼要重畫 firewall backend 這條路線

Docker 從第一天起就靠 iptables 開關容器網路。多年下來累積出的 chain 結構是 DOCKERDOCKER-ISOLATION-STAGE-1DOCKER-ISOLATION-STAGE-2DOCKER-USER,其中 DOCKER-USER 是官方唯一保證「Docker 不會覆蓋」的插點,讓管理員能塞自己的規則進去做防火牆策略。這套結構撐了將近十年。

問題是,主流發行版早就把預設防火牆改成 nftables。Debian 10、Ubuntu 20.04、RHEL 8 都是 2019 到 2020 年就完成切換,iptables 這支指令實際上是 iptables-nft 相容層,把 iptables 語法翻譯成 nftables 規則寫進 kernel。這層翻譯多年下來累積出兩個問題:Docker 寫的規則跟 host 上其他 nftables 規則的優先序時常衝突、firewalld 的 direct interface 已經被官方標記 deprecated 且要移除。Docker 再堅持走 iptables 抽象,等於自願被綁在一個逐漸失去支援的 API 上。

Docker 29 給出的答案是拿掉中間人:daemon 直接呼叫 nft 指令寫規則、跟 host 的 nftables 世界一起共存。當前是實驗性的,官方講白了未來版本會反過來把 nftables 變預設、iptables backend 標記 deprecated,時程沒公開但方向確定。

啟用之後 host 上的 table 長什麼樣

要開啟這條 backend,/etc/docker/daemon.json 加上一行:

1
2
3
{
"firewall-backend": "nftables"
}

重啟 dockerd 之後執行 docker info | grep -i firewall 應該回 Firewall: nftables。這時候用 nft list ruleset 掃一遍會看到兩張新表:ip docker-bridgesip6 docker-bridges,各自包一組 base chain。IPv4 那張表的結構長這樣(節錄):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
table ip docker-bridges {
chain filter-forward {
type filter hook forward priority filter; policy accept;
...
}
chain nat-prerouting {
type nat hook prerouting priority dstnat; policy accept;
fib daddr type local counter jump docker-bridge-nat-prerouting
}
chain raw-prerouting {
type filter hook prerouting priority raw; policy accept;
...
}
}

每建立一個 user-defined bridge network 會在這兩張表裡再插一組 chain,命名帶 network id 前綴。這跟舊 iptables 結構最大的差異是「命名 table」概念:Docker 完全擁有 docker-bridges 這兩張表,其他人不該碰。官方文件明確講「不要直接改 Docker 的 table,會在下次規則同步時被蓋掉」——這比 iptables 世代更嚴格。

順帶一提,docker info 底下如果看到 Firewall: iptables (nft),那代表 backend 還是 iptables、只是 host 上的 iptables 走 nft 翻譯,跟 Docker 29 這條新路線是兩回事。這兩者很容易搞混。

DOCKER-USER 消失之後,自訂規則掛在哪

新的 nftables backend 沒有 DOCKER-USER 這條 chain。想在 forward 路徑上插自己的規則,做法是另開一張 table、掛在跟 Docker 相同的 hook 上、用 chain priority 排序。範例:

1
2
3
4
5
6
table ip my-filter {
chain forward-in {
type filter hook forward priority filter - 10; policy accept;
iifname "eth0" ip saddr != 203.0.113.0/24 counter drop
}
}

priority filter - 10 表示這條 chain 比 Docker 的 filter chain 早跑,先擋、再放行 Docker 那份規則。如果要在 Docker 之後跑(例如記錄已經被 Docker 放行的封包),寫 priority filter + 10。這套機制比舊的 DOCKER-USER 靈活很多——同時掛多層規則、精確控制順序,但也逼管理員要理解 netfilter hook 跟 priority 的關係,門檻明顯高一階。

真正麻煩的是「放行」語意。iptables 世代在 DOCKER-USER-j ACCEPT 就等於這條封包放行完成、其他 chain 不會再擋。nftables 世代不吃這一套:accept 動作只是離開當下這條 chain,同一個 hook 的其他 base chain 還是會照跑,Docker 那份規則裡如果有 drop 一樣會生效。

Docker 29 的解法是新增 --bridge-accept-fwmark 這個 daemon option。用法是:在自己那條較早跑的 chain 裡對封包打 fwmark,Docker 看到有這個 mark 就跳過 drop 規則直接放行。設定範例:

1
2
3
4
{
"firewall-backend": "nftables",
"bridge-accept-fwmark": "0x1/0x1"
}

搭配自己的規則:

1
2
3
4
chain accept-trusted {
type filter hook forward priority filter - 10; policy accept;
ip saddr 10.0.20.0/24 meta mark set 0x1
}

這種寫法一開始會覺得很繞,實際上是 nftables 世代處理「跨 chain 訊號傳遞」的標準做法。理解成本擺在這裡,遷移舊部署腳本就是逐條重寫,沒有捷徑。

IP forwarding 不再幫你打開,policy=DROP 也會出事

舊行為裡 Docker 啟動時會主動把 net.ipv4.ip_forward 設成 1。nftables backend 明確不做這件事——如果 bridge network 需要 forwarding 但 host 上沒開,daemon 直接報錯不啟動。官方文件強調這是刻意設計:多網卡 host 上自動打開 forwarding 是安全風險,Docker 不該替管理員做這個決定。

實務上補這一步很簡單,/etc/sysctl.d/99-docker.conf 加:

1
2
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1

比較容易漏的是 iptables FORWARD chain policy。如果 host 上原本用 iptables -P FORWARD DROP 加白名單這種寫法,nftables backend 放行的封包在離開 nftables 世界之後還是會被 iptables 的 legacy FORWARD 掛掉。這個交叉汙染的排查極痛,因為兩邊的規則掃描工具通常分開跑、iptables -Lnft list ruleset 也不會互相引用。切換 backend 前把 FORWARD policy 改成 ACCEPT、把過濾邏輯全搬進 nftables,才是乾淨路徑。

Swarm、direct routing 這幾個目前不能用

nftables backend 有一份明確的功能落後清單,遷移前要先確認自己的部署有沒有踩到。

Swarm 完全不支援。Docker 29 這一版的 overlay network 規則還沒從 iptables 搬過來,只要 daemon 進 Swarm mode,nftables backend 會拒絕啟用。官方講「未來版本會補」,但沒給時程。Compose + 單機 bridge 網路的 VPS 場景幾乎不受影響,Swarm cluster 就得整包等下一版。

Direct routing 有預設封鎖。舊行為裡,只要有辦法路由到 container IP,就能繞過 published port 直接連上容器。這在 host 有多張網卡、其中一張是 trusted 內網時是常用手法。nftables backend 預設在 raw-prerouting 這條 chain 直接 drop 這種直連封包,而且是在 raw 這麼早的 hook,fwmark 都來不及打。要打開得靠 network option com.docker.network.bridge.trusted_host_interfaces,或 daemon 層級的 allow-direct-routing global option。

iptables DOCKER-USER 的舊規則不會被 nftables backend 讀到,但也不會被清掉。切換 backend 之後 iptables 那條跳轉還在,直到重開機或手動清才會消失。這段過渡期兩份規則同時存在,追封包流向會比平常吃力。

什麼時候可以往 nftables backend 遷移

真的要下判斷,時機看幾個條件。單機 VPS 跑 Compose、沒有 Swarm、沒有複雜自訂防火牆規則、iptables FORWARD 已經是 ACCEPT policy——這種標準配置切過去幾乎零成本,順手把 host 拉進 nftables 世代。有大量 DOCKER-USER 規則要保留的環境,先把規則寫成 nftables 版、拿一臺測試機驗過再說,遷移工具目前只有 iptables-translate 而且產出得手動修。跑 Swarm 的環境完全不用考慮,等下一版。

Docker 官方明確講「未來版本 nftables 會變預設、iptables 標記 deprecated」,但沒給版本號跟時間點。從 v29 的實驗支援到預設反轉之間會有幾個版本的緩衝期,這期間把握機會把手上的防火牆邏輯搬過去,比等到強制切換那天再手忙腳亂划算很多。

想在乾淨 VPS 上驗這條 nftables backend 直接開一臺就好

Docker 29 的 nftables backend 目前狀態是「可以測、還不建議吃 production 流量」,但遷移前的驗證動作越早做越輕鬆。要一臺乾淨的 Debian 13 或 Ubuntu 26.04、kernel 帶完整 nftables 支援、能自由開關 daemon option 而不影響手邊服務——最省事的做法是開一臺獨立的 VPS 專門測。NCSE Network 提供的 VPS 主機跑在臺灣是方電訊機房、Intel Gold CPU 加 NVMe SSD,Debian 13、Ubuntu 26.04 等主流映像檔開機即用,kernel 版本全支援 nftables 完整功能集。想了解相關方案細節,可以到 ncse.tw 查看。

需要穩定的雲端主機?

NCSE Network 提供企業級 VPS,7 天免費試用,臺灣是方電訊機房,99% SLA 保證。

查看 VPS 方案 →