SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor

Ubuntu 上的 iptables 與 nftables 有何不同?

Ubuntu 的 iptables 其實會寫入 nftables 規則。本文用實機命令驗證後端、讀取完整原生規則集,並說明 ufw 與 Docker 規則衝突的位置。

Ubuntu 上的 iptables 與 nftables:您的伺服器目前使用哪一個?

在 Ubuntu 20.04 及更新版本中,iptables 命令是會寫入 nftables 規則的前端。核心中只有一個封包篩選器,也就是 nftables;使用者空間則有兩個命令可對其進行設定。iptables -A INPUT 行仍可完全按照原本的方式運作,而它建立的規則是 nftables 規則,nft 可以將其列印出來。

先在您自己的伺服器上確認,再採信這項說法。

iptables -V
sudo update-alternatives --display iptables
sudo nft list ruleset

在 Ubuntu 24.04(截至 August 2026 為 iptables 1.8.10)中,iptables -V 會列印 iptables v1.8.10 (nf_tables)。方括號中的名稱就是後端。(nf_tables) 表示該命令會與 nftables 通訊。(legacy) 表示舊的 x_tables 後端;Ubuntu 仍會以 iptables-legacy 的形式提供該後端,而核心也會將其保留為完全分離的規則集。update-alternatives 會列印這項選擇背後的 symlink:link currently points to /usr/sbin/iptables-nft

在尚未設定防火牆的新 VPS 上,sudo nft list ruleset 不會列印任何內容。這個空白輸出就是基準狀態。使用舊方法新增一條規則,再次查看。

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo nft list ruleset
# Warning: table ip filter is managed by iptables-nft, do not touch!
table ip filter {
  chain INPUT {
    type filter hook input priority filter; policy accept;
    tcp dport 22 counter packets 0 bytes 0 accept
  }
  chain FORWARD {
    type filter hook forward priority filter; policy accept;
  }
  chain OUTPUT {
    type filter hook output priority filter; policy accept;
  }
}

您的 iptables 規則其實是 nftables 規則。iptables-nft 會標示它建立的 tables;nft 偵測到這個標記時會列印警告,因為使用 nft 編輯這類 table,會讓兩個工具同時管理相同的規則。查看其中一個命令建立的內容:有一個您未命名的 table,以及幾個您未要求建立的 chains。這就是舊模型,也是直接撰寫 nftables 時首先會改變的部分。

iptables -L 隱藏的內容

iptables -L 只會顯示 filter 表格。NAT(network address translation)規則需要 iptables -t nat -L,而 mangle 規則需要 -t mangle。IPv6 使用獨立的命令 ip6tables,其中也有每條規則的獨立副本。因此,某個清單看起來可能沒有問題,但實際上仍有規則從你未檢查的表格中丟棄或改寫封包。

sudo nft list ruleset 會在同一份輸出中列印所有 address family、所有表格、所有 chain 及所有規則。在不是由你自行建置的伺服器上,這個單一命令是查看實際載入內容最快的方法。加入 -a 可列印規則 handle;若要刪除單一規則而非整個 chain,就需要這些 handle。

你也可以順便修正以下兩個習慣。iptables -L 會將位址與連接埠解析為名稱,因此在 resolver 故障的伺服器上,看起來會像命令停止回應;請使用 iptables -nvL。另外,使用 sudo iptables-legacy -nvL 確認 legacy back end 為空,因為如果兩個 back end 都有規則,kernel 會同時評估兩者,而任一清單都無法顯示完整狀態。

建立的 tables 與 chains 不會自動繼承

nftables 啟動時沒有任何內容。在建立之前,不存在 filter table,而 filter 只是你選定的名稱。只有在指定 type、hook 和 priority 後,chain 才會接收封包,這會使它成為 base chain。未指定這些屬性的 chain,只能透過明確的 jumpgoto 進入,因此在有其他規則跳轉到它之前,不會產生任何成本。

另一項重大變更是 inet family。單一 inet table 可在相同規則中處理 IPv4 與 IPv6,避免某個連接埠在 iptables 中關閉,卻在 ip6tables 中完全開放這類錯誤。這種不一致相當常見,在使用 ufw 的伺服器上甚至有專屬的失敗模式

以下是完整的伺服器規則集。請將它放在 /etc/nftables.conf

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
  set admin_ips {
    type ipv4_addr
    flags interval
    elements = { 203.0.113.5, 198.51.100.0/24 }
  }

  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    ct state invalid drop
    iif lo accept
    ip protocol icmp accept
    meta l4proto ipv6-icmp accept
    tcp dport 22 ip saddr @admin_ips counter accept
    tcp dport { 80, 443 } counter accept
  }

  chain forward {
    type filter hook forward priority filter; policy drop;
  }

  chain output {
    type filter hook output priority filter; policy accept;
  }
}

