伺服器維運

iptables 命令詳解:四表五鏈、檢視規則與儲存配置

iptables 是 Linux 核心 netfilter 防火牆的規則管理命令:資料包在不同位置經過不同的“鏈”,每條鏈上按“表”的順序處理,鏈內規則從上往下匹配,命中 ACCEPT、DROP 就停止。預設策略改成 DROP 之前,先放行 SSH 和已建立的連線;改完要儲存,否則重啟後規則全部丟失。

作者 頂聯產品團隊發布於 7 分鐘閱讀

iptables 是 Linux 上管理 netfilter 防火牆規則的命令。規則按“表”和“鏈”組織:鏈決定在資料包經過的哪個位置檢查,進入本機走 INPUT,本機發出走 OUTPUT,經本機轉發走 FORWARD,路由前後分別是 PREROUTING 和 POSTROUTING;表決定做哪類處理,filter 負責放行和拒絕,nat 負責位址轉換,mangle 負責改包和打標記,raw 決定是否跳過連線跟蹤。同一條鏈裡的規則從上往下匹配,命中 ACCEPT、DROP、REJECT 這類終止動作就不再往下看,全都不匹配時執行鏈的預設策略。日常用得最多的是三組命令:iptables -L -n -v --line-numbers 檢視規則,-A、-I、-D 增刪規則,iptables-save 配合持久化服務儲存規則。

四表五鏈:資料包按什麼順序經過

表 作用 常用動作 所在的鏈
raw 決定資料包是否進入連線跟蹤 CT --notrack PREROUTING、OUTPUT
mangle 修改包頭欄位、打標記 MARK、TOS、TTL 五條鏈都有
nat 位址和連接埠轉換,只處理每個連線的第一個包 DNAT、SNAT、MASQUERADE、REDIRECT PREROUTING、INPUT、OUTPUT、POSTROUTING
filter 放行或拒絕;命令裡不寫 -t 時操作的就是它 ACCEPT、DROP、REJECT INPUT、FORWARD、OUTPUT

另外還有一個 security 表,供 SELinux 給資料包打安全標記,一般用不到。資料包走的是下面三條路徑之一,括號裡是同一條鏈上各個表的處理順序(省略了 security 表和很少用到的 nat 表 INPUT 鏈):

外部進入本機:PREROUTING(raw → mangle → nat) → 路由判斷 → INPUT(mangle → filter) → 本機程序
本機發出:    本機程序 → 路由判斷 → OUTPUT(raw → mangle → nat → filter) → POSTROUTING(mangle → nat)
經本機轉發:  PREROUTING(raw → mangle → nat) → 路由判斷 → FORWARD(mangle → filter) → POSTROUTING(mangle → nat)

這張順序圖能解釋很多“規則沒生效”的問題:

  • DNAT 發生在過濾之前。PREROUTING 的 nat 表已經把目標位址改成了內網位址,所以 FORWARD 鏈裡的放行規則要寫轉換後的位址和連接埠,而不是公網位址。
  • nat 表只看每個連線的第一個包,後續的包按連線跟蹤裡記下的對映自動轉換。改了 nat 規則後,已經建立的連線不受影響,只有新連線才按新規則走。
  • 發給本機程序的包走 INPUT,經 DNAT 轉給 Docker 容器的包走 FORWARD,不經過 INPUT。所以在 INPUT 鏈上封連接埠,擋不住 Docker 釋出出去的容器連接埠。
  • 鏈內順序決定結果。終止動作一旦命中就結束;LOG、MARK 這類非終止動作執行完繼續匹配下一條;跳到自定義鏈後沒有命中任何終止動作,會回到原連結著往下走。只有內建鏈有預設策略,自定義鏈沒有。

檢視規則:iptables -L -n -v –line-numbers

iptables -L -n -v --line-numbers            # filter 表的全部鏈
iptables -L INPUT -n -v --line-numbers      # 只看 INPUT 鏈
iptables -t nat -L -n -v --line-numbers     # nat 表要用 -t 指定
iptables -S                                 # 以命令形式輸出,便於複製、比對
iptables -S INPUT
引數 作用
-L 列出規則
-n 位址和連接埠按數字顯示,不做反向解析
-v 顯示每條規則的包數、位元組數,以及 in、out 網路卡
--line-numbers 顯示規則編號,插入和刪除時要用
-x 計數顯示精確值,不縮寫成 K、M、G
-t 表名 指定表,不寫就是 filter

一段典型的輸出:

