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,应用能快速失败,排障也更直观。