請將第 2 行讀兩次。flush ruleset 會刪除伺服器上的所有 table,包括 ufw 和 Docker 各自建立的 table。在正式伺服器上執行前,請先繼續閱讀。

input chain 中的第一條規則負責大部分處理。ct state established,related accept 允許你發起的連線返回封包,因此 chain 的其餘部分只需判斷新的連線。ct state invalid drop 會丟棄不符合任何已知連線、也不是有效新連線起始封包的封包。後續規則都是明確開放的例外,而 policy drop 會處理其餘封包。

載入前先檢查檔案,並在操作期間保持第二個 SSH 工作階段開啟。policy drop 加上 SSH 規則中的任何一個錯字,都可能讓你失去伺服器的存取權。

sudo nft -c -f /etc/nftables.conf
sudo nft -f /etc/nftables.conf
sudo nft list ruleset

nft -c -f 會解析檔案並回報錯誤,但不會載入任何內容。解析成功時完全不會輸出任何訊息。

集合取代冗長的規則清單

tcp dport { 80, 443 } 是匿名集合:只需一條規則和一次查詢,不必為每個連接埠各寫一條規則。像 admin_ips 這類具名集合更具彈性,因為您可以在防火牆執行期間修改它。

sudo nft add element inet filter admin_ips { 203.0.113.9 }
sudo nft list set inet filter admin_ips
sudo nft delete element inet filter admin_ips { 203.0.113.9 }

不必重新載入,也不必重新編號規則。無論集合包含 5 個位址或 50000 個位址,比對都只需進行一次查詢。flags interval 讓集合能夠包含範圍和 CIDR(無類別網域間路由)前綴,例如 198.51.100.0/24。沒有該旗標時,集合只能接受單一位址,載入此前綴會失敗。

集合中的元素也可以自行到期。

set banned {
  type ipv4_addr
  flags timeout
  timeout 1h
}

搭配規則 ip saddr @banned drop 時,每個元素會在加入 1 小時後自行移除。Ubuntu 24.04 上的 fail2ban 所使用的 nftables 動作,就是透過將位址加入集合來封鎖位址,而不是新增規則。如果您還不熟悉連接埠,請先閱讀 Linux 中連接埠的實際用途

遷移期間,有一項差異很容易造成問題。除非明確要求,否則 nftables 不會計算封包。iptables -nvL 會一律顯示每條規則的計數器。在 nftables 中,只有包含 counter 關鍵字的規則才會有計數器,因此請在預期日後需要偵錯的規則中加入 counter

如何由 hook 與優先順序決定處理順序

Base chain 會指定一個 hook,也就是它在封包路徑中執行的位置。prerouting 會在路由決策前執行。input 會處理目的地為本機的封包。forward 會處理經由本機路由的封包。output 會處理本機程序產生的封包。postrouting 會在最後執行,也就是封包離開前。

優先順序會排列同一個 hook 內的 chain,數值越小越先執行。nftables 為常用數值提供了名稱:raw 是 -300,mangle 是 -150,dstnat 是 -100,filter 是 0,srcnat 是 100。撰寫 priority filter; 等同於撰寫 priority 0;

接下來是決定工具混用是否可行的部分。每個註冊在 hook 上的 base chain 都會依優先順序執行。在你的 chain 中接受封包,並不代表處理已完成:accept 只會結束該 chain,封包仍會繼續傳到同一個 hook 上的下一個 base chain。drop 在任何位置都具有最終效果,會立即停止封包處理。因此,不論哪一個先執行,你的 table 中允許封包通過的規則都無法取消 ufw table 中的 drop,而你的 accept 也無法防止後續執行的 chain 進行封鎖。

同一個 hook 上、優先順序相同的兩個 base chain,會依註冊順序執行;這取決於哪個服務先啟動。重新開機後,這個順序可能改變。如果必須在 ufw 旁邊執行自己的 table,請為它指定不同的優先順序,讓順序由設定明確決定,而不是互相競爭註冊順序。

為什麼沒有需要寫入的反向 NAT 規則?

這是最常答錯的問題,因此直接說明答案。連線追蹤會替你寫入反向轉譯。不需要再新增第二條規則。

同時處理一般 VPS 工作兩個方向的 nat table 如下所示。

table inet nat {
  chain prerouting {
    type nat hook prerouting priority dstnat; policy accept;
    iifname "enp1s0" tcp dport 8080 dnat ip to 10.0.0.5:80
  }

  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    ip saddr 10.0.0.0/24 oifname "enp1s0" masquerade
  }
}