Chain INPUT (policy DROP 8213 packets, 492K bytes)
num   pkts bytes target     prot opt in     out     source               destination
1     117K   12M ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0
2      52M   41G ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
3      206 12360 ACCEPT     tcp  --  *      *       203.0.113.0/24       0.0.0.0/0            tcp dpt:22
4    1893K  103M ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
5        0     0 DROP       all  --  *      *       198.51.100.23        0.0.0.0/0

逐項看:

  • 第一行的 policy:INPUT 的預設策略是 DROP,括號裡是被預設策略丟掉的包數和位元組數。這個數字增長很快,說明有大量未放行的流量打到這台機器,可能是掃描,也可能是你漏放了某個業務連接埠。
  • pkts、bytes:每條規則命中的包數和流量。第 5 條是後來用 -A 追加的封禁規則,排在放行規則後面:這個 IP 存取 80、443 時先被第 4 條放行,根本走不到第 5 條;存取其他連接埠時,即使沒有這條規則,也會被預設策略丟掉。範例裡它的計數是 0,只說明這個 IP 還沒存取過其他連接埠;就算計數在漲,它丟掉的也是預設策略本來就會丟的包。這條規則放在末尾等於沒寫,要封禁一個 IP 的全部存取,必須插到放行規則前面。
  • in、out:規則限定的網路卡,* 表示不限。第 1 條只匹配迴環介面 lo。
  • 最後一列:附加的匹配條件,tcp dpt:22 是目標連接埠 22,ctstate RELATED,ESTABLISHED 是已建立連線的後續包和相關連線。

較新版本的 iptables 加 -n 時,prot 列可能顯示協議號而不是名稱:0 表示全部協議,6 是 tcp,17 是 udp,1 是 icmp。想知道某條規則最近有沒有在命中,可以 iptables -Z INPUT 把 INPUT 鏈的計數清零,過幾分鐘再看。指令碼里要判斷一條規則是否已經存在,用 iptables -C INPUT -p tcp --dport 22 -j ACCEPT,存在時返回 0,不存在時返回 1,避免重複新增。

增刪改規則:-A、-I、-D、-R

引數 作用 範例
-A 鏈 追加到鏈的末尾 iptables -A INPUT -p tcp --dport 443 -j ACCEPT
-I 鏈 [編號] 插入到指定位置,不寫編號就插到第 1 條 iptables -I INPUT 3 -p tcp --dport 8080 -j ACCEPT
-D 鏈 編號、-D 鏈 規則 按編號或按內容刪除 iptables -D INPUT 3
-R 鏈 編號 規則 替換指定編號的規則 iptables -R INPUT 4 -p tcp -m multiport --dports 80,443,8443 -j ACCEPT
-C 鏈 規則 檢查規則是否存在 指令碼里防止重複新增
-F [鏈] 清空規則,不改預設策略 iptables -F INPUT
-P 鏈 動作 設定內建鏈的預設策略 iptables -P INPUT DROP
-N、-X、-E 新建、刪除、重新命名自定義鏈 iptables -N MGMT
-Z [鏈] 計數清零 iptables -Z

常用的匹配條件:

-p tcp                              # 協議:tcp、udp、icmp;--dport、--sport 必須和 -p 一起用
-s 203.0.113.0/24 -d 10.0.0.5       # 源、目標位址,前面加 ! 表示取反
-i eth0 -o eth1                     # 入、出網路卡;INPUT 鏈只能用 -i,OUTPUT 鏈只能用 -o
-m multiport --dports 80,443        # 一條規則匹配多個連接埠,最多 15 個
-m conntrack --ctstate NEW          # 連線狀態:NEW、ESTABLISHED、RELATED、INVALID
-m iprange --src-range 10.0.0.10-10.0.0.50
-m comment --comment "monitoring"   # 備註,-L 和 -S 的輸出裡都能看到

幾個日常例子:

# 封禁一個 IP 的全部存取:插到第 1 條
iptables -I INPUT -s 198.51.100.23 -j DROP

# 只允許監控伺服器存取 node_exporter 的 9100 連接埠
iptables -I INPUT 3 -p tcp -s 10.10.0.5 --dport 9100 -m comment --comment "node_exporter" -j ACCEPT

# 按內容刪除:把新增時的 -I 或 -A 換成 -D,其餘照抄
iptables -D INPUT -s 198.51.100.23 -j DROP

