iptables规则不生效?计数器+LOG+tcpdump三步定位法

发布时间:2026/10/11 14:54:34
iptables规则不生效?计数器+LOG+tcpdump三步定位法
你有没有遇到过这种场面辛辛苦苦写好了三条 iptables 规则加完之后还特意iptables -L看了一眼规则明明就在那儿可客户端那边死活连不上。这种规则写得没毛病但就是不生效的案子在我处理过的网络故障里能排进前三。很多人第一反应是删掉规则重写可真正的问题往往不在规则内容本身而在观察规则的方式。这篇文章想讲的就是一套我从实战里沉淀下来的 iptables 规则调试方法。核心思路很简单先用计数器判断规则到底有没有被命中再用 LOG 规则追踪包的决策路径最后用 tcpdump 把包层面的证据和 Netfilter 的决策对齐。无论你是在云服务器上做端口转发还是在公司内部网关维护整套防火墙策略这套思路都能把玄学排障变成按图索骥。文章适合所有需要维护 Linux 防火墙的运维、后端开发和网络爱好者我会尽量把每个判断依据和坑都讲透。1. 先分清问题归属规则没加载、被覆盖还是流量根本没走到防火墙很多人一上来就盯着规则内容看这其实是最低效的起点。iptables 规则不生效先要分清到底是三层问题里的哪一层规则没写进去、规则被其他规则抢先处理了、还是流量压根就没有经过这条链。这三类问题的排查手段完全不同。1.1 确认规则真的在正确的位置先别急着改规则用iptables -S看完整规则集。注意我特意不说iptables -L因为-L默认只展示 filter 表的内容而且默认会把 IP 解析成域名、端口解析成服务名看起来好像没问题实际上很容易被误导。-S输出的是规则保存时的原始格式比如iptables -S iptables -t nat -S iptables -t mangle -S iptables -t raw -S排查时务必把四个表都过一遍。我看到过不止一次有人把 DNAT 规则加到了 filter 表或者把转发放行规则写到了 INPUT 链规则确实保存了但语义完全不对。另外如果你用的是支持iptables-nft的发行版现在大部分新版系统默认就是iptables -S显示的仍然是一套兼容的表示但底层可能混着 nftables 的原生规则。如果服务器上有人直接用 nft 命令定义过规则建议顺手nft list ruleset看一眼否则你可能漏掉别人直接写在 nft 层的拦截项。1.2 规则顺序与默认策略存在不等于有机会执行iptables 的表和链都是顺序匹配这一点新手最容易忽略。假设 FORWARD 链里已经有一条-A FORWARD -j DROP兜底规则你又用-A在链尾追加了一条-A FORWARD -p tcp --dport 8080 -j ACCEPT那这条放行规则永远没有执行机会因为包在前面就被 DROP 吃掉了。用iptables -L FORWARD -n -v --line-numbers可以清楚看到每条规则的编号顺序。我自己的习惯是排查时先看链尾有没有大范围的 DROP 策略再看业务放行规则是不是排在 DROP 后面。很多时候问题根本不是规则写错而是规则插的位置不对。1.3 流量路径判断这条链本来就不该处理这个包第三个维度最隐蔽。iptables 的 INPUT、FORWARD、OUTPUT 链分别对应不同的流量路径选错链规则写了也是白写。我见过最典型的几个场景流量场景实际经过的链容易踩的坑外部访问本机端口PREROUTING → INPUT把入站规则写到 FORWARD本机转发到内网/其他主机PREROUTING → FORWARD → POSTROUTING只写了 NAT 规则没放行 FORWARD本机进程访问外部OUTPUT → POSTROUTING在 INPUT 里限制外部访问却拦不住出站本机访问自己回环走 lo 的 INPUT/OUTPUT规则限定在 eth0curl 127.0.0.1 却通了还有两个非常容易误导的场景。一个是网桥如果流量经过 Linux bridge 转发它走的是 FORWARD 链而不是 PREROUTING 后的本机入站而且很多 bridge 相关的 iptables 规则挂在physdev模块下。另一个是 Docker 等容器环境容器在独立的网络命名空间里容器内的 iptables 规则和宿主机是两套系统宿主机上写的规则对容器内的进程不生效需要先确认你到底该在哪个 netns 里排查。这些判断不需要猜tcpdump配合下面的计数器方法能很快确认流量实际路径。2. 计数器是第一诊断工具让每条规则自己汇报命中情况iptables 每条规则都自带两个计数器分别统计匹配的数据包数量和字节数。这不是鸡肋功能它是定位问题的第一步。很多规则不生效的谜题看一眼计数器基本就有答案了。2.1 怎么看计数器查看计数器要加-v参数同时建议加-n避免 DNS 解析干扰iptables -L INPUT -v -n --line-numbers iptables -t nat -L PREROUTING -v -n --line-numbers iptables -L FORWARD -v -n --line-numbers输出里每一行前面的pkts和bytes就是该规则累积匹配的包数和字节数。刚开始排查时先找出与你关注端口相关的规则盯住它的计数器。2.2 三种计数器状态分别说明什么第一种情况计数器始终是 0。这说明包根本没有到达这条规则。原因可能是前面的规则已经把它处理掉了也可能是流量压根没有进入这张表或这条链甚至包在更早的环节就被丢弃了。这时候不要继续盯着这条规则分析要往上游找。第二种情况计数器在持续增长但业务还是不通。这是最耐人寻味的。规则匹配上了说明包确实走到了这里但它执行的 action 被后续机制覆盖了。常见原因包括规则在子链里执行了 ACCEPT但子链结束后又被外层链的规则 DROP或者规则只是 LOG/NOTRACK 之类的非终结动作后面还有别的规则在等着也可能包被 ACCEPT 之后在 POSTROUTING 阶段又因为 NAT 或路由问题丢了。第三种情况计数器增长量级和你的预期对不上。比如你以为只有一台客户端在测试计数器却像被压测了一样狂涨。这可能是因为你盯的规则太宽泛比如没有限定端口或源地址把所有流量都算进来了。此时该做的是收缩匹配条件让计数器只统计你要观察的那一类流量。2.3 构造一对一的测试流量让计数器开口说话为了不让其他流量干扰判断我通常会构造一个特征非常明显的测试流量然后单独盯住对应规则。比如在客户端上指定源端口发起连接# 客户端执行指定源地址和源端口 nc -s 192.168.10.5 -p 33333 服务器IP 8080然后在服务器上刷新计数器重点看有没有一条规则匹配了源 IP192.168.10.5、源端口33333的流量。如果日志或抓包工具能带上这个端口号整个排查噪音会被压到最低。这条技巧在流量比较大的生产环境里尤其好用普通 curl 测试产生的包混在几万条连接里根本没办法用计数器精确定位。2.4 清零计数器给问题一个干净的起点排查时我习惯先把关注链的计数器清零然后重新跑一轮测试这样数字变化一目了然。清零命令# 只清空某条链内所有计数器 iptables -Z INPUT # 只清空某条链的某条规则 iptables -Z INPUT 3注意iptables -Z不加参数会清空整个 filter 表的所有链计数器生产环境用的时候要小心最好带上链表名和规则号缩小影响面。清零之后再做一次最小化测试基本就能看到那条规则到底有没有被命中。2.5 不要忽视 conntrack 状态表的配合计数器回答的是规则有没有匹配conntrack 回答的是连接处于什么状态。尤其在FORWARD和NAT场景很多问题其实是状态表异常导致的比如连接跟踪表满了新连接被丢弃或者回程包的状态和预期不一致。排查时配合查看cat /proc/net/nf_conntrack # 或者用 conntrack 工具 conntrack -L如果看到大量 TCP 连接处于TIME_WAIT或异常状态先把连接跟踪的问题解决再回头看规则。顺序反过来容易把简单问题复杂化。3. LOG 规则当探针把每一步决策变成可读的内核日志计数器能告诉你规则有没有被命中但回答不了一个包在整个链里到底经历了什么。这时候就需要 LOG 规则。它的原理很简单内核在匹配到 LOG 规则的瞬间把包的关键信息通过 printk 写进内核日志然后继续执行后面的规则。你可以把它理解成在防火墙决策路径上埋了一个个探针。3.1 LOG 规则怎么写先看一个基本的例子# 在 FORWARD 链最前面加一条 LOG观察转发流量 iptables -I FORWARD 1 -p tcp --dport 8080 -j LOG --log-prefix DBG-FWD-IN: --log-level 4--log-prefix是日志前缀最好写成一目了然的名字方便 grep。--log-level对应内核日志级别一般用 4warning就够了。关键点在于LOG 是一个非终结 target它记录完日志之后包还会继续走下一条规则。所以你可以在一张链的头部和尾部各放一条 LOG头部看的是一进链时的状态尾部看的是前面所有规则都执行完、即将离开这条链时的状态。3.2 日志去哪看内核日志默认写到内核环形缓冲区读取方式dmesg -T | grep DBG-FWD-IN # 或者实时跟踪 journalctl -k -f | grep DBG-FWD-IN如果系统里配置了 syslog 把 kern 级别的日志落盘也可以直接tail -f /var/log/kern.log。注意 LOG 规则走的是内核日志通道和 Nginx、Java 那类应用日志完全是两个体系很多新手在/var/log/messages里找不到 LOG 输出就开始怀疑规则没生效其实只是没找对地方。3.3 生产环境一定要限流否则日志风暴会教做人不加限制的 LOG 规则在高流量链路上能瞬间刷爆内核日志缓冲区极端情况下会拖慢整个系统的性能甚至把磁盘写满。给 LOG 加上limit匹配是基本素养iptables -I FORWARD 1 -p tcp --dport 8080 -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix DBG-FWD-IN: --log-level 4--limit 5/min表示每分钟最多记录 5 条--limit-burst 10是允许的初始突发量。这样既能看到日志又不会把系统打爆。调试完成后记得把 LOG 规则删掉这东西留在生产环境就是一个定时炸弹。3.4 更高级的追踪方式nftables 的 trace 机制如果你用的系统已经是 iptables-nft 后端现在大多数新版发行版都是Linux 还提供更细粒度的 trace 机制能看到一个包在每个 hook 点的完整决策过程。用法是先给包打上追踪标记再在另一个终端监听# 终端 A开始监听追踪事件 nft monitor trace # 终端 B给目标流量打标记 nft add rule inet filter INPUT tcp dport 8080 meta nftrace set 1执行之后你会看到类似这样的输出trace id 9f4b2c1a inet filter INPUT packet: iif eth0 ... trace id 9f4b2c1a inet filter INPUT rule ... verdict continue trace id 9f4b2c1a inet filter INPUT rule ... verdict accept通过 trace id 可以把同一个包的前后决策关联起来相当于把整个 Netfilter 的决策路径完整画出来了。这个功能排查复杂转发链时特别值钱比逐个加 LOG 规则高效得多。3.5 日志怎么解读一条包多个探针在链路首尾各加 LOG 规则之后你会看到同一个连接产生了多条记录。如果链首记录有、链尾记录没有说明包在中间某条规则被终结了要么被 ACCEPT 跳出了链要么被 DROP 丢弃。如果两条记录都在再看它们之间的其他 LOG 记录就能精确锁定是哪一条规则做出了改变命运的决定。4. tcpdump 交叉验证把抓包点和 Netfilter 决策点对齐日志和计数器反映的是 Netfilter 内部的决策但网络问题经常是包根本没到服务器或包被更上游的设备丢了。要排除这些可能必须用 tcpdump 从包层面拿到原始证据。但这里有个非常容易混淆的点tcpdump 抓到的包和 iptables 看到的包在路径上并不是同一时刻的。4.1 观测点差异抓得到包不等于防火墙放行了tcpdump 利用 AF_PACKET 套接字在协议栈很靠前的位置抓包对入站流量来说它在进入 Netfilter hook 之前就能看到包。所以当你看到eth0 上明明抓到了 SYN但 iptables 计数器纹丝不动时不要惊讶这正是两个观测点不同步造成的。包被网卡驱动接收后可能在到达 hook 之前就被丢掉了也可能是你盯错了 hook。比较实用的流程是先在物理网卡上确认包确实到了再进协议栈内部确认包被怎么处理# 抓取发往本机 8080 端口的 SYN 包 tcpdump -nni eth0 tcp port 8080 and (tcp[tcpflags] tcp-syn ! 0) -c 10如果这个命令什么都抓不到那问题基本不在 iptables而在更上游可能是路由不通、对端没发包、或者云平台上层网络策略把流量直接拦掉了。别急着怀疑自己的防火墙规则。4.2 四条基本结论帮你快速定位层次根据 tcpdump 结果和 iptables 计数器的组合可以做一个快速判断tcpdump 表现iptables 计数器表现问题层次网卡抓不到 SYN任何计数器都不涨上游网络/安全策略/路由问题网卡有 SYNINPUT 链计数不涨相关规则为 0包被 PREROUTING/raw 表处理掉或走的是 FORWARD 路径INPUPT 链计数涨但进程收不到服务监听/应用层问题检查 listen、backlog、SELinux回包没有出去OUTPUT/POSTROUTING 计数异常路由、SNAT、反向路径过滤问题这里面最容易被忽略的是 FORWARD 路径。很多人在网关做端口映射只盯 INPUT 链可转发流量根本不过 INPUT它只走 FORWARD。这种情况在云服务器上做端口转发时尤其常见后面第五章我会用一个完整案例复盘。4.3 抓 NAT 包时别被 DNAT 改写搞糊涂还有一个高频迷惑点你在网关的 eth0 上抓包看到的目的 IP 是公网 IP于是觉得 DNAT 规则没生效。其实未必因为 tcpdump 在入方向上抓到的包大概率是进入 Netfilter 改写之前的原始包DNAT 把目的 IP 从公网地址改成内网地址的动作发生在它后面。所以判断 DNAT 是否生效最可靠的手段还是看 nat 表的规则计数器iptables -t nat -L PREROUTING -v -n --line-numbers如果 DNAT 规则的pkts在增长说明改写动作确实发生了。如果你还想从包层面看到改写后的结果可以tcpdump -nni any或者在内网目标主机上抓包确认。始终记住一点tcpdump 和 iptables 的观测点不同结合起来看才能还原完整链路。5. 一次 DNAT 端口映射不生效的完整复盘理论讲再多不如走一遍真实排查链路。下面这个案例我做过很多次类似的处理里面的陷阱非常有代表性。场景是一台双网卡网关eth0 接外网eth1 接内网想把公网 IP 的 8080 端口转发给内网主机 192.168.8.10 的 80 端口。规则如下iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 192.168.8.10:80 iptables -A FORWARD -i eth0 -o eth1 -p tcp --dport 80 -j ACCEPT然而外网客户端访问网关公网 IP 的 8080 端口一直超时。我们按秩序来排查。5.1 第一步先看规则是否在再看计数器我先执行了iptables -t nat -S PREROUTINGDNAT 规则在没问题。然后看计数器iptables -t nat -L PREROUTING -v -n --line-numbersDNAT 那一条pkts在持续增长说明外部流量确实到达了 PREROUTING 链改写动作也执行了。于是问题缩小到转发路径。再看 FORWARD 链iptables -L FORWARD -v -n --line-numbers输出里有一条DROP all -- 0.0.0.0/0 0.0.0.0/0的兜底规则它的pkts涨得飞快。而那条ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80的计数却是 0。到这里嫌疑已经很重了。5.2 第二步加 LOG 规则把决策过程钉死为了确认包到底是在哪里被丢的我在 FORWARD 链首尾各加了一条 LOG 规则iptables -I FORWARD 1 -p tcp --dport 80 -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix DBG-FWD-IN: iptables -A FORWARD -p tcp --dport 80 -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix DBG-FWD-OUT: 然后在另一台终端跑dmesg -T | grep DBG-FWD。结果非常明显DBG-FWD-IN有记录DBG-FWD-OUT没有。这就证明包进入了 FORWARD 链但还没走到链尾就在中途被 DROP 规则终结了。5.3 第三步查规则顺序根因水落石出我用iptables -S FORWARD查看规则完整列表发现兜底 DROP 规则是之前某个安全加固操作追加的而新的 ACCEPT 规则因为用了-A追加在它后面。虽然内容都对但包先撞上 DROP根本轮不到 ACCEPT。这就是典型的顺序问题。那条 DROP 规则本来是为了兜底拒绝非法转发流量却被当成万能保险用最后把合法的端口转发也一并干掉了。修复方式是把 ACCEPT 规则插到 DROP 之前同时补上回程流量的放行iptables -I FORWARD 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -I FORWARD 2 -i eth0 -o eth1 -p tcp --dport 80 -j ACCEPT注意内网主机返回的流量也要经过 FORWARD 链如果不放行回程包客户端能发请求但收不到响应表现依然是超时。加上状态放行规则后再测试端口通了计数器也显示 ACCEPT 规则正常增长。5.4 案例复盘教训是什么这个问题的根因并不是iptables 规则没生效而是规则存在但顺序不对。《防火墙调试》里最核心的原则就是不要只问规则为什么没生效要问包在哪一步被谁处理了。计数器告诉你包在哪条规则上被命中LOG 告诉你决策过程顺序梳理则告诉你为什么命中顺序和你设想的不同。三步走完绝大多数问题都能定位。6. 从设计上避开调试地狱规则组织与变更管理的经验排查技巧再熟练也不如从一开始就把规则设计得不容易出错。这些年调试了太多防火墙我总结出几个让规则集更可控的习惯分享给大家。6.1 明确兜底策略但别用一刀切 DROP默认策略-P FORWARD DROP是好事但很多人在链尾再加一条-A FORWARD -j DROP作为双保险这就容易出问题。每当你新增业务放行规则时如果忘了用-I把它插到 DROP 前面这条新规则就形同虚设。更好的做法是用链默认策略承担兜底拒绝职责链内不要放无条件的 DROP。如果确实需要显式 DROP把它放在链的最后并且所有业务放行规则统一用-I插入到链首。在链首先放行ESTABLISHED,RELATED回程流量再放行具体业务端口。6.2 conntrack 状态规则的位置很多单向通问题的根源是回程包没有被妥善处理。对转发场景来说链首放一条iptables -I FORWARD 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT能极大简化后续规则。有了它你就不需要为每个内部服务单独配置回程放行规则只需关心主动发起的新连接。注意-m conntrack --ctstate是较新的写法旧系统上可能是-m state --state语义基本一致优先用前者。6.3 双栈环境别忘了 IPv6现在很多服务器默认开启 IPv6但 iptables 命令管理的是 IPv4 规则IPv6 的规则要单独用ip6tables维护。我碰到过好几次这样的案例IPv4 端口一切正常某个客户端却连不上最后发现对方走的是 IPv6而服务器上ip6tables -L显示所有入站流量都被 DROP。如果你的业务没有任何 IPv6 需求建议直接禁用 IPv6 或者明确配置ip6tables策略不要让没配置变成意外拒绝。排查时也要养成习惯iptables -S ip6tables -S两个都看一眼再下结论。6.4 规则变更必须有三步预案生产环境上改防火墙最忌讳改完再想怎么回滚。我个人的固定流程是先备份、再变更、最后验证。# 第一步全套备份带时间戳 iptables-save /root/fw-backup/iptables.$(date %F_%H%M%S).rules ip6tables-save /root/fw-backup/ip6tables.$(date %F_%H%M%S).rules # 第二步执行变更举例 iptables -I FORWARD 1 -i eth0 -o eth1 -p tcp --dport 8080 -j ACCEPT # 第三步验证 iptables -L FORWARD -v -n --line-numbers | grep 8080备份文件最好同步到另一台机器因为防火墙配置出错之后这台机器可能连 SSH 都进不来本地备份等于白搭。我甚至见过把 iptables 规则文件放在/etc/下并配置了开机自动加载结果试规则时不小心把当前连接也断了重启之后规则照样是坏的。有了异地备份至少能保证尽快恢复。6.5 用注释和自定义链管理大型规则集规则一多看iptables -S的输出就变成一件痛苦的事。两个小技巧能明显改善给关键规则加注释后续排查时一眼就能看出这条规则是干什么的iptables -I INPUT 5 -s 192.168.8.0/24 -p tcp --dport 22 -m comment --comment allow admin ssh -j ACCEPT按业务拆自定义链把某一类流量放在单独链里管理主链保持清爽iptables -N WEB iptables -A WEB -p tcp --dport 80 -j ACCEPT iptables -A WEB -p tcp --dport 443 -j ACCEPT iptables -I FORWARD 1 -i eth1 -m comment --comment web forward -j WEB这样做的好处是某天 WEB 业务要整体下线直接清空自定义链就行不用在几百条规则里逐个找。调试 iptables 这么多年我最大的体会是永远不要相信直觉永远让计数器说话。规则写了没生效先别急着删了重写先确认规则确实在正确的位置、再确认包确实走到了这条规则、最后再确认是哪个动作把它终结了。只要把包从哪来、到哪去、在哪一步被决策这三件事弄清楚大部分防火墙问题都能在十分钟内定位。如果你现在手头正好有一个不生效的规则建议按这个顺序来iptables -S看全量规则和顺序-v -n -L看计数器加 LOG 看路径再上 tcpdump 看包最后才谈改规则。多数时候你会发现在规则不生效之前其实还横着一条被遗忘的默认策略、一次错误的链选择或者一个你压根没注意到的 IPv6 地址。