搞Linux运维和网络安全这块的,要是没跟iptables打过交道,那基本等于没进过机房。哪怕是现在nftables、firewalld这些新工具铺天盖地,大量存量服务器、容器网络方案、云平台底层策略,绕来绕去最后还是落到iptables这套规则框架上。不少面试题里也喜欢拿它考人,软考和等保测评里更是常客。正因为它太常用,反而很多人只是机械地记几条命令,规则一多就乱,出了问题不知道从哪里查起。
这篇东西我准备换个思路,不给你整那种“命令大全”式的罗列,而是从“4表5链”的底层逻辑出发,把查规则、增删改、保存还原、NAT转发、日常排障这些真正高频的操作串起来讲。每一条指令我都会说清楚它解决什么问题、背后走的是什么流程、有哪些必须避开的坑。不管你是刚接手服务器的新人,还是被防火墙规则折磨过的老手,按这条线走一遍,iptables基本就能玩转了。
1. 先把“4表5链”刻进脑子,否则后面全是死记硬背
很多人学iptables最大的障碍,是一上来就背指令。-A、-I、-D这些参数背得滚瓜烂熟,可真到了要给服务器开一个端口,或者做一条端口转发的时候,反而不知道要写在哪个表、哪条链上。问题的根源在于,没理解iptables的骨架——表和链。
1.1 三张核心表和它们的职责边界
iptables里有多张表,日常折腾得最多的其实就三张:filter表、nat表、mangle表。还有一张raw表,一般搞特殊需求的人才碰,日常运维基本用不上,了解即可。
- filter表:负责过滤,也就是我们常说的“允许”和“拒绝”。它是默认表,你平时执行
iptables -A INPUT -p tcp --dport 22 -j ACCEPT这类命令,如果不显式指定-t参数,规则默认就进filter表。工作节点是INPUT、FORWARD、OUTPUT三条链。 - nat表:负责网络地址转换,核心功能是改写数据包的源地址或目标地址。典型场景就是SNAT(源地址转换,常用于共享上网)、DNAT(目标地址转换,常用于端口映射/转发)。它工作在PREROUTING、INPUT、OUTPUT、POSTROUTING这几条链上。
- mangle表:负责数据包的标记、修改头部字段,比如修改TTL、设置QoS标记、打上MARK标记供策略路由使用。优先级最高,最先处理数据包。日常用得少,但在一些复杂限速和分流场景里是神器。
提示:查规则的时候一定要带
-t参数看清是哪张表。很多人用iptables -L -n一看没有规则,就以为防火墙没配,实际是nat表里有NAT规则没看见。
1.2 五条链的流量走向和判断技巧
链就是数据包经过的路径节点。五条链分别是:
- PREROUTING:数据包进入网卡后、路由判断前。
- INPUT:数据包目的地是本机时,进入本机进程前。
- FORWARD:数据包目的地不是本机,需要转发给其他主机时。
- OUTPUT:本机进程发出的数据包,出去之前。
- POSTROUTING:数据包即将离开网卡前。
判断一条规则该放哪条链,核心就问两个问题:数据包是发给谁的在?是进还是出?
如果是外部访问服务器本身的SSH端口,那是进,走的是PREROUTING → INPUT,过滤规则放INPUT链。如果是服务器主动访问外面的网站,那是出,走的是OUTPUT链,过滤规则放OUTPUT链。如果这台服务器是路由器/网关,内网机器通过它上外网,数据包只是路过,不发给本机,那走的是FORWARD链,同时要配合nat表做SNAT。
这个概念搞不清楚,后面所有规则都会写得四不像。我自己见过很多新手在INPUT链里写FORWARD规则,或者在nat表里做filter的事,最后只能一脸懵地排查为什么不通。
1.3 数据包在表与链之间的穿越顺序
数据包穿过一串链的时候,各个表是有优先级顺序的。顺序大致是:
- PREROUTING链:raw → mangle → nat
- INPUT链:mangle → filter
- FORWARD链:mangle → filter
- OUTPUT链:raw → mangle → nat → filter
- POSTROUTING链:mangle → nat
看这个顺序能发现一个关键点:mangle表最先处理,filter表排最后。所以如果你既想标记数据包又想做过滤,标记规则必须在过滤规则之前生效。这也是为什么有些复杂场景里,你在filter表里写的规则明明看着对,却总是不生效——因为包在mangle表里就被拦截或者标记了,根本到不了filter表。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 查询指令:你要先学会看,才能学会改
我接手任何一台服务器,第一件事永远是查看现有规则,而不是急着加规则。很多线上事故都是因为不清楚已有规则,盲目追加导致冲突或者把管理通道给堵死了。
2.1 三条查询命令和它们的差异
实践中最常用的是这三条:
bash复制iptables -L -n -v
iptables -t nat -L -n -v
iptables -t mangle -L -n -v
拆开解释一下:
-L:列出规则。-n:不做DNS反解。这个参数非常重要,如果不加,iptables会对IP做反查域名,一旦DNS解析卡住,命令会在那里干等半天,线上排障时急死人。-v:显示更多详细信息,包括每个规则匹配了多少个包、多少字节、对应的网卡接口。
我个人的习惯是再加一个--line-numbers参数,给每条规则编号:
bash复制iptables -L -n -v --line-numbers
有了编号,后面插入、删除规则就方便多了。否则你想删一条规则还得把整条规则原样打出来,非常容易打错。
2.2 查看规则的输出信息怎么看
拿一段典型的输出举例:
text复制Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 1023 102K ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
2 5 320 ACCEPT tcp -- eth0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
3 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0
几个信息值得关注:
policy ACCEPT:这条链的默认策略。如果所有规则都没匹配到,就按默认策略走。最怕看到policy DROP且规则没放行管理端口,等于把自己关在外面。pkts和bytes:匹配计数。排障时看这个数字能判断规则有没有被命中。如果你加了一条DROP规则,但pkts一直是0,说明数据包根本就没走到这条规则,问题出在更早的路径上。in和out:入口网卡和出口网卡。source和destination:源地址和目标地址。
提示:用
iptables -L -n看到的是不带注释的规则。如果之前用-m comment --comment "xxx"加过注释,要看注释需要加-v参数。
2.3 为什么推荐用“-vnL”而不是“-L”
很多教程习惯写iptables -L,看着整齐,但实际排障效率很低。原因就是不带-n会做反解,规则里的IP变成域名,看着一头雾水;不带-v看不到匹配计数,无法判断规则是否生效。
我建议把这个习惯固定下来:
bash复制iptables -vnL
iptables -t nat -vnL
参数顺序不影响结果,-vnL三个字母一次打完,省事多了。如果规则特别长,还可以加--line-numbers,然后配合sed或awk做进一步过滤,比如只查INPUT链:
bash复制iptables -vnL INPUT --line-numbers
3. 增删改指令:加一条规则前,先想清楚匹配顺序
3.1 追加、插入、替换、删除四大操作
规则管理就四个动作:追加(-A)、插入(-I)、替换(-R)、删除(-D)。
bash复制# 追加:把规则加到链末尾
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# 插入:把规则插到最前面,或者指定编号
iptables -I INPUT -p tcp --dport 80 -j ACCEPT
iptables -I INPUT 2 -p tcp --dport 443 -j ACCEPT
# 替换:把指定编号的规则换成新内容
iptables -R INPUT 3 -p tcp --dport 8080 -j ACCEPT
# 删除:按规则全文删,或者按编号删
iptables -D INPUT -p tcp --dport 22 -j ACCEPT
iptables -D INPUT 3
重点说一下插入和删除的顺序问题。iptables匹配规则是从上到下逐个匹配,一旦匹配就不再往下走(如果该规则的target是ACCEPT、DROP、REJECT,就停止继续匹配;如果是LOG等非终止target,则继续往下走)。这意味着:
- 一条规则放在前面和放在后面,效果可能完全不同。
- 用
-I插入到前面的规则,优先级最高。 - 删除规则时按编号删最稳妥,但在删除前务必重新
-L确认编号,避免误删。
我踩过一个很典型的坑:服务器上原本有一条DROP all规则兜底,我想再加一条放行8080端口的规则,顺手用了-A追加,结果新规则全程没生效。原因就是DROP all在前面已经把包拒了,后面的ACCEPT根本轮不到。正确做法是先用-I INPUT 1把放行规则插到最前面,或者找对位置插到DROP规则之前。
3.2 细说常用匹配参数
光有-A INPUT -j ACCEPT这种笼统规则远远不够,生产环境必须精确匹配。常用的匹配参数有:
-p:协议,tcp、udp、icmp、all。--dport:目标端口。只对tcp和udp有效。--sport:源端口。-s:源IP地址,可以带掩码,比如192.168.1.0/24。-d:目标IP地址。-i:数据包进入的网卡,比如eth0、ens33。-o:数据包出去的网卡。-j:动作,常见的有ACCEPT、DROP、REJECT、LOG、SNAT、DNAT、MASQUERADE。
一个稍微完整点的例子:
bash复制iptables -A INPUT -i eth0 -p tcp -s 192.168.1.0/24 --dport 22 -j ACCEPT
这条规则的意思是:允许来自192.168.1.0/24网段的主机,通过网卡eth0访问本机的22端口。
3.3 ACCEPT、DROP、REJECT怎么选
这个选择是新手最爱问的,也是安全评审里经常被挑战的点。
ACCEPT:放行,不用多说。DROP:直接把包丢弃,不回复任何信息。对方的表现就是连接超时。REJECT:拒绝,并给发送方回一个错误信息,比如--reject-with icmp-port-unreachable。对方的表现是立刻收到“连接被拒绝”。
从安全角度讲,DROP更隐蔽,端口扫描时不容易暴露本机存在服务;REJECT的优点是排障快,对方能立刻感知到不通,适合调试阶段。
实际生产里,我的习惯是:对外部未知来源一律DROP,对自己人能讲清楚的服务临时调试可以用REJECT。因为DROP不回应,能把暴露面藏到最小,也不会被扫描器一下摸清楚底细。但要注意,一旦全链DROP,自己也别想从外部连进来,所以放行规则必须提前写好。
3.4 连接状态匹配:state模块和它的进阶版
状态匹配是iptables的核心玩法,也是能写出高效规则的基石。常见写法:
bash复制iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
这条规则的意思是:只要是已建立的连接或者与已有连接相关的连接(比如FTP的数据连接),统统放行。把它放在INPUT链最前面,能大幅减少后续规则的匹配压力。
为什么它能生效?因为iptables是有状态防火墙,它会通过conntrack模块自动追踪连接状态。数据包进来时,内核会查一下这个包是不是属于一个已知连接。如果是,直接放行,不需要走到后面的逐条规则里。
我自己的习惯是在配置服务器防火墙时,永远把下面两条放在最前面:
bash复制iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
这样能保证本机发起的SSH、yum更新、DNS解析等所有正常流量的回包都能进来,同时新进来的陌生连接还得走后面的规则过滤。
conntrack模块是state模块的进阶版,支持更多状态,比如NEW、INVALID、UNTRACKED,还能配合--ctstate做更细的控制。现在的系统上两者基本都可用,但新脚本建议直接写-m conntrack --ctstate。
4. 规则保存与还原:不保存的规则,重启就没了
这是我见过最多人踩的坑。费了半天劲写了一堆规则,测下来也通,结果机房一断电重启,全部失效。别人会以为是有黑客入侵清空了规则,其实就是没做保存。
4.1 为什么iptables规则不能自动持久化
iptables的规则是直接加载到内核netfilter框架里的,它本身不负责保存。你执行的每一条iptables命令都只是改了内存里的规则集,重启后必然丢失。所以必须通过其他手段把规则导出到文件里,再在开机时导入。
不同发行版的做法不太一样:
- CentOS 6及以前:
service iptables save,规则保存到/etc/sysconfig/iptables,开机自动加载。 - CentOS 7及以上:默认没有iptables服务,需要用
iptables-services包,然后service iptables save。 - Debian/Ubuntu:用
iptables-persistent,规则保存到/etc/iptables/rules.v4和/etc/iptables/rules.v6。
4.2 通用的导入导出指令
不管什么发行版,底层通用的都是这两条:
bash复制# 把当前规则导出到文件
iptables-save > /etc/iptables.rules
# 从文件恢复规则
iptables-restore < /etc/iptables.rules
如果只想导出某张表,可以指定:
bash复制iptables-save -t nat > /etc/iptables-nat.rules
4.3 开机自动加载的几种实现方式
方法一是配置系统服务,以Debian/Ubuntu为例:
bash复制apt-get install iptables-persistent
netfilter-persistent save
netfilter-persistent reload
安装过程中它会自动把当前规则存到/etc/iptables/rules.v4,后面每次开机都会自动加载。
方法二是写开机启动脚本,适合CentOS或者其他不想装额外包的系统:
bash复制/sbin/iptables-restore < /etc/iptables.rules
把这行写进/etc/rc.local,并确保rc.local有执行权限。
提示:不管用哪种方式,保存规则前先把规则测试到万无一失。否则保存进去的规则一次比一次烂,重启后连本机都进不去,只能去物理控制台救。
4.4 我保存规则前必做的三步检查
- 用
iptables -vnL --line-numbers逐条过一遍,确认没有写错IP、端口。 - 故意断开SSH再重连一次,测试当前规则是否会影响远程管理。
- 用
iptables-save输出到临时文件,grep一下关键放行规则,确认在文件里存在。
5. 扩展匹配与NAT:从单机防护到网络转发
5.1 limit模块与hashlimit模块:限速和防扫描
除了基本匹配,iptables的-m扩展模块能实现很多高级功能。举个限速的例子:
bash复制iptables -A INPUT -p icmp -m limit --limit 1/sec --limit-burst 5 -j ACCEPT
iptables -A INPUT -p icmp -j DROP
这条规则的意思是:允许ICMP(ping)流量,但速率限制为每秒1个包,突发不超过5个包,超过的直接DROP。这样既能保持网络可探测性,又不会被ping flood打成筛子。
防端口扫描可以这样写,限制单位时间内来自某个IP的新连接数:
bash复制iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 5 -j DROP
这段做的是:同一个IP在60秒内新建SSH连接超过5次,直接DROP。虽然不能完全替代fail2ban,但应付一般扫描器足够了。
5.2 DNAT:端口映射/转发
NAT是iptables最重要的应用场景之一。最典型的DNAT用法就是把公网IP的某个端口映射到内网服务器的某个端口:
bash复制iptables -t nat -A PREROUTING -d 1.2.3.4 -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:8080
这条规则的含义是:当数据包的目标地址是1.2.3.4的8080端口时,把目标地址和端口改写为192.168.1.100:8080,然后数据包继续往后走。
别忘了,如果要让内网服务器能正确回包,通常还要配套一条SNAT或确保路由能回去:
bash复制iptables -t nat -A POSTROUTING -d 192.168.1.100 -p tcp --dport 8080 -j SNAT --to-source 192.168.1.1
如果内网服务器网关指向的就是这台iptables机器,且ip_forward已开启,有时候DNAT单独也能通。实战里最稳的还是把回程流量也处理好。
5.3 SNAT与MASQUERADE:内网共享上网
SNAT用于把内网IP转换为固定的出口IP,适合有静态公网IP的场景。MASQUERADE则适合出口IP不固定的场景,比如ADSL拨号、DHCP获取地址的宽带。
共享上网的标准配置是:
bash复制iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE
这条规则的意思是:来自192.168.1.0/24的流量,从eth0出去时,源地址自动替换为eth0的IP。
如果出口IP固定为1.2.3.4,可以改成SNAT:
bash复制iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 1.2.3.4
两者相比,MASQUERADE更省心,不需要关心IP变化,但每次建连时都要查一下当前IP,性能略低一点。高流量场景下建议用SNAT写死IP。
5.4 开启内核转发:这一步漏了,NAT全白搭
配置好NAT规则后,最常遇到的问题就是“数据包转发不出去”。多半是因为内核的IP转发功能没打开。
临时开启:
bash复制echo 1 > /proc/sys/net/ipv4/ip_forward
永久开启,修改/etc/sysctl.conf:
text复制net.ipv4.ip_forward = 1
然后执行sysctl -p生效。
提示:装Docker的机器,Docker会自动把ip_forward打开,因为容器网络依赖这个。但裸机做网关用的时候,很多人会漏掉这一步。
5.5 实际案例:一台Linux机器做简易端口转发网关
假设有一台内网服务器192.168.1.10跑着MySQL(3306),但我不能直接让它暴露公网,于是用一台有公网IP的Linux机器做转发:
bash复制# 1. 开启内核转发
echo 1 > /proc/sys/net/ipv4/ip_forward
# 2. 公网进来的3306流量,转到内网数据库服务器的3306
iptables -t nat -A PREROUTING -p tcp --dport 3306 -j DNAT --to-destination 192.168.1.10:3306
# 3. 内网数据库服务器回包时,源地址改回这台公网机器
iptables -t nat -A POSTROUTING -p tcp -d 192.168.1.10 --dport 3306 -j SNAT --to-source 公网机器内网IP
# 4. FORWARD链放行相关流量
iptables -A FORWARD -p tcp -d 192.168.1.10 --dport 3306 -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
这个方案的优势是:外部永远不知道数据库的真实IP,所有请求都必须经过这台跳板机,审计和管控都很方便。
6. 日常维护与踩坑排障:规则不生效,先别急着删
iptables的排障是有套路的。规则写了不生效,原因通常是固定的那几类:表选错了、链选错了、匹配顺序不对、默认策略拦截、ip_forward没开、conntrack状态没放行。下面整理一套排查链路。
6.1 完整排查链路:从确认数据包路径到流量计数检查
第一步,确认数据包到底走到哪一步了。用iptables -L -v看计数器。如果计数为0,说明包压根没到这条规则,往更前面找。如果计数一直在涨,说明是匹配到了但动作不对,比如该放行写成了DROP。
举个例子,你发现外部访问不了80端口,先查:
bash复制iptables -vnL INPUT --line-numbers
如果发现INPUT链第一条就是DROP all,而且pkts数字在涨,那就说明80端口的新连接包被这条DROP吃了,ACCEPT规则得插到它前面。
第二步,确认是进本机还是转发流量。如果是转发流量,检查FORWARD链,不是INPUT链。很多人在路由器上配了NAT但忘了放行FORWARD,结果内网机器死活上不了网。
第三步,检查默认策略。iptables -L最上面会显示policy ACCEPT还是policy DROP。如果是DROP,说明所有没被显式放行的流量全部丢弃,包括你回包流量,必须在规则里显式放行。
第四步,检查有没有其他安全组件拦截。比如firewalld还在运行,它和iptables是管理同一套内核netfilter框架,但配置互相覆盖,极容易冲突。上生产之前最好统一:
bash复制systemctl stop firewalld
systemctl disable firewalld
第五步,查路由和反向路由。如果数据包能进但不能回,多半是反向路由问题。可以用ip route get检查数据包的出口路径。
6.2 我曾经被“规则顺序”坑到差点重装系统
有一次给一台线上机器做加固,原本有规则:
text复制1 ACCEPT tcp --dport 22
2 DROP all
我在执行时想把新业务端口3030加进去,输入了:
bash复制iptables -A INPUT -p tcp --dport 3030 -j ACCEPT
结果新增后变成了:
text复制1 ACCEPT tcp --dport 22
2 DROP all
3 ACCEPT tcp --dport 3030
客户端访问3030端口,包走到第2条就被DROP了,第3条永远匹配不到。而且这是远程服务器,万一SSH也被DROP了,连救援入口都没了。后来我是通过云控制台的VNC登录进去,先用iptables -D INPUT 3删掉错误规则,再用iptables -I INPUT 1插入正确的放行规则才救回来。
所以我现在给自己定了一个死规矩:在生产服务器上,凡是新增放行规则,一律先-I插到最前面,或者插到DROP规则之前,坚决不用-A追加。
6.3 常见错误指令和修复对照表
| 错误现象 | 可能原因 | 修复方式 |
|---|---|---|
| 新增规则不生效 | 追加到了DROP规则之后 | 用-I插入到DROP前 |
| 重启后规则丢失 | 没做保存 | iptables-save导出,配置开机加载 |
| 内网无法上网 | 没开ip_forward / 没配SNAT | 开启net.ipv4.ip_forward=1,加MASQUERADE |
| 端口映射不通 | 只配了DNAT,没配FORWARD放行 | 在FORWARD链放行对应端口和状态流 |
| 本机能上网,外部访问不了服务 | INPUT链默认策略DROP | 显式放行服务端口 |
| 系统日志报conntrack溢出 | 连接追踪表满了 | 调大net.netfilter.nf_conntrack_max,或精简规则 |
| 规则看着对但pkts为0 | 包在mangle表被拦截或标记 | 检查mangle表规则,确认优先级 |
6.4 让规则可维护:不写注释的规则都是坑
刚开始维护iptables时我也吃过没写注释的亏:一条规则明明是自己三个月前加的,回头再看完全不记得为什么要这样写。后来强制自己用comment模块:
bash复制iptables -A INPUT -p tcp --dport 3030 -j ACCEPT -m comment --comment "3030 for application-api"
加上之后,用iptables -vnL就能看到注释,排障时一眼就能认出每条规则的用途。如果是专人负责的安全基线,还可以配合iptables-save之后的规则文件做版本管理,每次改动留痕。
6.5 在高并发的边缘场景下的性能隐患
很多人以为iptables规则条数多没关系,但它的匹配是线性的,规则越多,每个包需要比较的次数越多,高并发场景下性能损耗会很明显。
优化的思路有三个:
一是把最常用的规则放在最前面,比如放行ESTABLISHED状态的规则永远排第一位,大部分流量在这一条就被处理掉了,根本不用往后扫。
二是把不需要参与状态追踪的流量提前分流。使用raw表的NOTRACK标记可以绕过连接追踪,比如内网互访的大流量备份:
bash复制iptables -t raw -A PREROUTING -s 192.168.1.0/24 -d 192.168.2.0/24 -j NOTRACK
这样这些包就不进conntrack表,减少追踪开销。但要注意,NOTRACK之后该方向的流量也不会再有状态匹配能力,UDP这类无连接协议可能受影响,需要自己评估。
三是尽量让规则简洁,同一个IP段、端口的规则能合并就合并:
bash复制iptables -A INPUT -s 192.168.1.0/24 -p tcp -m multiport --dports 22,80,443 -j ACCEPT
用multiport模块一次匹配多个端口,能省掉好几条规则。20条规则和200条规则在高并发下的差距,压测一下你就明白了。
7. 结尾:最后分享几个我的个人习惯
iptables用久了,我最深的体会是:它不是背出来的,是查出来的。遇到任何网络不通问题,先别急着拍脑袋加规则,按“数据包走到哪了 → 哪条规则接住了它 → 动作是放还是拒 → 默认策略是什么”这条线走一遍,九成问题都能找到根源。
还有一点想专门提醒:尽量别在生产环境“裸奔”状态下做实验。我自己的做法是在测试机先把整套规则跑通,再用iptables-save导出,拿到生产环境iptables-restore导进来。这样做的风险最小,万一导入后出问题,还能一条iptables -F清空回到原点(前提是物理控制台或云VNC可用)。
如果你刚开始接触iptables,可以先把“查规则、加放行、删规则、保存”这四类操作练熟,再去碰NAT和高级模块。等哪一天你能不看手册、只用iptables-save的输出文件就还原出一台服务器的网络策略时,这台服务器的防火墙在你眼里就没有秘密了。