兩個常見的坑:

  • 按編號連續刪除,編號會變。 刪掉第 3 條後,原來的第 4 條變成第 3 條。要刪多條時從大號往小號刪,或者每刪一條重新看一次 --line-numbers。
  • -A 追加到了拒絕規則後面。 CentOS 7 的 iptables-services 預設規則以 -A INPUT -j REJECT --reject-with icmp-host-prohibited 結尾,之後用 -A 追加的放行規則都排在它後面,永遠匹配不到,表現為“規則加上了,連接埠還是不通”。這種鏈要用 -I 指定編號,插在 REJECT 之前。

預設策略改 DROP:先放行 SSH,最後再 DROP

把 INPUT 改成預設拒絕,是伺服器防火牆的常規做法,順序錯一步就會把自己鎖在外面。原則是先放行、後拒絕。以 INPUT 鏈原本為空為例:

# 1. 備份現有規則
iptables-save > /root/iptables-before.rules

# 2. 放行迴環介面和已建立的連線,當前的 SSH 會話靠這條維持
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# 3. 放行 SSH(儘量限定來源)、業務連接埠和 ping
iptables -A INPUT -p tcp -s 203.0.113.0/24 --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# 4. 核對一遍,最後一步才改預設策略
iptables -L INPUT -n --line-numbers
iptables -P INPUT DROP

改完不要關掉當前視窗,另開一個終端測試 SSH 能否新建連線。更穩妥的做法是給自己留退路:Debian、Ubuntu 的 iptables 包自帶 iptables-apply,它先載入規則檔案,再要求你在限定時間內確認,新規則把連線斷了、你沒法確認,它就自動回滾到原來的規則:

iptables-apply -t 60 /etc/iptables/rules.v4

沒有這個工具時,可以先安排一個定時恢復任務,確認沒問題再取消(需要 atd 服務在執行):

echo 'iptables-restore < /root/iptables-before.rules' | at now + 5 minutes
atq                     # 記下任務號,確認新規則沒問題後執行 atrm 任務號

iptables -F 和 iptables -A INPUT -j DROP 的區別

預設拒絕有兩種寫法:用 -P INPUT DROP 設預設策略,或在鏈末尾追加一條 -A INPUT -j DROP。兩者在日常操作中的表現正好相反:

寫法 好處 風險
iptables -P INPUT DROP 之後用 -A 追加的放行規則照常生效 iptables -F 只清規則、不改策略,清空後連已建立的連線也被丟棄,遠端會話當場斷開
iptables -A INPUT -j DROP iptables -F 會連它一起清掉,清空後變成全部放行,不會鎖死 之後用 -A 追加的規則全排在它後面,全部無效,只能用 -I

所以要清空規則時,順序是先 iptables -P INPUT ACCEPT,再 iptables -F。另外,-F 只清 filter 表,nat 表要用 iptables -t nat -F 單獨清;自定義鏈要在清空後用 -X 刪除。裝了 Docker、Kubernetes 的機器不要整表清空,它們維護的鏈被清掉後,容器網路會中斷。

儲存配置:CentOS 和 Debian 的方法不同

iptables 命令改的是核心里正在生效的規則,重啟就沒了。儲存靠兩個命令加一個開機載入服務:iptables-save 把當前規則匯出成文字,iptables-restore 把文字一次性載入回核心。

發行版 安裝 儲存當前規則 規則檔案 開機載入
CentOS 7、RHEL 8 系 yum install iptables-services service iptables save /etc/sysconfig/iptables、/etc/sysconfig/ip6tables systemctl enable iptables
Rocky、AlmaLinux 9 包名為 iptables-nft-services,以 dnf search iptables 的結果為準 同上 同上 同上
Debian、Ubuntu apt install iptables-persistent netfilter-persistent save /etc/iptables/rules.v4、/etc/iptables/rules.v6 安裝後 netfilter-persistent 服務自動啟用

幾點說明:

  • RHEL 系要先停用 firewalld:systemctl disable --now firewalld,再 systemctl enable --now iptables,兩者只能留一個。
  • /etc/sysconfig/iptables-config 裡的 IPTABLES_SAVE_ON_STOP 和 IPTABLES_SAVE_ON_RESTART 預設是 no,改成 yes 後,停止或重啟服務時會自動儲存當前規則。一般建議保持 no,把規則檔案當作唯一來源,改檔案後重新載入,不容易出現“記憶體裡的規則和檔案不一致”。
  • Debian 安裝 iptables-persistent 時會詢問是否儲存當前的 IPv4、IPv6 規則;以後每次改完規則,都要再執行一次 netfilter-persistent save。

規則檔案也可以直接編輯,格式就是 iptables-save 的輸出:

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -s 203.0.113.0/24 -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
COMMIT

