在Linux服务器上摸爬滚打这些年,要说哪个命令最让人又爱又恨,iptables绝对能排进前三。它是Linux内核netfilter框架的用户态配置工具,负责数据包过滤、NAT地址转换、端口转发、流量审计这些最底层的网络能力;平时它被各种云安全组、运维脚本藏在幕后,可真到了网络不通、服务连不上、面试官追问"Linux防火墙怎么工作"的时候,它总会被第一个拎出来。这篇文章我会从表、链、规则三个维度把它讲透,再配上四个真实场景的配置示例和一堆亲测踩坑实录,尤其是那条频繁出现在搜索引擎里的"iptables v1.8.9 can't initialize nat table"报错,我会把排查思路完完整整捋一遍。目标读者是刚开始接触Linux防火墙的新手,以及会用几个命令、但遇到异常就抓瞎的运维老手,面试前拿来抱佛脚也管用。
1. 先弄明白 iptables 到底在管什么
1.1 五张表、五条链,一次讲清楚
把一台服务器想象成一个小区,iptables就是小区入口的门禁系统。你要管理的不只是"谁可以进",还包括"进来之后换不换门牌号""包裹上要不要贴特殊标签"这些更细的事。在Linux内核里,负责这套网络包处理逻辑的框架叫netfilter,而我们平时敲的iptables命令,只是操作这个框架的用户态工具。很多人把iptables本身当成防火墙,其实不太准确,它是"给netfilter框架下指令的管理员"。
netfilter框架下分了五张表,每张表负责一类工作。最常用的是filter表,也就是纯过滤,决定数据包是放行还是丢弃;其次是nat表,做地址转换,内网多台机器共享一个公网IP上网、公网访问内网服务,全靠它;mangle表用来修改数据包的特征,比如TTL、TOS;raw表的优先级最高,用来关闭连接跟踪,主要在高并发场景下提升性能;还有一张security表,配合SELinux做强制访问控制,绝大多数人碰不到。你只要把filter和nat这两张表吃透,日常运维基本就够用了。
数据包进出服务器,会经过五条内置的链(chain)。INPUT链管"发往本机"的包,OUTPUT链管"本机发出"的包,FORWARD链管"经过本机转发、但目的地不是本机"的包。还有两条链挂在更早和更晚的位置:PREROUTING在路由决策之前处理包,POSTROUTING在包即将离开网卡之前处理包。这两条链主要配合NAT使用,后面做端口转发时会用到。
为了方便记忆,我把表和链的对应关系列成了一张表。表是"功能维度",链是"路径维度",两者交叉,不是所有链在所有表里都存在:
| 表 | 关联的内置链 | 主要职责 |
|---|---|---|
| filter | INPUT、FORWARD、OUTPUT | 数据包过滤,放行或丢弃 |
| nat | PREROUTING、INPUT、OUTPUT、POSTROUTING | 源地址转换(SNAT)、目的地址转换(DNAT) |
| mangle | 全部五条链 | 修改数据包标志、TTL、TOS |
| raw | PREROUTING、OUTPUT | 关闭连接跟踪 |
| security | INPUT、FORWARD、OUTPUT | SELinux强制访问控制 |
链路顺序这么记就行:外部新连接进来,先过PREROUTING,内核然后做路由决策——目的地是本机就进INPUT,本机处理后从OUTPUT发出;目的地不是本机、但内核开了转发,就走FORWARD直接出去。不管走哪条路,离开网卡前都会经过POSTROUTING。如果涉及NAT,DNAT改的是包的目的地址,所以放在路由决策前的PREROUTING;SNAT改的是包的源地址,所以放在离开前的POSTROUTING。这个位置关系是死规定,反过来配就完全失效。
1.2 规则的"从上到下"匹配逻辑
链不是一块铁板,它是一排规则组成的队列。数据包到了这条链,从上往下挨个比对,一旦某条规则匹配,就执行规则指定的动作(target),而且不再继续看后面剩下的规则。这就意味着iptables对规则的顺序极其敏感,"先放行特定来源的SSH,再把其他人统统拒绝"这两条规则如果不按顺序放,白名单就完全失效,因为重排序之后,数据包先撞上DROP规则,后面的ACCEPT根本轮不到。
这里有两个常见的命令误区。第一个是-I和-A的区别:-I默认把规则插到链的最前面,-A则是追加到末尾。很多人想给已有的规则表补一条白名单,随手敲了-I,结果新规则插到了第一条,把原本规划好的匹配顺序全打乱了。第二个是DROP和REJECT的区别:DROP是把包悄悄扔掉,对方等不到任何响应,只能自己超时;REJECT则是明确回一个类似"目标不可达"的错误包,对方立刻就能感知。安全防护上DROP更隐蔽,排错调试时REJECT更好用,两者各有各的场合。
顺带提一下自定义链。当规则多到几十条时,主链会变得又臭又长,你可以建一条自定义链,把某一类规则(比如"所有来自恶意IP段的处理")集中放进去,在主链上用一条-j跳转规则引过去,处理完再RETURN回来。它的作用类似门禁总闸后面分几个独立检查岗,让整体结构清晰很多。
2. 日常运维最常用的 iptables 命令
2.1 查看、添加、删除规则的正确姿势
先说查看。iptables -L -n -v --line-numbers是我每条命令里最常用的一条。-L列出规则,-n不做域名反解(不然会卡在DNS查询上),-v显示每条规则的命中计数,--line-numbers把规则从上到下编号。命中计数的价值非常大,它直接告诉你流量有没有走到这条规则的管辖范围。我排查"端口明明放行了但服务连不上"时,第一件事就是看对应规则下的计数器——如果计数是0,说明包根本没有走到这条规则,问题要么在更上游,要么链路压根不经过本机,这能迅速把排查范围缩小一大截。
iptables -S也是宝贝,它会把当前生效的规则按原始命令行格式打印出来。这个输出可以直接用来做备份、对比和回滚,比看-L的表格化输出直观得多。查看的时候加上-t nat、-t mangle就能看对应表,比如iptables -t nat -L -n。
添加规则最常用的是-A(追加)和-I(插入),用法分别长这样:
iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables -I INPUT 2 -p tcp --dport 8080 -j ACCEPT第一条把SSH放行追加到INPUT链末尾;第二条把8080端口的规则插到INPUT链的第2位。删除规则有两种方式,一种按行号删,一种按内容删,我实际中用行号删更多,因为省事且不容易误删:
iptables -D INPUT 3 iptables -D INPUT -p tcp --dport 22 -j ACCEPT-F清空规则也是高危操作。默认的iptables -F只清filter表,你要是想清nat表,必须写iptables -t nat -F。很多人图方便直接iptables -F一把梭,结果发现NAT映射没消失,其实是清错表了,顺手就把容器网络或者NAT上网服务给搞挂了。
规则里常用的匹配条件我整理成了表,新建规则时对照着看就不容易漏参数:
| 匹配条件 | 含义 | 示例 |
|---|---|---|
| -s | 源地址/源网段 | -s 10.0.0.0/8 |
| -d | 目的地址/目的网段 | -d 203.0.113.10 |
| -p | 协议(tcp/udp/icmp) | -p tcp |
| --dport | 目的端口(需配合-p) | --dport 443 |
| --sport | 源端口 | --sport 1024: |
| -i | 从哪个网卡进来 | -i eth0 |
| -o | 从哪个网卡出去 | -o eth0 |
| -m state --state | 连接状态 | -m state --state ESTABLISHED,RELATED |
2.2 保存规则,别让重启回到解放前
我接手过的服务器里,十台有八台出过"规则配好了,重启全没了"的事故。原因很简单:iptables命令改的是内核内存里的运行期规则,断电或重启后内核一重新加载,规则文件没被恢复,你辛辛苦苦敲的命令等于白敲。
Linux发行版上保存规则有各自的姿势。Debian/Ubuntu系,装上iptables-persistent后执行netfilter-persistent save,规则会写进/etc/iptables/rules.v4(IPv6是rules.v6)。Kylin/CentOS 7及以上,默认防火墙服务是firewalld,不是iptables服务;你如果希望继续用iptables管理,需要先把firewalld停掉并禁用,再安装iptables-services,然后把iptables服务enable:
systemctl stop firewalld systemctl disable firewalld yum install -y iptables-services systemctl enable iptables systemctl start iptables我个人的习惯是无论哪种发行版,都手动执行iptables-save > /etc/iptables/rules.v4做快照,再用iptables-restore < /etc/iptables/rules.v4恢复。这样规则文件在明面上,既能用git做版本管理,也能在出问题时diff对比两个版本之间到底改了什么。注意iptables-restore对语法要求很严格,手工编辑文件时千万别随手改格式。
3. 四个真实场景下的配置实战
3.1 给Web服务器只开80和443
最基础也最刚需的场景是:一台公网Web服务器,我只希望外面的人能访问80和443,其余端口的入站连接一律拒绝。先坐实默认策略,再把需要放行的例外列出来,这套配置顺序几乎是铁律:
# 1. 设置默认策略:入站和转发默认丢弃,出站默认放行 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT # 2. 回环接口必须放行,否则本机访问本机服务都会出问题 iptables -A INPUT -i lo -j ACCEPT # 3. 放行已建立连接及其相关连接,否则出方向的连接回包全被拦 iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 4. 放行 Web 服务端口 iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT新手最容易犯的错误,是把-P INPUT DROP放在最后执行。你想想,默认策略一旦改成DROP,而你还有放行SSH、放行established这些规则没来得及加,下一次SSH长连接的保活包就再也进不来,远程会话直接断掉。正确顺序是先放行回环、放行SSH(如果走远程)、放行ESTABLISHED,RELATED,最后才把默认策略改成DROP。
有些场景还需要额外考虑:想ping通服务器就加一条iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT;如果服务器跑着DHCP客户端,记得放行-p udp --dport 67:68。这些细节不配好,小问题排查起来特别闹心。
3.2 做一张"白名单优先"的访问控制表
什么叫"白名单优先"?就是先把可信来源放行,再用一条兜底DROP把其他人拦在外面。举个例子,我只允许内网10.0.0.0/8网段的机器SSH登录,其余来源想都别想:
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j DROP这两条规则的顺序绝对不能反。先加ACCEPT再追加DROP,匹配顺序天然正确;如果反着写,所有SSH连接都会先撞上DROP,白名单形同虚设。这个"放行靠前、拒绝兜底"的原则,是所有访问控制规则设计的基本盘。
黑名单场景更简单,一条iptables -A INPUT -s 203.0.113.7 -j DROP就能把某个IP拉黑。但生产环境里,病毒扫描源、攻击者IP往往动辄几百上千个,一条条写规则就不现实了。这种时候我强烈建议用ipset把IP集合管起来:
ipset create blacklist hash:ip ipset add blacklist 203.0.113.7 ipset add blacklist 203.0.113.0/24 iptables -A INPUT -m set --match-set blacklist src -j DROPipset用哈希表存IP集合,一条iptables规则引用整个集合,几万条恶意IP也不会拖慢匹配速度,比几千条规则硬叠出来性能好一个量级。需要更新名单时,只管往集合里增删,不用碰iptables规则本体。
3.3 用DNAT做端口转发
端口转发是NAT表最经典的应用。举个例子:内网Web服务器192.168.1.10:80,外网客户通过防火墙公网IP203.0.113.10的8080端口来访问,需要用DNAT把目的地址改写掉。
第一步确认内核开了IP转发,否则FORWARD链路根本走不通:
sysctl -w net.ipv4.ip_forward=1 # 持久化写入 /etc/sysctl.conf然后是三条核心配置:
# 把目标为公网IP 8080端口的包,目的地址改写为内网Web iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.10:80 # 让内网Web回包时能正确返回客户端,把源地址改成防火墙内网IP iptables -t nat -A POSTROUTING -d 192.168.1.10 -p tcp --dport 80 -j SNAT --to-source 192.168.1.1 # 放行FORWARD链路到内网Web的流量 iptables -A FORWARD -d 192.168.1.10 -p tcp --dport 80 -j ACCEPT很多人配DNAT时只记得PREROUTING那一条,回包却不改源地址,结果内网服务器回包时的源地址是客户端的公网IP,它根本没有路由返回去,连接就断了。所以POSTROUTING这条SNAT是灵魂步骤。如果防火墙出口IP不固定,干脆用MASQUERADE自动取出口网卡当前IP:
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE这里我多说一句:MASQUERADE在家庭和企业NAT上网场景里非常常见,它把内网机器的源地址统一替换成出口IP,正常网络管理中完全合规。配转发规则时始终记住一点——你改的不是单方向的包,而是整条双向连接的回程路径,少任何一半,连接都建不起来。
3.4 用limit模块给SSH加保险
暴力破解SSH口令是最常见的网络攻击之一。除了换高位端口、禁用密码登录这些常规操作,iptables自带的limit模块也能承担一部分流量压制任务:
# 只对"新建连接"限速,不限制已建立的连接 iptables -A INPUT -p tcp --dport 22 -m state --state NEW \ -m limit --limit 5/minute --limit-burst 5 -j ACCEPT # 超过限制的新建连接直接丢弃 iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j DROP--limit 5/minute表示长期平均允许每分钟5个新连接,--limit-burst 5表示初始突发容量是5个。通俗讲,桶里先装了5个令牌,前5个新连接立刻放行;之后每放行一个就消耗一个令牌,令牌按每分钟5个的速度补充。效果就是正常运维的几台机器不受影响,但攻击脚本大批量建立半开连接时会被压制住。
需要注意,limit模块只对"新连接建立速率"做限制,真正的分布式暴力破解靠它挡不住,完整方案应该是fail2ban做动态封禁、云安全组做外围过滤、SSH本身换成密钥登录。iptables的limit适合做第一道闸门,不能当唯一防线。
4. 这些坑我替你踩过了
4.1 iptables 报 nat 表初始化失败怎么排查
这个报错堪称iptables搜索词里的顶流,原文长这样:
iptables v1.8.9 (legacy): can't initialize iptables table `nat': table does not exist (do you need to insmod?)看到这个报错,思维要按顺序走三条路。第一,内核没加载nat相关模块。检查方法看/proc/net/ip_tables_names这个文件,里面如果只有filter没有nat,说明nat表没注册。试着手动加载模块:
modprobe nf_nat modprobe nf_conntrack加载完再执行iptables -t nat -L -n,多半就正常了。
第二,发行版默认用的是nftables内核API,而你的iptables命令是legacy后端。新系统里存在两套iptables实现:legacy通过传统ip_tables内核模块工作,nft通过nftables框架工作。可以用update-alternatives --config iptables查看当前指向,切换到另一套试试。很多老脚本写死了legacy用法,换了新系统后某个表就不被支持,报错就是这个。
第三,内核编译时把Netfilter的NAT支持裁剪掉了。这种情况多出现在嵌入式设备或精简内核上,软件层面再怎么折腾都白搭。拿这个报错的排查顺序总结一下:先看内核模块有没有,再看命令后端是不是对不上,最后才怀疑内核裁剪。模块问题占了大多数,别一上来就重装系统。
4.2 重启后规则全部丢失,一度以为被入侵
我帮人排查过一台服务器,头天配好了全部防火墙规则,第二天早上起来全没了。第一反应是遭贼了,看了一圈日志才发现压根没人动过,纯粹是规则没有持久化。这台机器用的是Debian,只装了iptables,没装iptables-persistent,我上次用systemctl enable iptables的习惯在他那儿根本没生效。
这事的根源在发行版演进。CentOS 6那会儿,service iptables save是标配,规则自动存到/etc/sysconfig/iptables。CentOS 7之后默认防火墙换成了firewalld,iptables服务不再是默认启用的服务了,很多从老系统过来的运维没意识到这一点,规则一写就丢。
正确的永久化方案我放在上面2.2节了,这里只提醒三个检查点:第一,iptables-save > /etc/iptables/rules.v4是否执行过;第二,系统里有没有对应的systemd服务且处于启用状态;第三,如果是容器场景,宿主机和容器各自有自己的network namespace,规则要配到对应的命名空间里才有效。
4.3 Docker 与 iptables 的相爱相杀
一台机器上同时跑Docker和自定义iptables规则,属于生产环境的高发事故区。Docker daemon启动时会直接操作iptables,在nat表加MASQUERADE规则做NAT上网,在filter表创建DOCKER链、改FORWARD链策略,还会一路加规则进去。你要是自己也维护一套iptables规则,两者很容易互相踩踏。
最常见的症状:你为了收紧安全策略,把FORWARD链默认策略改成DROP,结果Docker容器之间、容器访问外网全部瘫痪;或者你执行了iptables -F清空规则,Docker的端口映射全失效,重启Docker后才恢复。
如果你的机器上跑着Docker,自定义防火墙规则有两条基本原则。第一,不要随意清空FORWARD整条链,尤其别碰名字里带DOCKER的链。第二,新版Docker专门给你留了DOCKER-USER链,这是"用户自定义规则"的官方位置,Docker自己不会动这条链的规则,你写在这儿的限制规则会优先生效。看上去就像:系统管理员进小区门禁后,在Docker家的门前又加了一道自己管辖的检查岗,两边职责互不干扰。
4.4 误封自己的SSH怎么自救
配防火墙把自己锁在门外的经历,我相信不少人都有过。真遇到这种事,先深呼吸,然后按优先级自救。
第一优先级是云控制台。大多数云厂商都提供VNC登录或带外管理,相当于跑到服务器屏幕前操作,登录后执行iptables -F清空规则即可。没有云控制台也没关系,用"定时炸弹"法:提前在本地计划任务里放一条命令,等两分钟后自动恢复,给自己留出足够操作窗口:
echo 'iptables -F' | at now + 2 minutes或者写一个后台脚本,sleep 60秒后清空规则:
#!/usr/bin/env bash sleep 60 iptables -F把它放到后台运行,然后不慌不忙地调试你真正想加的规则。但最重要的还是预防。我的习惯是每次改规则前先备份:
iptables-save > /backup/iptables.$(date +%F_%H%M).save改完立刻开第二个SSH窗口做连接测试,确认无问题后再继续改下一条。一次只改一条,拒绝"一口气贴十行"这类操作。这套习惯养成后,被锁在门外的概率会降到极低。
5. 生产环境里的几条个人经验
5.1 把规则当代码管,别当临时命令
iptables规则本质上是一份可版本化的配置文件,但太多人把它当成"敲一次就完事"的临时命令,导致出了问题无法回溯,无法对比,也无法快速还原。我的做法是:所有规则收敛到一个脚本文件里,每次改动都用git留下记录。
脚本大致长这样:
#!/usr/bin/env bash # 统一应用本机iptables规则,仅演示,按环境调整网段和端口 set -e # 1. 清空现有规则 iptables -F iptables -X iptables -t nat -F iptables -t nat -X # 2. 设置默认策略 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT # 3. 回环和已建立连接 iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 4. 管理网段SSH白名单 iptables -A INPUT -p tcp --dport 22 -s 10.20.0.0/16 -j ACCEPT # 5. Web服务 iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT # 6. 其他未放行的请求统一拒绝,便于第一时间发现异常 iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited比脚本更推荐的是用iptables-restore。把规则整理成纯文本:
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -i lo -j ACCEPT -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -A INPUT -p tcp --dport 22 -s 10.20.0.0/16 -j ACCEPT -A INPUT -p tcp --dport 80 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited COMMIT然后iptables-restore < rules.v4一次性加载。好处是格式整齐、支持注释、diff起来直观,出问题时一行命令就能回滚到上一个版本。
5.2 记录日志和审计思路
防火墙不能只会"挡",还得会"记录"。iptables的LOG target能把命中的包写进内核日志,适合拿来做攻击行为审计。典型组合是先LOG再DROP:
iptables -A INPUT -s 203.0.113.0/24 -j LOG --log-prefix "IPT-BLOCK " --log-level 4 iptables -A INPUT -s 203.0.113.0/24 -j DROP第一条LOG把来源IP、端口、协议记下来,第二条才真正丢包。顺序反了LOG就永远不触发,因为包已经被丢了。日志默认送进syslog,量大时可以单独配置输出文件,比如在rsyslog里按syslog facility和level过滤出来写到/var/log/iptables.log。
这里有个坑:LOG规则不能不加限制地乱挂。攻击扫描的包成千上万,每命中一次就写一行日志,磁盘容量会被日志瞬间淹没。生产环境给LOG本身也加上限速是个好习惯:
iptables -A INPUT -m limit --limit 10/minute --limit-burst 20 -j LOG --log-prefix "IPT-BLOCK "还有一种更稳妥的审计思路:先在疑似的恶意IP上放LOG但不DROP,观察几天流量规律,确认无误后再改成DROP。这样既不误伤,又能留证据。排查时直接grep "IPT-BLOCK" /var/log/iptables.log | awk '{print $3}' | sort | uniq -c | sort -nr看哪个IP被拦得最凶,思路一下清晰了。
5.3 防火墙到底能不能关
"防火墙关闭有影响吗"这个问题,年度搜索量相当可观。会问这个的人,大多遇到了软件连不上、服务无法访问、开发工具报错的场景,想通过关防火墙一了百了。我的观点很直接:能不开的别乱开,能不全关的别全关。
开发调试阶段,临时把防火墙关了确实能加速定位问题,但记得调试完恢复。生产环境关防火墙裸奔,等于把家门钥匙挂在门外,迟早出事。很多"关了防火墙就能连上"的问题,本质是端口或者进程没放行,而不是防火墙本身没价值。你放行具体端口、来源IP段,效果和关防火墙一样,风险却小得多。
遇到服务连不上,我的排查顺序固定是:服务本身有没有监听、监听在哪个地址、进程有没有绑定端口、防火墙规则有没有命中、是不是路由或安全组拦截。一上来就iptables -F是最懒也最危险的做法。同样,在Windows上装开发工具被提示关防火墙时,正确做法是给对应进程或端口加出入站白名单,而不是整个把防火墙关掉。
跟iptables打了这些年交道,我最深的体会是:它不复杂,但非常讲究秩序。规则从上到下生效,改之前先看现状,动之前先备份现状,遇到报错先看内核模块再看命令后端。把它当成代码来管,当成生产环境的基础设施来敬畏,它就能成为你手里最顺手的那把刀,而不是时不时跳出来咬你一口的坑。希望这篇能帮你少踩几个我踩过的坑。