iptables 规则修改与持久化保存:从命令到生产环境实践
改 iptables 规则最怕的不是命令记不住而是改完当时生效、重启全没。我接手过不少线上机器排查事故时发现配置的人当时敲完iptables -A INPUT -s x.x.x.x -j DROP就走了结果机房一断电规则全部蒸发该封的没封该放行的也没放行。所以这篇我就把修改规则和保存规则这两个环节彻底展开聊聊从查看规则到动手修改再到持久化保存、导入恢复和生产环境里的操作习惯一条线讲完。写这篇文章主要是给那些已经会iptables -L但还没把改规则和存规则串起来的人不涉及太深的 netfilter 内核原理但会把每个命令背后的行为逻辑讲明白。1. 修改规则前先把当前规则集当作战场地图看透很多人上来就iptables -A这不是不对而是容易在不知不觉中违反自己的安全基线。改规则之前你至少要搞清楚三件事现在有哪些表、哪些链链上已经排了哪些规则以及规则的匹配顺序是怎么决定的。这三件事搞清楚后面不管是加、插、替换还是删除都不容易出错。1.1 用对查看命令-L -n -v --line-numbers 一套带走iptables -L是大家最熟的但裸敲iptables -L输出里会把 IP 反解成域名碰上内网解析慢的时候等半天不出结果。而且不带行号的话你没法精确引用某一条规则因为后续-I、-D、-R都要基于规则编号操作。我平时固定使用这一串iptables -L -n -v --line-numbers iptables -t nat -L -n -v --line-numbers iptables -t mangle -L -n -v --line-numbers对应每一项参数-n不做 DNS 反解纯 IP 显示快且准确。-v显示 pkts 和 bytes 计数能看出来哪条规则真正被流量命中过。--line-numbers给每条规则编行号后面-I 3、-D 2、-R 1全都依赖这个行号。-t指定表。默认只查 filter 表但 NAT 规则、mangle 规则经常才是问题源头。光看-L还不够我还建议用iptables -S看规则集的另一种视角。-S输出的是一堆可以直接拿去iptables-restore的规则语句相当于把当前规则集以命令形式导出来。对比着看能避免名字遮蔽造成误导——-L在某些情况下会把 ICMP 类型显示成名称-S则输出原始参数。iptables -S iptables -t nat -S1.2 规则匹配顺序从上往下先到先得iptables 的每条链本质是一个规则列表数据包进到链里就从头开始逐条匹配。匹配到一条规则且该规则的动作是 ACCEPT/DROP/REJECT 时基本就停止继续往下走了。所以规则的顺序天然决定策略的权重。一个经典例子INPUT 链里如果你先把-j DROP放在最上面那下面写的所有 ACCEPT 全部白搭。我见过有人为了封一个 IP执行了iptables -A INPUT -s 1.2.3.4 -j DROP结果服务器直接连不上因为这台机器可能原本就有一条靠前的规则放行了所有流量这条新加的规则排在后面永远轮不到或者反过来排到了所有白名单规则之前把正常流量也一并杀掉。无论哪种本质都是没有预估顺序。所以改规则之前先把--line-numbers的输出拿来画个草图哪几条是白名单哪几条是默认策略我要动的位置在整条链的哪个位置。这一步花不了两分钟但能帮你躲掉很多刚加完规则就把自己锁外面的坑。1.3 分清默认策略和具体规则链的 policy 是链的兜底行为比如iptables -P INPUT DROP表示 INPUT 链没有规则命中时一律丢弃。这个默认策略单独存在不在规则编号里但你改规则时必须心里有数。判断一条规则是否生效要结合 policy 一起看如果链默认策略是 ACCEPT那么就算你没有放行某端口流量也能进只是有没有后续规则匹配的问题。如果链默认策略是 DROP即使你没写拒绝规则未放行端口一样进不来。改规则之前建议先看一眼默认策略iptables -L INPUT | head -1pkts列下面那一行policy ACCEPT或policy DROP就写着结果。很多新手误以为只要自己有 ACCEPT 规则就万事大吉结果被人改了默认策略后完全没察觉等到排查外联异常时才发现 INPUT 链的 policy 不知道什么时候变成了 DROP。改动前把 policy 记下来是基本素养。2. 修改规则不是只有加一条追加、插入、替换、删除各自的适用场景iptables 修改规则的操作动词看着多其实就四类追加、插入、替换、删除。每一类都有明确使用场景用错了虽然不报错但规则行为会和你预期差很远。2.1 -A 追加别把追加当成默认-A规则追加到链的末尾。如果链里已有规则很多追加往往意味着最后再兜底。但对于某些安全策略追加容易出问题如果你要封一个 IP并且链里前面有 ACCEPT 放行了全部流量追加的 DROP 不会生效因为数据包在前面已经被接受了。如果你要把某个新服务端口开出来追加的 ACCEPT 如果排在了尾部 DROP 之后同样白搭。所以我的习惯是凡是新增规则先iptables -L -n --line-numbers看尾巴再决定用-I指定行号还是用-A追加。不要无脑-A。2.2 -I 插入把规则放到指定位置当规则必须插队时用-I。典型场景是把一条封禁规则插到白名单之后、放行规则之前让流量先走白名单没匹配到再被拦下来。# 插入到 INPUT 链第 3 行 iptables -I INPUT 3 -s 10.0.0.8 -j DROP如果不写行号-I INPUT默认插到第 1 行。这一点也常被人忽略有时你只是想加在末尾输错成-I结果规则瞬间成为链首直接影响之前所有流量的走向。插入前建议先把行号感建立起来先-L --line-numbers看当前行数比如要插在现有第3行和第4行之间用iptables -I INPUT 4因为插入动作会把后面规则整体下移一位此时第4行就是新规则的位置。2.3 -D 删除按编号或按完整规则两种删法都得会删除规则有两种写法分别应对不同场景。第一种按编号删除适合你已经知道要删哪一条iptables -D INPUT 3第二种按完整规则删除适合你不确定编号但能写出完整匹配条件的情况iptables -D INPUT -s 10.0.0.8 -j DROP第二种方式要求参数必须和原规则完全一致包括协议、端口、源地址、目标等。少写一个-p tcp --dport都可能导致删除失败但注意iptables 对顺序不敏感-s和-j哪个写前面都可以匹配条件一致就行。如果删除时报Bad rule基本就是参数对不上。我的经验是先iptables -S把原始规则原样查出来复制粘贴到-D里去掉-A那一段可靠得多。2.4 -R 替换改单条规则参数最省事-R用于替换某一行规则不新增也不删除只换内容。比如原第3行是把 10.0.0.8 丢弃现在想改成 10.0.0.9直接iptables -R INPUT 3 -s 10.0.0.9 -j DROP替换有个容易忽略的坑-R需要你提供完整的新规则不是只写要改的那个参数因为 iptables 不会自动继承原规则的其余部分。比如原规则是-s 1.2.3.4 -p tcp --dport 22 -j DROP你只想改源IP结果写成iptables -R INPUT 3 -s 5.6.7.8 -j DROP新规则就变成了丢弃来自 5.6.7.8 的所有协议所有端口流量丢掉了 tcp/22 的限制。这种静默变化很危险经常成为线上故障的隐患。替换前把原规则完整摘出来改完再作为新规则整体放回去。2.5 -F、-X清空链和删除自定义链要格外小心-F是清空链里所有规则-X是删除自定义链。这两个虽然不算修改单条规则但事故率极高。我在操作这两个命令前一定会再三确认iptables -F INPUT只清空 INPUT 链问题不大但有人习惯性iptables -F不带参数那就把 filter 表的所有链全清空了默认策略还在但结果往往是 SSH 直接断掉如果默认策略是 DROP或者安全防线全无如果默认策略是 ACCEPT。-X删除自定义链之前链必须为空否则报错。所以顺序是-F 自定义链名再-X 自定义链名。注意-F和-X都不会修改默认策略但对一个设置了默认 DROP 的机器执行无差别清空等于把自己和外界隔离开来行为上等同于原地断网。3. 保存规则的三种路子以及不同发行版下的实际差异理解修改命令之后最主要的坑就是规则不持久。iptables 规则的存储位置在内核内存相当于汽车里的临时设置重启引擎就恢复出厂。所以保存规则就是把当前运行时规则导出成文件再在开机阶段重新灌入。3.1 为什么需要保存规则默认只是临时的内存状态iptables 规则实际作用在 Linux 内核的 netfilter 框架中这些规则修改后只驻留在内存里。一旦机器重启或者iptables -F清空内存规则就没了。这不是配置错误而是 iptables 本身的设计定位它只是一个用户态管理工具负责和内核交互不负责持久化。所以当你执行iptables -A INPUT -p tcp --dport 80 -j ACCEPT后你只是给当前内核加了条规则如果你想 60 秒后机器重启依然有这规则就必须手动做持久化。3.2 方案一发行版自带的服务式保存老牌 CentOS 6 / RHEL 系CentOS 6 那一代系统里安装 iptables 后自带/etc/sysconfig/iptables文件和service iptables save命令。改完规则后执行service iptables save它会执行iptables-save并把输出写到/etc/sysconfig/iptables启动时再由/etc/init.d/iptables读取恢复。但 CentOS 7 开始默认用 firewalld自带的 iptables service 不一定有即便装上了 iptables-services 包也要手动启动这个至下而上的恢复链。这里有一个很多老运维踩过的坑他们习惯service iptables save系统却没装 iptables-services于是命令直接提示unrecognized service规则自然保存不了。3.3 方案二iptables-save / iptables-restore 手工导入导出通用路子最通用、最不会出错的保存方式是把运行时规则导出成规则文件然后配置开机自动恢复。导出iptables-save /etc/iptables/rules.v4如果有 IPv6额外导出ip6tables-save /etc/iptables/rules.v6恢复iptables-restore /etc/iptables/rules.v4这种方式的优点是规则文件是纯文本可读、可 diff、可放入版本管理。我通常建议把规则文件放在/etc/iptables/目录下权限严格控制为 root 可读写chmod 600 /etc/iptables/rules.v43.4 方案三iptables-persistent / netfilter-persistentDebian/Ubuntu 系Debian/Ubuntu 下有个工具包叫iptables-persistent安装时它会让你选择是否把当前 IPv4/IPv6 规则保存下来。装好之后规则文件存放在/etc/iptables/rules.v4和/etc/iptables/rules.v6。它的使用逻辑是netfilter-persistent save netfilter-persistent reload其实底层调用的还是iptables-save和iptables-restore。你可以直接手动修改/etc/iptables/rules.v4里的文本然后netfilter-persistent reload方便批量编辑规则文件而不是一条条敲命令。3.5 更可控的做法写一个 systemd service 保证开机恢复如果发行版既没有 iptables-services也不想装 iptables-persistent完全可以自己写一个 systemd unit 来做规则恢复。这个方式最灵活尤其在容器或特殊裁剪系统上非常实用。新建/etc/systemd/system/iptables-restore.service[Unit] DescriptionRestore iptables rules Beforenetwork-pre.target Wantsnetwork-pre.target [Service] Typeoneshot ExecStart/usr/sbin/iptables-restore /etc/iptables/rules.v4 ExecStart/usr/sbin/ip6tables-restore /etc/iptables/rules.v6 RemainAfterExityes [Install] WantedBymulti-user.target然后systemctl daemon-reload systemctl enable iptables-restore.service systemctl start iptables-restore.serviceBeforenetwork-pre.target是为了在网卡正式启用前先恢复规则避免出现网卡起来了但规则还没恢复的空窗期。这个时序细节很关键很多自定义的恢复脚本没注意结果每次开机后的一小段时间机器是裸奔状态。4. 规则文件的导入恢复以及改完规则后的验证手段保存规则不是终点能把规则文件正确地导回运行环境才完整。这一节主要说iptables-restore的导入细节以及怎么验证规则真的生效了。4.1 iptables-restore 的格式与行为iptables-restore接受的输入格式就是iptables-save导出的那种文本以*filter开表、以COMMIT结束链之间用:链名 策略 [计数器]声明规则行以-A开头。一个典型片段*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [123:4567] -A INPUT -i lo -j ACCEPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -A INPUT -p tcp --dport 22 -j ACCEPT COMMIT直接执行iptables-restore /etc/iptables/rules.v4注意iptables-restore默认会清空当前表的规则再灌入文件内容相当于整体覆盖不是逐条追加。如果你只想在现有规则基础上追加需要加-n参数iptables-restore -n。我在生产环境恢复规则时一般不加-n因为覆盖能保证规则和文件完全一致避免留下运行环境里残留的未知规则。但你得明白这个行为区别否则可能会误以为iptables-restore是追加。4.2 导入前用 --test 做语法检查iptables-restore 没有像 nginx -t 那样广为人知的测试选项但新版本通常支持--testiptables-restore --test /etc/iptables/rules.v4它只做解析检查不真正修改规则集。如果有语法错误会输出具体的行号和原因不会破坏现有运行状态。这个命令应该成为你执行恢复前的固定动作。如果没有--test支持至少可以人工先iptables-restore 文件到一个测试容器或非生产机上跑一遍。生产机直接吃语法错误的文件最坏情况是规则全部加载失败当前网络策略直接失效等于裸奔。4.3 修改规则后如何验证真的生效了很多人改完规则后用iptables -L看一眼规则在就以为完事了但规则在和流量符合预期是两回事。验证手段可以分几步验证放行从另一台机器用nc或curl测试端口连通性。比如放行了 8080就curl -v telnet://目标IP:8080注意观察是否真的建立了连接。验证拒绝/丢弃从非放行的源测试。不要在自己本机测试因为本机流量会走 lo 接口很多规则对 lo 放行容易产生明明封了却还能通的假象。看计数器用iptables -L -n -v看规则命中的 pkts 是否增长。如果某条规则 pkts 一直是 0大概率是它前面的规则先把流量处理掉了或者你测试的源地址不对。确认模块加载规则里带-m conntrack、-m recent、-m limit等条件时检查内核模块是否已加载否则规则会报错或完全不匹配。用lsmod | grep 模块名查看必要时modprobe 模块名。4.4 规则文件被清空或错误修改后怎么恢复假设你不小心执行了iptables -F内存规则没了但规则文件还在恢复很直接iptables-restore /etc/iptables/rules.v4如果你的规则文件本身也被覆盖或删除了那就只能靠备份了。这也就是我反复强调备份的原因规则文件是纯文本体积小你完全可以在每次确定变更前先备份成带日期的文件。cp /etc/iptables/rules.v4 /etc/iptables/rules.v4.bak.20250101一旦发现改坏了直接恢复到最近的备份成本几乎为零。5. 生产环境里改规则的稳妥流程备份、演练、回滚一次讲清前面的内容解决的是命令怎么用和保存怎么搞但实际线上操作比命令更关键的是操作流程。这一节把我的习惯操作流程完整拆开算是多年踩坑换来的经验。5.1 永远先备份再动手不管你在规则文件里改还是用命令行直接改第一步都建议备份当前状态。命令行环境下备份就是iptables-save /root/iptables-backup-$(date %F-%H%M%S).rules这个文件同时具备审计和回滚价值。真要出了问题iptables-restore 备份文件就能回到改动前。我见过不少同事图省事跳过这步结果删错一条规则全网服务异常只能靠记忆手搓回原规则耗时又危险。如果是直接改规则文件那副本就是最简单的备份形式把rules.v4复制一份带时间戳的文件即可。5.2 大改动先写在文件里再整体 restore不要敲一行执行一行生产环境里的复杂变更比如新加一段访问控制列表、调整某条链的顺序我强烈建议先在本地文本编辑器里改好规则文件确认无误后再在机器上执行恢复。这样做的理由规则集可以整体审阅而不是陷在一行行命令里难以总览。文件可以做 diff看清楚每一条变化。恢复失败时有明确的报错来源方便定位是格式问题还是模块缺失。比如你打算给 INPUT 链加一段IP封禁列表直接在规则文件里加几行-A INPUT -s 192.0.2.10 -j DROP -A INPUT -s 198.51.100.0/24 -j DROP然后iptables-restore --test /etc/iptables/rules.v4 iptables-restore /etc/iptables/rules.v4改完再netfilter-persistent save或iptables-save /etc/iptables/rules.v4保持文件和运行态一致。5.3 改防火墙规则期间永远给自己留一条后门没有人能保证规则改完一定没问题。所以生产机操作时最忌讳的就是清零式改动而又没有带外管理通道。我的建议尽量通过本机终端或带外管理口操作防火墙规则而不是只开着一个远程 SSH。因为一旦规则导致连接中断带外管理是你唯一的救命通道。如果只能远程操作那么新加的规则最好先sleep延迟生效或者借助 at 任务在几分钟后自动清除规则避免把自己锁死。下面这个脚本思路很值得参考(sleep 300 iptables -D INPUT -s 你自己IP -j DROP) 这个子 shell 会在 5 分钟后自动删除刚加进去的封禁规则。你可以先加上一条放行自己IP的规则、再把自己封掉验证不通过也能自动恢复。等确认无误后再去取消这个定时任务。5.4 一个完整例子批量加封禁 IP 并持久化结合前面所有内容我给出一个完整的流程示例。需求禁止来自203.0.113.0/24网段的流量访问本机并且重启后依然生效。第一步备份当前规则iptables-save /root/iptables-before-block.rules第二步在规则文件里增加两条规则并放在合适位置。如果原来 INPUT 链已经有-P INPUT DROP且尾部有全放行规则你必须放在全放行规则之前。我给一个示例片段*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -i lo -j ACCEPT -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT -A INPUT -s 203.0.113.0/24 -j DROP -A INPUT -p tcp --dport 22 -j ACCEPT COMMIT第三步测试并应用iptables-restore --test /etc/iptables/rules.v4 iptables-restore /etc/iptables/rules.v4第四步验证封禁是否生效从该网段的一台主机测试ping -c 3 目标IP如果通则用iptables -L -n -v | grep 203.0.113.0看该规则命中计数是否增长确认规则顺序是否堵截正确。第五步持久化保存让重启后依然生效netfilter-persistent save # 或 iptables-save /etc/iptables/rules.v4最后再把规则文件和备份比对一次确认没有多余改动。5.5 习惯上值得养成的几个小细节修改规则后立刻保存不要让运行态和文件态长期不一致否则下次重启会神秘回滚。规则文件里不要写死绝对依赖机器的临时 IP除非你就是想做临时封禁。对规则文件启用版本管理git是非常值得的每次改动都有记录出事故能快速看到 diff。清理规则优先用-D删单条少用-F清全链除非你明确知道自己在做什么。6. 规则改了、也保存了重启后为啥还是不生效最后单独用一个章节讲一个高频问题明明保存了规则也确认文件里有内容重启后 iptables 还是空荡荡。这类问题的原因通常不在 iptables 本身而在启动链路上的某个环节断掉了。6.1 文件路径不对服务不知道去哪加载iptables-persistent 默认读/etc/iptables/rules.v4CentOS 的 iptables-services 默认读/etc/sysconfig/iptables。如果你把规则文件存错了路径服务自然找不到启动时静默跳过重启后规则就没了。排查方式很直接看服务状态和启动日志。systemctl status netfilter-persistent journalctl -u netfilter-persistent如果是自己写 systemd unit确认ExecStart里的路径是否真实存在、有没有权限问题。我遇到过把规则文件放在 root 家目录、unit 里用相对路径的开机恢复失败得非常隐蔽。6.2 systemd 服务执行顺序引发先有网络后有条规则有些自定义 unit 没加Beforenetwork-pre.target导致网卡先起、规则后恢复甚至服务和规则恢复并行执行。在网卡已经拿到地址、服务已经开始监听的同时防火墙规则还没就位这中间就有一小段裸奔窗口。要缩小这个窗口必须显式声明依赖Beforenetwork-pre.target和Wantsnetwork-pre.target并执行systemctl enable创建符号链接确保 unit 被开机加载。6.3 内核模块依赖缺失导致某些规则加载失败如果规则文件里有-m conntrack或-m recent等匹配模块但系统里对应内核模块没有自动加载iptables-restore会报Couldnt load match一类的错误。服务可能显示 failed但如果你把 unit 配置成了不检查退出状态它可能表面上继续跑规则却没恢复。解决方式有两种一是在规则文件顶部用modprobe预加载二是给 systemd unit 加ExecStartPre/usr/sbin/modprobe xt_recent之类的预加载命令。具体模块按需加载不能一股脑全加载否则会扩大内核攻击面。6.4 容器场景里的特殊坑在 Docker 容器里执行 iptables 修改规则通常需要容器具备NET_ADMIN权限而且很多容器镜像没有安装 iptables 用户态工具。就算装了默认的 network namespace 也不一定和宿主机一致。所以在容器里改完没生效很可能不是持久化的问题而是你的规则作用在了错误的网络命名空间里。这类场景建议尽量用容器编排层的能力去管理网络策略真要操作 iptables务必确认iptables -L看到的链和宿主机是否一致避免误伤。6.5 给自定义恢复服务加上可观测性自己写的 systemd unit 最好加一个 ExecStart 后的状态确认比如恢复完成后自动对比规则条数写入系统日志。这样重启后你能很快判断恢复有没有成功。ExecStart/usr/sbin/iptables-restore /etc/iptables/rules.v4 ExecStartPost/bin/bash -c iptables -L -n | wc -l /var/log/iptables-count.log虽说不至于把每条规则都打日志但至少留一个规则条数的可观测点排查时能快速判断到底是没恢复还是恢复的数量不对。改 iptables 这件事很多时候出问题不是因为你不知道某个参数而是因为你没把当前状态、改动目标、持久化方式这三件事连成一条线。我个人实际操作中的体会是不管多熟练每次改动前备份一份规则文件、改动后立刻验证并保存这两步一次都不能省。尤其是远程操作时先想想自己会不会被新规则挡在门外必要时留个定时自动回滚的保底任务。这套流程走顺了iptables 就从高危操作变成了日常可维护的普通配置项。如果你生产环境里还没给规则文件做版本管理我建议尽快补上下次排查规则异常时你会感谢这个决定的。