*filter 到 COMMIT 是一張表,:INPUT DROP [0:0] 是鏈的預設策略和計數。改完先檢查語法,再載入:

iptables-restore --test < /etc/iptables/rules.v4    # 只解析不生效,出錯時會指出行號
iptables-restore < /etc/iptables/rules.v4           # 整表一次性替換
iptables-restore --noflush < extra.rules            # 不清空現有規則,只追加檔案裡的規則

裝了 Docker 的機器,iptables-save 匯出的內容會包含 Docker 自己生成的鏈和規則,原樣儲存再開機載入,可能和 Docker 啟動時重建的規則重複或衝突。這類機器只儲存自己寫的規則;要限制外部存取容器連接埠,規則寫在 Docker 預留的 DOCKER-USER 鏈裡。

幾個常用場景的寫法

管理連接埠只對白名單開放,用自定義鏈集中維護來源位址,以後增減位址只改這條鏈:

iptables -N MGMT
iptables -A MGMT -s 203.0.113.0/24 -j ACCEPT
iptables -A MGMT -s 10.10.0.0/24 -j ACCEPT
iptables -A MGMT -j DROP
iptables -I INPUT 3 -p tcp -m multiport --dports 22,3306,6379 -j MGMT

記錄被預設策略丟棄的包,放在 INPUT 鏈末尾,用 limit 限制寫日誌的頻率,避免日誌被刷爆。日誌寫進核心日誌,用 journalctl -k | grep IPT-DROP 檢視:

iptables -A INPUT -m limit --limit 10/min -j LOG --log-prefix "IPT-DROP: " --log-level 4

內網伺服器經本機存取外網,本機作為 NAT 閘道器。出口 IP 固定時也可以把 MASQUERADE 換成 -j SNAT --to-source 公網IP:

sysctl -w net.ipv4.ip_forward=1        # 要長期生效,寫進 /etc/sysctl.d/ 下的配置檔案
iptables -t nat -A POSTROUTING -s 10.10.0.0/24 -o eth0 -j MASQUERADE
iptables -A FORWARD -s 10.10.0.0/24 -o eth0 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

在管理系統裡怎麼落地

規則寫錯把自己鎖在外面,是改防火牆時最常見的事故。伺服器有 BMC 時,VNC 控制檯走的是帶外通道,不經過作業系統的網路協議棧,iptables 擋不住它:在 Toplink DCIM 的 IPMI 遠端管理裡開啟控制檯,本地登入後把預設策略改回 ACCEPT 即可;租用伺服器的客戶也能在客戶自助端自己開啟 VNC 控制檯處理,不必等機房值班。主機防火牆管的是“誰能存取這台機器的哪個連接埠”,偽造源位址、盜用 IP 這類問題要在接入交換器上解決,DCIM 的交換器管理可以對連接埠開啟 ARP 繫結和源位址校驗。

常見問題

iptables 的規則對 IPv6 生效嗎?

不生效。IPv6 由 ip6tables 單獨管理,語法基本相同,儲存檔案分別是 /etc/sysconfig/ip6tables(RHEL 系)和 /etc/iptables/rules.v6(Debian 系)。只配了 IPv4 規則的伺服器,IPv6 位址上的連接埠照樣對外開放;給 IPv6 設預設 DROP 時,還要放行 ICMPv6,否則鄰居發現失敗,IPv6 會直接斷網。

iptables -L 執行很慢、半天才出結果是怎麼回事?

沒加 -n 時,iptables 會對規則裡的每個 IP 做反向 DNS 解析、把連接埠號換成服務名,DNS 不通或很慢時每個位址都要等超時。加上 -n 就能立即輸出,日常檢視建議總是帶上。

DROP 和 REJECT 有什麼區別,用哪個?

DROP 直接丟棄,對方收不到任何回應,只能等超時;REJECT 會回一個錯誤,預設是 ICMP 連接埠不可達,TCP 可以用 --reject-with tcp-reset 回 RST,對方立刻知道被拒絕。面向公網的預設策略多用 DROP,不給掃描器任何反饋;內網用 REJECT,應用能快速失敗,排障也更直觀。

想看看在你的機房裡怎麼用?

從一次產品示範開始,梳理你的業務鏈路。

查看價格
服務熱線 400-112-2951

讓我們聊聊你的 IDC 業務

掃碼新增企業微信,預約產品示範或申請試用。

Toplink 企業微信二維碼

長按儲存或使用企業微信 / 微信掃描

也可致電
400-112-2951
Telegram
@TopLink88