iptables 命令詳解:四表五鏈、檢視規則與儲存配置
iptables 是 Linux 核心 netfilter 防火牆的規則管理命令:資料包在不同位置經過不同的“鏈”,每條鏈上按“表”的順序處理,鏈內規則從上往下匹配,命中 ACCEPT、DROP 就停止。預設策略改成 DROP 之前,先放行 SSH 和已建立的連線;改完要儲存,否則重啟後規則全部丟失。
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,應用能快速失敗,排障也更直觀。