记得刚上手服务器那会儿,我第一次配置 Linux iptables 防火墙,顺手把默认策略设成了 DROP,紧接着 SSH 就断了。那一刻我坐在机房门口,看着黑掉的窗口,才真正意识到:防火墙规则不是写给评审看的,是你要亲手交给每一台机器的边界说明书。
从那以后我再也没把 iptables 当命令列表背过。它本质上是一套跑在 Linux 内核网络栈里的包过滤机制,理解数据包怎么进、怎么出、在哪条链上被检查,比记住几十条命令重要得多。这篇文章就把我从入门到实战过程中攒下的经验完整梳理一遍,覆盖原理、规则语法、常用场景和排障技巧。不管你是刚接触 Linux 的运维新人,还是平时用 ufw、firewalld 但想摸清底层的开发者,都能拿到可以直接上手的思路和避坑点。
1. 一眼看懂 iptables 原理:表和链的协作关系
1.1 为什么先理解原理而不是先背命令
很多人学 iptables 的第一反应是打开搜索引擎找“常用命令大全”,然后复制粘贴跑一遍。结果遇到“为什么我加了规则但没生效”“为什么我清空规则后服务还是不通”就彻底懵了。
我个人的经验是:iptables 的命令其实非常有限,翻来覆去就是增删改查那几种,但真正难的是规则背后的流量路径。你只有知道一个数据包从网卡进来之后要经过哪几个检查点,才能解释得清“这条规则为什么写在这里”“为什么这么做”。所以这一节我们先打地基,内容不多,但会管很久。
1.2 四表五链:一张表理清核心结构
iptables 的整个框架可以概括为“四张表、五条内置链”。表是用来分类管理功能的,链则是数据包流经的检查点。不同的表在各自的链上注册规则,同一个链上可能同时存在来自不同表的规则。
常用的四张表:
| 表名 | 作用 | 常用场景 |
|---|---|---|
| filter | 包过滤,决定放行还是丢弃 | 日常防火墙策略,绝大多数人只用这张表 |
| nat | 地址转换,修改源地址或目标地址 | 端口转发、共享上网、容器网络 |
| mangle | 修改数据包头部字段,比如 TTL、TOS | 策略路由、负载均衡标记,日常很少直接操作 |
| raw | 绕过连接跟踪,主要用于性能优化 | 高流量环境下让部分数据包不进入 conntrack |
五条内置链则对应数据包在内核协议栈中的几个关键位置:PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。名字看着抽象,其实只要把“数据包从外网进来、到本机、转出去”这条路径走一遍就清晰了。
1.3 数据包在链中的走向:走一遍就通
我习惯用一句话概括数据包的一生:进来先过 PREROUTING,目的地是本地就进 INPUT,目的地是别人就进 FORWARD,本地发出去的先逛 OUTPUT 再去 POSTROUTING。
具体展开就是:
- 数据包从网卡进入,首先到达 PREROUTING 链。这里是做 DNAT 和其他早期处理的地方,典型场景是“公网访问 80 端口,悄悄转发给内网某台机器的 8080”。
- 如果数据包的目标地址是本机,走 INPUT 链。这是绝大多数人写防火墙规则的地方,比如“允许外部访问 22 端口”“拒绝某个 IP 访问”。
- 如果数据包的目标地址不是本机,而是需要转发到其他主机,走 FORWARD 链。这就是路由器、网关、容器宿主机上的数据包要走的路。
- 本机自己发出去的包,先经过 OUTPUT 链,比如本机主动访问外网,可以在 OUTPUT 控制出站流量。
- 无论是要转发的包,还是本机发出的包,最后都会经过 POSTROUTING 链。这里是做 SNAT(源地址转换)的地方,比如内网机器共享一个公网 IP 上网,就是把源地址改写成了网关的公网 IP。
很多初学者会问:为什么我在 INPUT 链上写规则,转发流量不生效?就是因为转发流量根本不经过 INPUT。数据包从 PREROUTING 进来后,判断目标不是本机就直接往 FORWARD 走了。如果你没有理解这条路,写一万条规则也是白搭。
1.4 规则匹配的隐含逻辑:搞定一条就不再匹配
还有一个特别关键、也特别容易忽略的点:iptables 的规则是“顺序匹配,匹配即停”的。数据包到达某条链后,会拿着链上的规则从第一条开始逐条比对,一旦某条规则的所有条件都匹配上,就直接执行这条规则指定的动作(ACCEPT、DROP、REJECT 等),后面的规则不再检查。
这个机制和很多传统访问控制列表(ACL)不一样。ACL 往往是按顺序匹配之后还要继续往下看,iptables 则是“一锤定音”。所以规则顺序极其重要。你自己都能想明白后果:如果有一条DROP 所有来自 192.168.1.0/24 的包的规则排在最前面,那后面再怎么写“允许 192.168.1.10 访问”都是无效的,因为它根本没有机会被检查到。
这也就是为什么我建议在实战中把规则按“量级从大到小”来排:先拦截大网段,再放行具体 IP。或者反过来,如果你想做“先放行少数白名单,再拦截其他所有流量”,就必须把“允许”规则放在前面,把“默认拒绝”放在最后。顺序错了,轻则业务异常,重则把自己挡在门外。
1.5 自定义链:给规则做项目管理
当规则多了以后,全塞进内置链会让排查变成噩梦。比如你这么看iptables -L INPUT -n,看到几十条规则混在一起,根本分不清哪条是防扫描的、哪条是给 Docker 留的、哪条是业务白名单。自定义链就是用来做规则分组的。
你可以创建一条名为SYN-FLOOD的链,把防 SYN 攻击的规则都放进去,然后在 INPUT 链上用一条-j SYN-FLOOD跳转进去。这样主链简洁,子链职责单一,排查的时候直接iptables -L SYN-FLOOD -n --line-numbers看一条链就够了。写自定义链并不复杂,就是-N 链名创建,往里面加规则,然后在需要的位置用-j 链名跳过去,最后要在子链的末尾写一条RETURN,让不匹配的流量回到主链继续检查。我在后面的实战环节里会带大家一起建一条。
2. 规则语法拆解:从一条命令到一套体系
2.1 iptables 命令的通用格式
iptables 的命令说白了就是一条“填空式”的语句,你只需要弄清楚每个空位填什么。我习惯把它拆成五个部分:表 + 操作动作 + 链 + 匹配条件 + 目标动作。
iptables -t 表名 操作动作 [链名] [匹配条件...] -j 目标动作这里的-t 表名如果不写,默认就是 filter 表。因为绝大多数场景用的都是 filter 表,所以日常命令里经常会省略-t filter,这是完全正常的。最容易看花眼的是各种单字母参数,我整理了一份常用对照:
| 参数 | 含义 |
|---|---|
| -A | 在链末尾追加一条规则 |
| -I | 在链开头或指定位置插入规则,-I INPUT 3表示插到第 3 条 |
| -D | 删除指定规则 |
| -F | 清空链内所有规则 |
| -P | 设置链的默认策略(ACCEPT/DROP) |
| -L | 列出当前规则 |
| -n | 不做 DNS 反向解析,直接显示 IP,速度更快 |
| --line-numbers | 显示规则序号,排查顺序问题必备 |
| -p | 协议类型,比如 tcp、udp、icmp |
| -s / -d | 源地址 / 目标地址 |
| --dport / --sport | 目标端口 / 源端口,常配合 -p tcp 使用 |
| -i / -o | 数据包进入的网卡 / 出去时用的网卡 |
| -j | 匹配后执行的动作,比如 ACCEPT、DROP、REJECT、LOG、SNAT、DNAT |
实操里记住一个原则:写规则的时候先把这五个部分在心里填一遍。哪个空填不出来,就说明你对这条规则的效果还没想清楚。
2.2 从零拆解一条真实规则
拿一条很典型的规则来拆:
iptables -A INPUT -p tcp -s 10.0.0.5 --dport 22 -j ACCEPT这条规则的意思是:允许来自 10.0.0.5 的 TCP 数据包访问本机的 22 端口。
拆开看就是:
-A INPUT:在 INPUT 链末尾追加规则,也就是检查“进到本机”的包。-p tcp:只匹配 TCP 协议,这是 SSH、HTTP、HTTPS 等绝大多数应用的基础协议。-s 10.0.0.5:源地址限制为 10.0.0.5,其他 IP 发来的包不匹配这条。--dport 22:目标端口是 22。-j ACCEPT:匹配成功就放行。
注意,--dport 22虽然没有显式写-d 本机IP,因为数据包到达 INPUT 链时目标本来就是本机,所以一般不用重复写目标地址。但如果你想限制“只能访问本机的某个特定 IP”,比如本机有多网卡多 IP,那就得加上-d 具体IP。
这里还有个细节:--dport单独使用往往不生效。它必须跟在-p tcp或-p udp之后,因为端口这个概念只存在于 TCP/UDP 协议里。如果你写了iptables -A INPUT --dport 22 -j ACCEPT而没写-p tcp,有些版本的 iptables 会直接报错,有些则行为怪异。别问我怎么知道的,都是踩过的。
2.3 方向与网卡参数:什么时候该用 -i,什么时候该用 -o
-i和-o是新手特别容易搞混的一对参数。简单记:-i是数据包从哪个网卡进来,-o是数据包从哪个网卡出去。
判断依据就看当前在哪个链上。在 INPUT 链上,数据包是“进来”的,所以用-i限定来源网卡,写-o基本没意义。在 OUTPUT 链上,数据包是“出去”的,所以用-o限定出口网卡。在 FORWARD 链上,进和出两个方向都有,所以-i和-o可以同时出现,分别用来限制“从哪个网卡进来再转发到哪个网卡出去”。
举个实际例子,公司网关上有两张网卡,eth0 接内网,eth1 接外网。你想限制“只有内网网卡 eth0 进来的包才允许转发”,就可以写:
iptables -A FORWARD -i eth0 -o eth1 -j ACCEPT这条规则就是告诉内核:只有从 eth0 进来、准备从 eth1 出去的转发流量才放行。如果你不关心具体网卡,只按 IP 和端口做策略,那-i/-o不写也行,但写清楚能让规则的语义更严谨,也能避免某些情况下出现“内网进来的包也能伪装成外网流量被放行”之类的问题。
2.4 三种常见目标动作:ACCEPT、DROP、REJECT 到底怎么选
很多新手卡在 DROP 和 REJECT 的区别上。这两个动作都是“拒绝”,但表现截然不同:
- DROP 是直接把数据包扔掉,不回复任何信息。外部客户端的表现就是“一直转圈直到超时”,像是主机根本不存在。
- REJECT 是拒绝这个包,同时给发送方回一个错误信息,比如
icmp-port-unreachable。客户端会立刻收到“连接被拒绝”,反馈更快,方便排查问题。
实际使用中,我对外部未知流量倾向于用 DROP,因为它不会暴露主机的存在感,也不容易让扫描工具通过“拒绝报文”的类型来判断你在用什么防火墙策略。但在调试阶段或者内网环境,用 REJECT 更舒服,因为一眼就能看出来是被拒绝还是网络不可达。
ACCEPT 就不多说了,放行。还有几个动作虽然不常用但必须知道:
- LOG:把匹配到的数据包记录到内核日志,但不做拦截,通常配合 DROP 写在一起,既能记日志又能拦截。
- SNAT:源地址转换,典型场景是内网机器上网共享一个公网 IP。
- DNAT:目标地址转换,典型场景是端口转发。
关于 LOG 有个细节:LOG 不会终止匹配。如果你写了iptables -A INPUT -j LOG,数据包匹配 LOG 之后还会继续往下检查后面的规则。所以如果你想“记录并丢弃”,需要写两条规则,先 LOG 再 DROP,而且两条规则的条件必须一样。
3. 实战:用 iptables 搭建一套可用的防火墙策略
3.1 第一步:确定初始策略
动手之前先想清楚:你想要的模型是“默认拒绝,仅放行白名单”,还是“默认放行,手动拦截黑名单”?
我的建议是,对新部署的服务器或者安全要求较高的业务,直接上“默认拒绝”模型。这就像门禁系统的逻辑:没有明确授权的人一律不让进,而不是先让所有人进来再说。虽然前期配置麻烦一点,但长期来看安全收益最大。
先写一个最小化基线:
# 清除现有规则 iptables -F iptables -X iptables -Z # 设置默认策略 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT这里有个极其重要的顺序问题:必须先加“放行”规则,再设置默认策略为 DROP,或者至少确保你当前的操作终端流量会被放行。如果你已经有 SSH 連上服务器,直接上来一句iptables -P INPUT DROP,恭喜你,下一秒你的 SSH 就掉了。我自己干过这事,后来学乖了:每次改默认策略之前,先检查现在的 SSH 连接是不是稳定,或者干脆先加一条“放行当前来源 IP 的 22 端口”规则垫底。
-X清空自定义链,-Z清零计数器,这两步在重置规则时是常规操作。需要注意,-F只是清空规则,并不会改默认策略,所以默认策略要单独用-P设置。
3.2 放行回环接口和必要服务
确认默认策略后,一般先放行回环接口 lo,否则很多本机程序之间的通信会莫名其妙出问题。然后是已经建立的连接和关联连接,这一条几乎是必写的:
# 放行回环 iptables -A INPUT -i lo -j ACCEPT # 放行已建立连接及关联连接 iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTESTABLISHED,RELATED这条规则是 iptables 状态机制的灵魂。它允许所有“你已经建立起来的连接”的返回流量进入本机。比如你的服务器主动向外发起了一个 HTTP 请求,返回的数据包到达 INPUT 链时,conntrack 发现这是某个已建立连接的一部分,就直接放行了。如果没有这条,哪怕你服务器本身对外提供服务没影响,但主动访问外网的返回包全都会被默认策略丢掉,表现就是“能发请求但收不到响应”。
从这里也能看出 iptables 是“有状态防火墙”,它不只是逐包孤立判断,而是能记住连接的状态。这个概念理解透了,后面很多问题都可以自己推导出答案。
接着放行你真正想要对外开放的服务。以最常见的 SSH + HTTPS 为例:
# SSH 建议限制来源 IP iptables -A INPUT -p tcp -s 你的办公网段/32 --dport 22 -j ACCEPT # HTTPS 对外开放 iptables -A INPUT -p tcp --dport 443 -j ACCEPT这里长时间踩的坑是“我从公司网段访问,为什么还是连不上”。多半是因为你没搞清公司出口 IP 和你的本机 IP 不是一回事。-s后面应该填你公司出口的公网 IP,不是你自己电脑上的内网 IP。你可以先不加-s,测试通了之后再收紧,这样排障更快。
3.3 常用安全加固规则:防扫描、防 SYN Flood
下面这条命令是防止外部对服务器进行无差别端口扫描的常用姿势:
# 记录丢弃的新连接尝试(防止大量扫描日志淹没系统) iptables -A INPUT -m conntrack --ctstate NEW -p tcp --syn -j LOG iptables -A INPUT -m conntrack --ctstate NEW -p tcp --syn -m limit --limit 5/s --limit-burst 10 -j ACCEPT iptables -A INPUT -m conntrack --ctstate NEW -p tcp --syn -j DROP第一条先记录所有新建 TCP 连接,第二条用limit模块限制新建连接的速率到每秒 5 个,超过这个速度的连接会被第三条 DROP 掉。作用是给正常访问留出余量,同时把一瞬间的高频扫描包拦下来。
limit模块的--limit 5/s表示平均每秒允许 5 个包,--limit-burst 10表示在开始限速前允许先突发通过 10 个包。这个参数要按业务调整,如果服务器同时在线几百人,每秒 5 个肯定不够,可以调到 100/s。我见过有人抄了网上的“防攻击规则”直接上生产,结果正常业务的大批量连接全部被限速误伤,这种“防攻击防到自己”的案例真不少。
如果服务器内存和 CPU 足够,还可以控制单 IP 的并发连接数:
iptables -A INPUT -p tcp --syn -m connlimit --connlimit-above 50 -j REJECT意思是单个源 IP 与本机同时建立的 TCP 连接数超过 50 时,新连接被拒绝。这在某些被恶意占连接数的情况下非常有效,但要小心动态 IP 池用户(比如手机网络出口 IP 是共享的),一个池子里可能几十人共用出口 IP,限制太低会误伤。建议先观察正常业务的连接数量,再把阈值设到两倍左右。
3.4 黑白名单实战:两种思路的区别和写法
黑名单和白名单不是 iptables 的专有概念,但设计思路会影响整套规则形态。
黑名单思路:默认允许一切,只拒绝名单中的 IP。适合公网业务,因为白名单没法覆盖所有真实用户。典型写法:
# 黑名单列表 iptables -A INPUT -s 192.168.1.66 -j DROP iptables -A INPUT -s 192.168.1.88 -j DROP黑名单很好理解,但维护成本会随恶意 IP 增多而上涨。有没有办法自动维护?有,我常用的一种方式是写脚本定期解析访问日志里的异常 IP,然后批量生成规则。比如统计某个 IP 一分钟内 SSH 登录失败超过 10 次,就把它追加进 DROP 规则。把这段逻辑写进 cron,五分钟跑一次,黑名单就基本自动化了。
白名单思路:默认拒绝所有,只允许名单内 IP。更适合内部系统、管理后台这类只给特定人员使用的资源。写法也简单:
iptables -P INPUT DROP iptables -A INPUT -p tcp -s 10.10.0.0/24 --dport 8080 -j ACCEPT白名单方案最怕的就是 IP 变动。比如你人在咖啡厅办公,公司出口 IP 却不固定,每次访问都要先在防火墙里加 IP,那体验会很痛苦。我建议把白名单规则的更新脚本化,配合一个简单的 Web 管理界面做“自助申请”,后台批准后自动把申请人的当前出口 IP 加进去,这样既安全又不用每次手动改规则。
还有人说“iptables 反向允许”,其实指的就是状态机制下的返回流量放行——你不需要手动为每一个允许出去的请求写相反方向的允许规则,conntrack 的ESTABLISHED已经帮你处理了返回方向。如果你的规则里漏了ESTABLISHED,RELATED这条,就会看到“只能出不能进”或者“只能进不能出”的诡异现象。
3.5 NAT 转发与端口映射:把内网服务发布出去
很多场景下需要把内网服务的端口映射到公网,或者让内网机器通过一台 Linux 网关共享上网。这两件事分别对应 DNAT 和 SNAT。
先看端口映射。假设内网有台机器 192.168.1.100,跑着 8080 端口的 Web 服务,你想让公网用户通过这台 Linux 网关的 80 端口访问到它。
第一步是开启内核转发:
echo 1 > /proc/sys/net/ipv4/ip_forward sysctl -w net.ipv4.ip_forward=1第二步加 DNAT 规则,在 nat 表的 PREROUTING 链上把目标端口 80 改写成内网机器的 8080 端口:
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080第三步加 SNAT 规则,在内网机器处理完请求返回时,把源地址改回网关地址,让客户端以为始终在和网关通信:
iptables -t nat -A POSTROUTING -p tcp -d 192.168.1.100 --dport 8080 -j SNAT --to-source 网关内网IP第四步放行 FORWARD 链上对应流量。注意默认策略刚才设了 DROP,所以要在 filter 表的 FORWARD 链放行从任何网卡进来、到 192.168.1.100:8080 的流量,同时放行走内网网卡出去的返回流量:
iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 8080 -j ACCEPT iptables -A FORWARD -p tcp -s 192.168.1.100 --sport 8080 -j ACCEPT如果你已经写了-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT在 FORWARD 链上,返回流量那条可以不用重复写,但建议还是写出来,语义更清楚。
共享上网的 SNAT 是另一个经典场景。让内网大量设备通过网关上网,只需要一条规则的变体:
iptables -t nat -A POSTROUTING -s 192.168.2.0/24 -o eth1 -j MASQUERADEMASQUERADE 和 SNAT 的区别在于,SNAT 需要显式指定转换后的源 IP,而 MASQUERADE 会自动读取出口网卡上当前的 IP 来做转换。如果网关的出口 IP 是动态获取的(比如拨号上网),用 MASQUERADE 更省心。如果出口 IP 是固定的,用 SNAT 性能更好,也更容易查日志。
3.6 规则持久化:挡不住重启就是白干
很多人的防火墙规则在运行期好好的,一重启就全面失效。这是因为 iptables 的规则是存在内存里的,不落盘就不会自动恢复。
常见的持久化方案有三类。
第一类,发行版自带服务。RHEL/CentOS 系早期版本用service iptables save,规则会写到/etc/sysconfig/iptables。但 CentOS 7 之后的系统默认是 firewalld,iptables 服务要自己安装并启用。
Ubuntu/Debian 系推荐用iptables-persistent:
apt install iptables-persistent netfilter-persistent save netfilter-persistent reload它会读取/etc/iptables/rules.v4和/etc/iptables/rules.v6,分别对应 IPv4 和 IPv6 规则。
第二类,手动导出导入。不管什么发行版,都可以用:
iptables-save > /etc/iptables.rules恢复时:
iptables-restore < /etc/iptables.rules想开机自动恢复,可以把恢复命令写进/etc/rc.local,或者做成 systemd service 写个 ExecStart。这个方法最通用,适合各种“精简版”系统。
第三类,用配置管理工具,比如 Ansible 的iptables模块或直接管理规则文件。团队协作时效果最好,规则文件进 Git,每次变更记录都有迹可循。
我现在的习惯是:所有服务器都把/etc/iptables.rules当成受版本控制的配置文件来管理,写完规则立即iptables-save备份一份,改之前也会先备份一份。哪怕不要自动化,光这一个习惯就救过我很多次。
4. 常见问题与排查技巧实录
4.1 问题速查表:先看现象再下手
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 开启防火墙后 ping 不通 | ICMP 被默认策略拦截,或没放行 echo-request | iptables -L INPUT -n看是否有 icmp 相关规则,临时加一条-A INPUT -p icmp -j ACCEPT验证 |
| SSH 配置完规则就掉线 | 默认策略改成 DROP 前没放行 SSH | 用机房 console/带外管理登录,先-P INPUT ACCEPT保住入口,再逐步收紧 |
| 本机能访问外网,外网访问不了本机 | 入站端口没放行,或服务只监听在内网 IP | ss -lntp看监听地址,再用curl到端口测连通性 |
| 规则清空后服务还是不通 | firewalld 或其他工具叠加管理、SELinux 拦截 | 检查systemctl status firewalld,临时关闭 SELinux 测试,确认不是应用层问题 |
| 重启后规则丢失 | 没有做持久化,或持久化文件被覆盖 | 检查/etc/sysconfig/iptables或/etc/iptables/rules.v4是否存在、内容是否正确 |
| NAT 转发不生效 | ip_forward 没开、FORWARD 默认 DROP | sysctl net.ipv4.ip_forward查看,再看iptables -L FORWARD -n |
| 连接被限速误伤 | limit 参数过小,或 connlimit 阈值过低 | 查看iptables -L INPUT -n -v的计数器增长情况,调整阈值后观察业务指标 |
| 规则很多但无法判断谁生效 | 重复规则、顺序不对、自定义链跳转有误 | 加--line-numbers列出序号,用iptables -L 链名 -n -v --line-numbers看计数器 |
| 配置多了之后性能下降 | 规则过多且结构扁平,匹配每次都从头遍历 | 用自定义链拆分场景,或考虑迁移到 nftables |
4.2 最值得说道的四个坑
第一个坑:把自己锁在外面。这是几乎每个 iptables 新手都会经历的事。解决办法除了“先放行后设默认策略”之外,我再补充一个土办法:永远不要在一条命令里同时清空规则、设置默认 DROP 和添加白名单。分三步走,每步之间停顿观察一下 SSH 是否正常。如果 SSH 断了,就用带外管理(比如服务器厂商的远程管理卡)或去机房本地登录。另外,很多云厂商的控制台里有“防火墙规则”和“安全组”两层,它们和系统内 iptables 是独立生效的,排查问题别只盯着一层看。
第二个坑:DROP 和 REJECT 的语义混淆。我见过有人把 SSH 的失败尝试从 REJECT 改成 DROP 以后,排查问题时摸不着头脑,因为从客户端看就是“超时”,完全不知道是被墙了还是线路问题。反过来,生产环境不分青红皂白全用 REJECT,等于告诉扫描器“这里有人”,而且会放大日志量。合理的策略是:对外隐藏敏感端口用 DROP,对内调试和快速反馈用 REJECT,两边各取所需。
第三个坑:FORWARD 链被忽略。只写了 INPUT 链的规则,发现内网机器访问外网不通,或者端口映射不生效。转发流量走的是 FORWARD,和 INPUT 无关。很多厂家设备的“防火墙双机热备”“主备切换”里也会涉及 FORWARD 链状态同步,但这就是另一个话题了。作为运维,记住一条:只要你的 Linux 在扮演路由器、网关或容器宿主机的角色,FORWARD 链就要认真对待。
第四个坑:规则顺序和计数器不分。排查的时候用iptables -L INPUT -n -v看每个规则的 Packet 计数增长情况,能非常直观地判断某条规则到底有没有被匹配到。如果计数一直是 0,说明数据包根本没有走到这条规则,要么是前面已经有规则拦截了,要么是链不对。这个技巧比瞎猜高效十倍,我现在排障第一件事就是看计数器。
4.3 和 firewalld、ufw 的关系:到底用哪个
不少发行版预装的是 firewalld 或 ufw,它们的底层其实还是 iptables(或者 nftables)。firewalld 引入了“区域”概念,ufw 则简化了端口管理命令,对于简单场景都够用。但如果你需要精细的 IP+端口+协议匹配、需要写复杂的 NAT 规则、需要自定义链来做流量分组,直接操作 iptables 反而更顺手。
我的建议是:如果是在同一个环境里,不要混用 firewalld 和直接 iptables 命令。因为 firewalld 有自己的一套规则管理机制,它的规则和手工 iptables 规则可能互相覆盖或冲突,排查起来头大。CentOS 上我一般直接停掉 firewalld,切回原生 iptables 服务;Ubuntu 上如果装了 ufw 就用 ufw 管理,如果要用 iptables 写复杂规则,那就顺手把 ufw 停用。一句话:选一条路走到黑。
4.4 命令不存在的处理:常见精简镜像场景
有些精简版 Linux 镜像默认没有安装 iptables 命令,或者装上之后提示模块没加载。这种情况在自制的精简镜像、容器镜像里特别常见。先看包管理器能不能装:
# Debian/Ubuntu apt install iptables # RHEL/CentOS yum install iptables装完之后如果服务依然报错,很可能是内核没加载对应模块:
modprobe ip_tables modprobe iptable_filter modprobe iptable_nat modprobe nf_conntrack模块加载问题通常会报iptables: No chain/target/match by that name这类错误。确认模块加载好之后,再重新执行你的规则。这里也牵扯到一个新趋势:新版内核主推 nftables,iptables命令在很多新发行版上只是一个兼容层,底层可能已经换成了 nftables 的框架。如果你用的是很新的发行版,建议看看内核默认用的是哪个框架,直接学 nftables 也行。但 iptables 的匹配思路和规则设计理念是通用的,学透它再切 nftables 会非常顺滑。
5. 谈谈我个人的维护习惯和技巧
写到这里,我分享一下日常维护 iptables 时的一些习惯,应该能帮你避开不少弯路。
规则文件一定要有注释。iptables-save 导出的规则文件支持注释行,用#开头就行。我会把规则按业务模块分段:# SSH、# HTTP、# 防扫描、# Docker,这样过了几个月再回来改规则,能立刻定位到对应区域。如果规则是纯iptables命令脚本的形式,我也建议在脚本里写清每条规则的目的,以及“这条规则为什么这么写”,因为你自己也未必记得住当时的想法。
变更之前先备份。我已经养成了肌肉记忆:改规则前先iptables-save > /etc/iptables/backup_$(date +%F).rules存一份。这样哪怕新规则把服务搞挂了,也能秒回滚到上一个可用状态。回滚的命令是iptables-restore < 备份文件,前提是备份文件是当时执行iptables-save生成的,规则状态忠实反映了内存里的情况。
新规则先测试再上生产。有条件的话,先在测试环境把规则跑几天,观察计数器、连接数、日志有没有异常,再同步到生产。没条件的话,至少挑业务低峰期操作,并且每条规则的生效范围先放宽一点,比如先不加-s限制,确认放行后再收紧源 IP。这样做看起来慢,但比一次性把规则写“完美”又写错要好得多。
最后分享一个小技巧:iptables -L不带-n时会做 DNS 反向解析,大量规则下会非常卡,而且输出可能对着 IP 解析出的域名发呆。所以我从很早开始就只用iptables -L -n,必要时加-v看计数器。既然是排障,就别让格式问题耽误时间。
iptables 这套东西,说难不难,说简单也不简单。难的不是命令,是你能不能把“数据包怎么走、规则怎么匹配、状态怎么维护”这十几个字真正想透。想透了之后,再复杂的防火墙策略,本质上都是在这条路径上做取舍而已。