只有連線的第一個封包會根據 nat chain 進行比對。規則符合時,kernel 會將該轉譯與連線項目一併儲存在 connection tracking table 中。之後雙向的所有封包,都會依據這個已儲存的項目進行改寫,不會再次讀取規則。安裝 conntrack 工具,查看即時項目。

sudo apt install -y conntrack
sudo conntrack -L -p tcp
tcp 6 431999 ESTABLISHED src=198.51.100.20 dst=203.0.113.10 sport=54321 dport=8080 src=10.0.0.5 dst=198.51.100.20 sport=80 dport=54321 [ASSURED] mark=0 use=1

請將它讀成兩組 tuple。前 4 個欄位是 client 傳送的連線,目的地為 203.0.113.10:8080,也就是你的公開位址。後 4 個欄位是 kernel 預期的回覆,已經反轉並完成轉譯,來源為 10.0.0.5:80,也就是實際的後端。第二組 tuple 就是反向規則。第一個封包符合規則時,kernel 就會寫入它。

因此,不要為回傳方向撰寫規則。這類規則無法符合,因為回傳封包屬於已建立的連線,不會再次到達 nat chain;即使它意外符合,也會再次轉譯已由 kernel 修正的封包。

改寫應放置的位置,也取決於相同的機制。目的地轉譯必須在 prerouting 執行,也就是路由決策之前,因為路由必須看到新的目的地,否則封包會送往錯誤的位置。主機自行產生的流量則因相同原因,在 output hook 處理。來源轉譯,包括來源埠改寫,必須在 postrouting 執行,也就是路由選定輸出介面之後。masquerade 會從該介面取得位址,而介面要等路由完成後才會確定。

因此,這類規則應放在路徑末端,不能放在其他位置。

ip saddr 10.0.0.0/24 oifname "enp1s0" snat ip to 203.0.113.10:20000-30000

該埠範圍會連同來源位址一起改寫來源埠。當多個內部 client 共用一個公開位址,且來源埠發生衝突時,這正是所需的行為。回覆抵達時,目的地是該範圍內的埠;conntrack 會將它對應至項目,並在傳遞封包前還原原始來源埠。同樣不需要第二條規則。

這會產生一個實務上的結果:變更 NAT 規則不會影響已存在的連線,因為它們的轉譯已經儲存。這些連線會持續使用舊行為,直到對應項目過期。sudo conntrack -D -p tcp --dport 8080 會刪除符合條件的項目,sudo conntrack -F 會刪除所有項目。在 NAT box 上使用後者時務必謹慎,因為目前連線能夠維持運作,正是依靠這些已儲存的轉譯;清除它們會同時中斷所有通過該 box 的連線。

ufw 與 Docker 都會自行寫入規則

ufw 是 iptables 的前端,而在 Ubuntu 上,iptables 又是 nftables 的前端。因此,使用 ufw 的主機會有一個 ip filter 表,其中包含名為 ufw-before-inputufw-user-input 等鏈,另有一個相同結構的 ip6 filter 副本。使用 sudo nft list ruleset | grep ufw 查看。這些鏈是根據 /etc/ufw 中的檔案產生,ufw reload 會從頭重寫這些檔案,因此手動新增在最上層的 iptables 規則會在下次重新載入時消失。VPS 的 ufw 基礎涵蓋了這些檔案的配置方式。

Docker 會自行設定防火牆,不會參照 ufw。使用 -p 80:80 發布連接埠時,Docker 會將 DNAT 規則寫入 nat 表,並在轉送路徑中加入允許規則;這兩者都會先於 ufw 的使用者鏈執行。結果通常會讓人措手不及:ufw deny 80 已載入,但容器仍可從網際網路連線。解決方式是在 Docker 留給使用者規則的 DOCKER-USER 鏈中設定,Docker 容器為何忽略 ufw會逐步說明。使用 sudo nft list ruleset | grep -i docker 查看主機上的內容。

現在重新查看上方設定中的 flush ruleset 行。它會刪除所有表,包括這兩個工具管理的表。在 Docker 主機上,發布的連接埠會停止運作,直到 sudo systemctl restart docker 重建這些鏈。這一行是人們整理防火牆時,最常導致自有服務離線的原因。

能在重新開機後保留的規則

兩套規則集本身都不具備持久性。核心會在關機時清除所有內容,而兩者分別透過不同套件解決這個問題。

對 nftables 而言,/etc/nftables.conf 會由 nftables.service 讀取。Ubuntu 預設停用該服務,因此在信任它之前,請先確認服務狀態。

systemctl is-enabled nftables
sudo nft -c -f /etc/nftables.conf
sudo systemctl enable --now nftables

對 iptables 而言,套件是 iptables-persistent。該套件會安裝 netfilter-persistent,並將規則儲存至 /etc/iptables/rules.v4/etc/iptables/rules.v6

sudo apt install -y iptables-persistent
sudo netfilter-persistent save

