只要你还跑着Linux服务器,iptables就不是可以绕开的东西。不管是云主机、物理机、还是公司内部的路由设备,几乎所有流量进出都在某个环节被netfilter框架审查过一遍,而iptables恰恰就是我们管理这套审查规则最常用的入口。很多朋友一上来就复制几条命令,规则能加能删就觉得会了,可一旦遇到“为什么端口不通”“为什么重启规则没了”“为什么nat表报错”就彻底卡住。
这篇文章就是想把这个缺口补上。我会先讲清楚表、链、规则这几个核心概念,再把数据包在Linux内核里的完整走向捋一遍,然后直接带你把一台机器的防火墙从零配起来:默认策略、SSH和Web放行、NAT端口转发、防暴力破解、规则持久化,每一个命令都会解释为什么这么写。最后把我踩过的坑和常见的排查思路整理成速查表。
适合谁看?刚接手Linux服务器的运维新人、写后端但要自己部署服务的开发者,以及那些一直用firewalld/ufw掩盖底层细节、真出问题就抓瞎的朋友。
1. iptables到底是什么:先搞清它和“防火墙”的关系
1.1 netfilter与iptables的分工
每次看到有人把iptables说成“防火墙软件”,我都想纠正一下:iptables只是一个用户态命令,真正干活的是Linux内核里的netfilter框架。你把一条规则敲进去,iptables通过setsockopt、getsockopt这一类系统调用,把规则塞给内核里挂在各个协议栈关键点上的钩子函数。数据包每到达一个钩子点,内核就按顺序执行挂在这个点上的规则,决定它是放行、丢弃、拒绝,还是转向其他处理流程。
这一点看似琐碎,但理解后你就知道为什么“规则加了没用”这个问题经常出现:要么规则没真正写进内核(比如内核模块没加载),要么规则挂在错误的链上,要么规则顺序不对。排查的第一步永远不是怀疑配置,而是确认内核里到底有什么。
1.2 都202x年了,为什么还要学iptables
新版Linux内核和主流发行版已经全面转向nftables,很多系统里你敲iptables命令,底层其实已经被映射到nftables了。但现实是:存量服务器里还有大量老系统,网上绝大多数教程、运维文档、云厂商帮助中心写的还是iptables命令,Docker的端口映射原理也完全依赖netfilter的NAT逻辑。不把iptables这套概念吃透,你连Docker的-p参数为什么有时会失败都说不清楚。
所以iptables不是一门“过时技术”,而是理解Linux网络处理的底层语言。学懂了它,再看nftables、firewalld、ufw,都是在同一个知识框架上加一层壳而已。
1.3 本文的适用范围
这篇文章围绕Linux主机上的iptables展开,讨论的是标准内核协议栈、物理机或虚拟机场景。至于Windows防火墙(比如Win11防火墙错误代码0x800706d9)、企业级设备(华为USG、H3C、山石、锐捷等厂商的Web界面和命令行)是另一套体系,不在本文范围内。但理解本文讲的链、NAT、策略顺序这些思想,对你操作任何防火墙设备都有帮助,因为底层逻辑是相通的。
2. 核心概念拆解:表、链、规则怎么配合
2.1 五条内置链:数据包的必经之路
链的本质是数据包在内核协议栈中的“检查点”。iptables内置了五条链:PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。
- PREROUTING:数据包刚进入协议栈、还没做路由决策时经过,常用于DNAT,也就是目的地址转换。
- INPUT:目的地址是本机的数据包进入本地进程前经过,本地主机防火墙的主要战场就在这里。
- FORWARD:不是发给本机、只是路过本机转发的数据包经过,在做路由器或网关时使用。
- OUTPUT:本机进程发出的数据包在发出前经过。
- POSTROUTING:数据包准备离开协议栈前经过,常用于SNAT和MASQUERADE,也就是源地址转换。
用生活化的方式理解:你是一个小区门口的保安。PREROUTING是“来人先看一眼身份”,INPUT是“进楼的人登记”,FORWARD是“通过小区去隔壁小区的人只判断放不放行”,OUTPUT是“本楼住户出门时检查有没有带违禁品”,POSTROUTING是“离开时在出门条上盖个章,改一下显示身份”。每个环节干的事不一样,挂错环节的规则自然就不生效。
2.2 四张表:职责要分开
有了检查点,还需要分工。iptables的表就是不同的“业务模块”,同一张表可以挂到多条链上,不同表可以挂到同一条链上,规则按表、按链组织。
| 表名 | 主要职责 | 通常会用到哪几条链 |
|---|---|---|
| filter | 过滤,决定放行还是拒绝 | INPUT、OUTPUT、FORWARD |
| nat | 网络地址转换,改写源地址或目的地址 | PREROUTING、OUTPUT、POSTROUTING |
| mangle | 修改数据包头、设置标记,QoS常用 | 所有五条链都可用 |
| raw | 跳过连接跟踪,性能调优用 | PREROUTING、OUTPUT |
四张表在同一条链上是有执行优先级的,顺序一般是 raw → mangle → nat → filter。同一个数据包在同一链上,会先过raw,再过mangle,再进nat,最后才轮到filter。很多人习惯把所有规则一股脑往INPUT上堆,结果用到NAT的时候发现规则写到了POSTROUTING上,或者把filter规则写进了nat表,命令虽然没报错,效果却完全不对,就是这个分工没搞清楚。
2.3 规则的三个要素:匹配条件、目标、计数器
一条iptables规则的完整写法可以拆成三个部分:
- 匹配条件:什么样数据包会被这条规则匹配,比如协议类型、源IP、目的端口、网卡接口、状态。
- 目标(target):匹配之后怎么办,常见的有ACCEPT(放行)、DROP(丢弃且不响应)、REJECT(拒绝并回错误信息)、LOG(记录日志)。
- 计数器:内核会自动记录这条规则匹配了多少个包、多少字节,方便调试。
看一条例子:
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT拆开就是:追加(-A)一条规则到INPUT链;匹配条件是“TCP协议、目的端口22、来源是192.168.1.0/24网段”;命中后目标(-j)是ACCEPT。其余不满足这几个条件的包,这条规则根本不看它,继续往链的下一条规则走。
2.4 规则顺序与“匹配即落定”
iptables的链是顺序执行的,规则从上到下逐条匹配,一旦某条规则被匹配,后面的规则就不再看了。这就是“匹配即落定”原则。它带来一个非常重要的结论:规则顺序就是规则逻辑。
举个例子,你想“除了192.168.1.100这台机器,其他都不许访问22端口”。如果先写“全部DROP 22端口”,再写“允许192.168.1.100访问22端口”,后者永远不会生效,因为包在第一条就被DROP了。正确的顺序一定是先放行特定IP,再拒绝其余所有。
这个坑几乎每个新手都踩过。所以我建议你在动手配置之前,先拿张纸把规则的先后关系画出来,尤其是有“全局拒绝”和“放行例外”同时出现时。
3. 从零配置:策略先行,规则跟上
3.1 配之前先看清现状
很多人拿到一台机器就刷规则,这是最危险的操作。第一步永远是看现状,我一般用这三条命令:
iptables -L -n -v --line-numbers iptables -t nat -L -n -v --line-numbers iptables-save-L是列出所有规则,-n不做反向解析IP,跑得快也不会被DNS带偏,-v显示计数器,--line-numbers显示行号,方便后面按行号删规则。iptables-save输出的是标准的持久化格式,能看出当前所有表的完整配置。如果你连现在机器上有什么规则都不知道,就不要动任何一条规则。
3.2 默认策略:DROP还是ACCEPT要想清楚
默认策略(policy)是链上所有规则都没匹配时,最终执行的兜底动作。一条链只能有一个默认策略,可以用-P修改。比如:
iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT这里有个灵魂问题:INPUT默认策略是选DROP还是ACCEPT?
- DROP策略:默认拒绝一切,只放行你明确写出来的流量。安全系数高,但配置时必须先把SSH等管理通道放行,否则一条规则写错,你就被锁在外面了。
- ACCEPT策略:默认放行一切,只拒绝你明确禁止的流量。安全性差一些,但不容易断连,适合内部测试环境。
我个人的习惯:凡是暴露在公网的机器,INPUT和FORWARD一律DROP,OUTPUT按需选择;内网机器可以先ACCEPT,再逐步收紧。生产环境走上线前,一定要经过DROP策略的完整验证。
3.3 黑白名单两种落地方式
热点里经常看到“防火墙黑白名单”,在iptables里其实就是两种完全不同的配置思想。
白名单模式:先放行已知合法流量,然后默认拒绝一切。典型配置是先把回环、已连接状态、明确的业务端口都放行,最后把INPUT的默认策略设置成DROP。它的优点是被攻击面最小,缺点是每加一个新服务都要改防火墙,运维负担重。
黑名单模式:默认一切放行,只封禁已确认的恶意IP、恶意端口。实现起来很简单,几条DROP或REJECT规则就能搞定,日常维护省事。缺点是机器暴露面大,只适合信任度高的内网环境。
需要特别提醒的是,黑白名单不是二选一写死的事。生产上常见的是“白名单为骨架、黑名单做补充”:整体走DROP默认策略,再把已知攻击IP用黑名单提前DROP掉,减少它们探测你的机会。
3.4 常用命令速查表
| 操作 | 命令 |
|---|---|
| 查看filter表规则 | iptables -L -n -v --line-numbers |
| 查看nat表规则 | iptables -t nat -L -n -v --line-numbers |
| 清空filter表规则 | iptables -F |
| 清空nat表规则 | iptables -t nat -F |
| 设置INPUT默认策略为DROP | iptables -P INPUT DROP |
| 在INPUT链顶部插入规则 | iptables -I INPUT 1 规则 |
| 删除第3行规则 | iptables -D INPUT 3 |
| 保存规则到文件 | iptables-save > /etc/iptables/rules.v4 |
其中-I INPUT 1这种“在顶部插入”的写法特别实用。因为新规则在最前面,优先级最高,适合临时加一条紧急放行、紧急封禁,不需要关心原来规则的长短。
4. 实操案例:一台Linux主机的完整防护配置
4.1 场景设定
假设我手里有一台新装的Ubuntu服务器,对外IP是203.0.113.10,上面的业务是:
- SSH远程管理,端口22,只允许管理网段192.168.10.0/24连入;
- 对外提供Web服务,80和443端口对所有来源开放;
- 内网还有一台Web服务器192.168.1.100,需要把公网80端口转发给它;
- 这台机器还要作为内网网关,让内网机器共享公网出口上网。
下面所有命令都按这个场景来写,你可以直接照着改IP和端口用。
4.2 基础放行:回环、已连接状态、SSH、Web
配置顺序非常重要,先放行“必须通的”,最后才收紧。我通常按这个顺序来。
# 1. 清空旧规则,防止之前的配置干扰 iptables -F iptables -t nat -F iptables -X # 2. 默认策略先设为ACCEPT,等规则都加完再收紧 iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT # 3. 回环接口直接放行 iptables -A INPUT -i lo -j ACCEPT # 4. 已建立连接及衍生连接放行 iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # 5. 放行SSH,只允许管理网段 iptables -A INPUT -p tcp -s 192.168.10.0/24 --dport 22 -j ACCEPT # 6. 放行Web服务 iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT # 7. 放行ping,方便连通性排查 iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT # 8. 最后收紧:其他入站流量全部丢弃 iptables -P INPUT DROP第4条很多人会漏。没有这条,即使你前面的规则放行了22端口,每次SSH连接建立后的交互包也可能被视为新连接被后续DROP规则拦掉,表现就是能连但一敲命令就断。ESTABLISHED是已建立的连接,RELATED是与此相关的衍生连接,比如FTP数据连接、ICMP错误报文。这两个状态放行之后,业务的表现会稳定很多。
第8步把INPUT默认策略设为DROP之前,一定要确认前面几条放行规则都已经加上,并且你的SSH连接还活着。我见过太多人第8步一执行,当前SSH会话直接断掉,后面重新登录也进不去了。
4.3 NAT端口转发与内网上网
端口转发的核心是DNAT,在PREROUTING链上完成。公网用户访问203.0.113.10的80端口时,目的地址被改写为192.168.1.100:80:
iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:80这里要解释一个关键点:DNAT只改了目的地址,应答包回来的时候,源地址是192.168.1.100,而不是用户访问的203.0.113.10。如果不做源地址转换,用户侧就会觉得“连接来了但没人说话”。解决办法是加一条SNAT,让内网服务器应答时把源地址伪装成网关的对外地址:
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADEMASQUERADE是SNAT的一种特殊形式,适合出口IP动态变化的场景,比如拨号、DHCP地址经常变的链路。它不需要手动指定转换后的源地址,会自动取网卡当前的IP。如果出口IP是固定的,也可以用显式SNAT:
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10上面这条命令同时解决了内网机器上网的问题:内网192.168.1.0/24的机器出去时,源地址都被改成公网IP,应答包也能正确回到内网。但要注意,跨网段转发还需要开启内核转发开关:
sysctl -w net.ipv4.ip_forward=1这个参数不改,FORWARD链路即使有允许规则,内核也不会转发任何包。想让配置永久生效,把它写进/etc/sysctl.conf并执行sysctl -p。
FORWARD链别忘了放行,很多人NAT配完了发现内网还是上不了网,结果一看FORWARD默认策略是DROP,转发包直接被拦掉。可以在上面基础配置里补一条:
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT4.4 限制SSH暴力破解
SSH暴露在公网,扫描和暴力破解几乎是24小时不打烊的。iptables可以基于连接速率做限流,虽然不如Fail2ban那么智能,但胜在零额外依赖,内核原生支持。
思路是:同一个来源IP,每分钟新建的连接数超过一定阈值,就拒绝它后续的新连接。
# 放行速率内的新SSH连接 iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m limit --limit 5/minute --limit-burst 10 -j ACCEPT # 超过速率的SSH新连接一律丢弃 iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j DROP # 已建立的SSH连接不受影响 iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT--limit 5/minute表示平均每分钟放行5个新连接,--limit-burst 10表示允许峰值时连续放行10个新连接,然后才开始按平均速率节制。这种配置对正常使用几乎没有影响,但能把扫端口的攻击脚本挡掉一大半。要注意,这两条规则必须在基础配置中SSH放行规则之后插入,并且排在DROP默认策略之前,否则新连接的包会被默认策略直接丢掉,限流就失去意义了。
实际用下来,我还会配合SSH本身的配置:修改默认端口、禁用密码登录改用密钥、限制允许登录的用户。iptables限流只是第一道闸,真正扛住暴力破解的是密钥和账户策略。
4.5 让规则永久生效
手敲的iptables规则存在内核里,重启就没了。想让它在重启后自动恢复,标准做法是先把规则导出,再在开机时导入。
Debian/Ubuntu下可以安装iptables-persistent这个工具,也可以手动操作:
iptables-save > /etc/iptables/rules.v4 ip6tables-save > /etc/iptables/rules.v6然后在系统启动脚本或网络接口配置里调用iptables-restore < /etc/iptables/rules.v4。用systemd的话,可以写一个简单的service:
[Unit] Description=Restore iptables rules After=network.target [Service] Type=oneshot ExecStart=/usr/sbin/iptables-restore /etc/iptables/rules.v4 ExecStart=/usr/sbin/ip6tables-restore /etc/iptables/rules.v6 RemainAfterExit=yes [Install] WantedBy=multi-user.target保存规则这件事,一定要在规则全部验证通过之后再执行。我习惯在每次规则调整后先保存一份带时间戳的备份,比如rules.v4.20250115,这样万一新规则出问题,还能快速回滚到上一份可用配置。注意,如果这台机器同时在跑Docker,规则保存和恢复要特别小心,因为Docker启动时会往nat表写入自己的链和规则,两个体系叠加,顺序错了很容易让容器网络出问题。
5. 常见问题与排查技巧实录
5.1 报错nat表不存在:先查内核模块和容器权限
有阵子我常遇到这类报错:
iptables v1.8.9 (legacy): can't initialize iptables table `nat': table does not exist第一次遇到时我也懵了,明明同一套配置在别的机器上好好的。后来排查发现,问题出在三个方向:
- 内核没有加载NAT相关模块。很多最小化安装的Linux发行版,nat表默认不启用。可以先用
lsmod | grep nat看看有没有nf_nat、iptable_nat这类模块,没有就手动加载:
modprobe iptable_nat modprobe nf_nat加载完再看看cat /proc/net/ip_tables_names,正常情况下应该能看到nat字样。
容器环境权限不足。如果你在Docker容器里敲iptables,容器默认没有NET_ADMIN权限,netfilter相关操作会被内核拒绝,表现就是各个表都初始化不了。解决办法是启动容器时加
--privileged或至少--cap-add=NET_ADMIN。不过这会把容器的网络权限放大,自己权衡。iptables的legacy后端与nft后端选错。iptables 1.8.x开始分
iptables-legacy和iptables-nft两套后端。默认用的是nft后端,某些老内核或老模块下,它去初始化nat表时会失败。可以强制切回legacy后端试试:
update-alternatives --set iptables /usr/sbin/iptables-legacy或者直接运行iptables-legacy -t nat -L -n -v确认是不是后端问题。
这个坑在云主机、容器平台、嵌入式设备上都很常见,排查路径就是从内核模块、容器权限、后端选择这三条线依次扫。
5.2 规则莫名其妙消失
“我明明加好了规则,重启就没了”,这几乎是新手的第一大问。原因很简单:iptables规则默认只存在内核里,不落盘。它和你在编辑器里打字不一样,没有一个“保存”按钮。
所以凡是要求规则重启后还在的场景,必须做规则持久化。方法就是4.5里讲的iptables-save/iptables-restore加systemd service。另外还有一种情况要留意:有些云平台的控制台有自己的“安全组”和云防火墙,它们和操作系统里的iptables是两层体系,有时候你的iptables规则还在,但入口流量在云平台安全组就被拦掉了,表现为“规则明明放行了还不通”。这不算iptables的问题,但排查时一定把这一层也查一遍。
5.3 手滑把自己关在门外怎么救
配置防火墙最怕的就是还没放行SSH,就先把INPUT默认策略设成了DROP,然后当前会话一断,再也登不进去。真遇到这种情况,我的救援顺位是:
- 如果有物理控制台或带外管理(IPMI/iDRC/iLO),直接通过控制台登录,把规则清掉或修正。这是最稳的办法。
- 如果云主机有VNC之类的网页控制台,效果同上,可以进入系统操作。
- 如果都没有,只能依赖定时任务自救。在操作前先在另一个会话里加一个“保险”:
# 30分钟后自动清空INPUT规则,给自己留一条退路 echo "iptables -F INPUT" | at now + 30 minutes这条保险命令一定要在动手改规则之前设置好。实际操作中必须先把SSH放行规则加进去并确认连接正常,才去设置DROP默认策略,这个顺序不能乱。我自己每次改生产环境防火墙,都坚持“改一条、验证一条、备份一条”,宁可慢,不能断。
5.4 加了规则还是不通:顺序、日志、计数器三件套
规则加了、持久化也做了,但端口还是不通,这是最耗时的排查场景。我的固定排查三板斧:
第一板斧,查顺序和计数器。用iptables -L -n -v --line-numbers看规则列表,重点看每条规则命中计数器是不是有增长。如果流量根本没到你的放行规则就被前面的DROP拦了,计数器会非常直观地告诉你。比如你放行了443端口,但计数器一直是0,那就说明包压根没走到这条规则。
第二板斧,查日志。在可疑位置临时加一条LOG规则,把它放在所有规则的较前位置:
iptables -A INPUT -p tcp --dport 443 -j LOG --log-prefix "IPT-443: " --log-level 4然后在/var/log/kern.log或/var/log/messages里看有没有对应记录。LOG规则不会改变包的走向,只是记录下来,非常适合定位“包到底来没来”“被哪条规则处理了”。排查完记得把LOG规则删掉,否则生产环境日志会被刷爆。
第三板斧,抓包确认。用tcpdump在服务器内外网卡分别抓包:
tcpdump -i eth0 -nn port 443看看SYN包有没有到达网卡。如果网卡根本没收到包,问题就不在iptables,而在上层路由、安全组或者对端网络;如果收到了但应用层没反应,才是防火墙或服务本身的问题。
这三板斧按顺序走下来,绝大多数“规则不生效”的谜团都能解开。
6. iptables、firewalld和ufw怎么选
6.1 三者的真实关系
很多新人被这仨名字搞晕,其实它们的关系很清晰:
- iptables:直接操作netfilter规则的命令行工具,最底层、最直接。
- ufw:Ubuntu/Debian系推出的前端工具,把iptables规则封装成“允许某个端口”“拒绝某个IP”这类简单命令,默认规则自动生成,适合个人和小型服务器快速配置。
- firewalld:RHEL/CentOS/Rocky系默认的防火墙服务,提供命令行和图形界面,核心是区域(zone)概念,底层在较新版本上基于nftables,但也兼容iptables语法。
可以这么理解:ufw和firewalld都是“翻译官”,把人类友好的需求翻译成内核能执行的规则。iptables是它们背后真正干活的引擎之一。所以firewalld和ufw管理下的机器,底层同样能看见iptables规则,只是不希望你手工去动。
6.2 什么时候你仍然得直接碰iptables
既然有更友好的工具,为什么还要碰iptables?我的实际经验是这几个场景绕不开:
- 排障:上层工具生成的规则出了问题,最终还是要回到iptables-save、iptables -L来看底层到底发生了什么。
- NAT和端口转发:ufw对NAT的支持很蹩脚,firewalld虽然支持,但配置复杂。而只要涉及一台机器做网关、做端口映射,直接写iptables的nat表往往是最快最清晰的方案。
- Docker和容器网络:Docker会在iptables的nat表和filter表里插入自己的链,比如DOCKER链。容器端口映射异常时,看不懂这些链就无从下手。
- 老系统维护:CentOS 6、Ubuntu 14.04这类老系统,没有firewalld,ufw也未必装,iptables就是唯一选择。
6.3 给新人的选型建议
如果你是刚上手Linux单机配置,用ufw或者firewalld快速满足需求完全没问题,没必要非跟自己较劲。但只要你满足下面任何一条,就建议系统学一遍iptables:管理多台服务器的NAT路由、排查过网络问题、用Docker做过端口映射、或者手里有老系统要维护。
学的时候不用背命令,把“表、链、规则顺序、匹配即落定、默认策略”这五个概念吃透,剩下的都是查手册的事。iptables命令本身语法固定,真正的门槛是理解数据包在内核里怎么走、规则在哪个环节起作用。
再补充一句关于企业级设备的题外话:华为USG、H3C、山石、锐捷这些硬件防火墙,界面和命令虽然五花八门,但你配置NAT、安全策略、黑白名单时,本质还是在决定“哪个检查点、对哪种流量、做什么动作”。把iptables这一套概念学扎实了,切到任何品牌设备都能快速上手。
最后说说我个人的体会。iptables这东西,配置策略写得再漂亮,都不如“可回滚、可验证”重要。我的习惯是把每台服务器的规则文件纳入版本管理,改动前先diff,改动后立刻保存,再跑一遍端口连通性测试。常见的安全加固都是靠这种笨功夫堆出来的,没有哪条命令能一键解决所有问题。
还有一个小技巧:每次配置完,把iptables-save的输出和操作日期一起记下来,积累一段时间后回头看,你会发现自己对“哪些流量该放、哪些该收”的判断,比刚开始那会儿清晰得多。防火墙的本质不是禁止,而是搞清楚你到底该信任谁。