不要同時使用兩者。兩個各自聲稱保存防火牆規則的檔案會逐漸產生差異。最後載入的檔案會覆寫先前的內容,但無法只透過讀取任一檔案預測實際結果。

傾印即時規則集時,也有一個相關陷阱。sudo nft -s list ruleset > /etc/nftables.conf 會擷取當下已載入的所有內容,包括 ufw 的表格與 Docker 的表格。在開機時還原這份內容,會先載入這些工具預期自行建立的規則副本,等工具啟動後又建立第二份副本。請使用 sudo nft -s list table inet filter,只傾印自己的表格。-s 旗標會排除計數器,因為計數器不應放在設定檔中。

是否應該在 VPS 上啟用它?

除非需要 ufw 無法表達的功能,否則不要修改 ufw。ufw 已涵蓋一般 VPS 的需求:預設拒絕,只開放少數連接埠。若只是為了改用手寫規則集而取代它,得到的仍是相同的防火牆,卻多了一套需要維護的設定。

當需求超出 ufw 的模型時,才改用原生機制,例如 NAT 與連接埠轉送、可在執行期間更新的集合、同一條規則同時涵蓋兩個位址系列,或自行指定鏈結優先順序。這些都是合理原因,而 ufw 無法表達其中任何一項。

如果改用原生機制,就應完整切換。執行 sudo ufw disablesudo systemctl disable --now ufw,再使用 sudo nft list ruleset 確認 ufw 的資料表已移除,然後載入自己的檔案。同時執行 ufw 與手寫資料表的主機仍然會轉送網路流量,但目前的政策已變成兩套規則集的聯集,且評估順序由服務啟動時決定。閱讀任一檔案的人都無法判斷主機實際執行的規則。

遷移現有的 iptables 規則集

iptables-translate會轉換單一規則,並輸出 nftables 格式。它不會變更伺服器上的任何設定。

iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
nft add rule ip filter INPUT tcp dport 22 counter accept

iptables-restore-translate -f /etc/iptables/rules.v4則會對整個已儲存的規則集執行相同操作。請將輸出視為初稿。轉換是機械式的,且會逐條處理,因此會保留舊有的 table 和 chain 名稱,分別產生 IPv4 與 IPv6 的兩套規則集,也不會建立讓遷移值得進行的 sets。請手動將其重寫為單一 inet table,再使用 nft -c -f 檢查,確認無誤後才能套用到正式伺服器。

這些範例中的位址來自文件所保留的範圍 203.0.113.0/24198.51.100.0/24,而 enp1s0 是介面名稱。請改從 ip route show defaultip -br addr 取得實際值,不要直接複製範例,因為目前的 Ubuntu 映像檔很少將介面命名為 eth0

FAQ

iptables 在 Ubuntu 上已淘汰嗎?

這個命令不會消失,在 Ubuntu 24.04 上仍可使用。改變的是底層的運作方式:iptables 是透過 iptables-nft 後端寫入 nftables 規則的前端。使用 iptables -V 檢查目前設定;在 24.04 上,該命令會輸出 iptables v1.8.10 (nf_tables)。舊的 x_tables 後端仍以 iptables-legacy 形式提供,且維護完全分離的規則集。因此,請將規則寫入其中一個後端,不要同時寫入兩者。

返回方向需要另一條規則來撤銷 NAT 嗎?

不需要。連線追蹤會在連線的第一個封包符合 nat 規則時儲存轉換內容,之後雙向的每個封包都會依該項目進行改寫。sudo conntrack -L 會將其顯示為每個連線的兩組 tuple:先是原始方向,再是已反轉的回覆方向。針對返回方向撰寫規則沒有作用,因為返回封包不會到達 nat chain。

我可以同時使用 ufw 和自訂的 nftables 規則嗎?

可以,但這會造成問題。hook 上的每個 base chain 都會執行,因此實際政策是兩套規則集的合併結果,順序由 priority 決定;priority 相同時,則取決於哪個服務先啟動。任一套規則中的 drop 都具有終止效果,而你自訂規則中的 accept 不會阻止另一套規則丟棄相同的封包。請選擇一項工具。如果選擇 nftables,請先停用 ufw,並從 sudo nft list ruleset 確認其 tables 已移除。

如何讓 nftables 規則在 Ubuntu 重新開機後保留?

將規則集放入 /etc/nftables.conf,使用 sudo nft -c -f /etc/nftables.conf 檢查,然後執行 sudo systemctl enable --now nftables。此服務預設不會啟用,因此建議執行一次 systemctl is-enabled nftables。產生該檔案時,只匯出你自己的 table,使用 sudo nft -s list table inet filter;完整的 list ruleset 匯出也會包含 ufw 和 Docker 各自管理的 tables。