
1. 先把环境搞清楚CentOS 7 的防火墙体系到底怎么回事1.1 firewalld 和 iptables 的关系别傻傻分不清CentOS 7 上搞 iptables很多朋友都踩过同一个坑规则明明敲进去了端口也通了结果服务器一重启一切回到解放前。这不是你操作有问题而是 CentOS 7 默认的防火墙体系和我们熟悉的 iptables 服务模式之间存在一个很容易被忽略的差异。CentOS 7 默认安装的是 firewalld它虽然底层还是基于内核的 netfilter 框架但管理方式和传统的 iptables 服务完全不同。firewalld 用 firewall-cmd 这一套命令来管理规则规则库放在 /etc/firewalld/ 目录下有自己的 zone 概念和运行时配置与永久配置的区分。而传统 iptables 服务规则保存在 /etc/sysconfig/iptables 文件里通过 iptables 命令直接操作内核的 filter、nat、mangle 等表。我之前遇到很多朋友在 CentOS 7 上直接敲 iptables -A INPUT -p tcp --dport 80 -j ACCEPT当时确实生效但心里默认重启就没了这其实是好事说明规则确实没有持久化机制在帮你自动保存。要让它开机永久生效核心思路就两条要么把规则交给一个开机自启的服务去加载要么让系统在启动流程里主动执行写入规则的脚本。下面我会把这两种方式都讲透。1.2 为什么你敲的命令重启后就消失了这里要先理解 iptables 命令的本质。你执行的每一条 iptables 规则都是直接写入内核内存里的 netfilter 规则表中它不写任何磁盘文件。重启后内核重新加载规则表被清空自然一切归零。这和 firewalld 的 firewall-cmd --permanent 机制完全是两码事firewalld 会把规则写进配置文件所以它能持久化。想验证这个现象很简单你随便加一条规则后去看 /etc/sysconfig/iptables 这个文件如果文件不存在或者里面没有你刚加的那条规则那就说明你的操作没有落盘。这就是重启即失效的根本原因。所以要让规则永久生效本质上就是把内存里的规则导出到文件并确保开机时能加载回来仅此而已。注意如果你当前的系统正在用 firewalld 管理网络直接去折腾 iptables 服务两者会互相干扰。我的建议是先停用 firewalld再切到 iptables 服务体系这样规则的管理权是唯一的排查问题也干净。2. 准备工作停掉 firewalld安装 iptables-services2.1 实操前的三条命令先把地基打好不管你最终选择哪种永久生效方式第一步基本都是统一的把系统默认的 firewalld 关闭并禁用开机自启然后安装 iptables-services 这个服务包。它提供了 /etc/init.d/iptables 脚本和 iptables-save、iptables-restore 等关键工具是方式一的核心依赖。systemctl stop firewalld systemctl disable firewalld systemctl mask firewalld第三行的 mask 可能有些人没见过它的意思是彻底屏蔽这个服务比 disable 更彻底防止有其他服务把它拉起来。如果你确认自己不再需要 firewalld建议加上。接着安装并启用 iptables 服务yum install -y iptables-services systemctl enable iptables systemctl start iptables启动后系统会自动读取 /etc/sysconfig/iptables 文件。如果这个文件不存在服务会使用默认的空规则集启动。这一步做完你的 iptables 服务就已经在 systemd 的托管之下了后面只要把规则保存进配置文件重启后服务会自动加载。2.2 快速过一遍常用命令后面都用得到既然要改规则最基本的命令得顺手。查看规则用 -L插入规则用 -I追加规则用 -A删除规则用 -D指定表用 -t指定链用 -p 配合链名。我一般习惯加 -n 让地址不做反向解析输出速度快很多iptables -L -n -v iptables -t nat -L -n iptables -I INPUT 3 -p tcp --dport 8080 -j ACCEPT iptables -D INPUT -p tcp --dport 8080 -j ACCEPT这里多说一句-I INPUT 3 表示把规则插入到 INPUT 链的第 3 条位置而不是追加到末尾。规则的顺序直接决定匹配结果这个在后面常见问题部分我会重点讲。另外改规则前建议先看一遍当前规则确认服务的出厂规则长什么样避免误操作把自己 SSH 断掉。3. 方式一iptables-services iptables-save最正统的做法3.1 保存规则的核心操作一行命令落盘要说最标准、最推荐的方式就是利用 iptables-services 提供的保存机制。你把规则在内存里调好后执行下面任意一条命令规则就会写入 /etc/sysconfig/iptablesiptables-save /etc/sysconfig/iptables或者更省事的写法service iptables save这两条命令的效果是等价的service iptables save 内部其实就是调用 iptables-save 重定向到配置文件。我个人的习惯是直接用 iptables-save因为它的含义更直白而且可以按需指定要导出的表。比如你只改了 nat 表想单独看它可以执行iptables-save -t nat这里有个细节要注意iptables-save 不带 -t 参数时会把 filter、nat、mangle 等所有表全部导出。所以执行重定向保存时配置文件里会包含所有表的规则这其实是好事意味着你再也不用担心漏掉某个表的规则。3.2 配置文件长什么样看懂格式才能不慌保存之后你可以用 cat 看一下 /etc/sysconfig/iptables 的内容。它的格式是 iptables-restore 能直接识别的语法典型的文件长这样# Generated by iptables-save v1.4.21 on Fri Jan 1 00:00:00 2021 *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT -A INPUT -i lo -j ACCEPT COMMIT # Completed on Fri Jan 1 00:00:00 2021注意几个关键点。*filter 表示开始一个表冒号开头的三行定义了链的默认策略-A 开头的是实际规则COMMIT 表示提交这个表的配置。如果文件里有多个表就会依次出现 *nat、COMMIT 这样的段落。文件里的规则顺序就是开机时加载的顺序和你在命令行敲 iptables-save 时看到的输出顺序一致。所以当你需要手工微调某条规则时可以直接编辑这个文件然后用 iptables-restore 让它即时生效iptables-restore /etc/sysconfig/iptables这个命令会先清空当前所有规则再按文件内容重建规则集。效果等同于重启 iptables 服务systemctl restart iptables。两者内部逻辑几乎一样我一般用 systemctl restart iptables因为还能顺便看到服务的启停日志。3.3 完整跑一遍从加规则到验证开机生效现在我把完整流程串一遍你照着操作就行。假设你的服务器需要开放 22、80、443 三个端口同时允许已建立的连接回包回环接口放行其余外部访问全部拒绝。依次执行iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT iptables -P INPUT DROP最后一条设置 INPUT 链默认策略为 DROP意思是不匹配上面任何一条规则的外部流量全部丢弃。这里有个顺序问题我先放行了 22 端口再设默认 DROP如果反过来把默认策略先改成 DROP一旦你当前 SSH 连接不在白名单规则范围内瞬间就会断连。所以血的教训是先加放行规则再改默认策略。规则加好后先验证一下当前状态iptables -L -n确认无误后保存iptables-save /etc/sysconfig/iptables然后模拟重启验证systemctl restart iptables iptables -L -n重启服务后规则还在说明文件加载正常。为了保险还可以直接 reboot 一次系统起来后再次查看规则。能看到刚才配置的 22、80、443 放行规则以及默认 DROP 策略就说明方式一已经大功告成了。提示iptables-services 服务在启动时会执行 iptables-restore /etc/sysconfig/iptables所以你只要保证这个文件内容正确服务开机自启规则就一定能恢复。这也是为什么我推荐优先用方式一因为 systemd 会替你管理好启动时序和异常状态。4. 方式二rc.local 脚本开机执行灵活但需要自己兜底4.1 把规则写进开机脚本原理就是这么朴素第二种方式的思路更直接既然开机后规则会被清空那我就在开机流程的最后阶段用脚本把规则重新敲一遍。这个脚本就是 /etc/rc.d/rc.local。在 CentOS 7 上它默认是存在的但有个坑systemd 时代rc.local 本身不是一个被默认启用的服务需要你手动授权它才能执行。先看一下当前 rc.local 的状态cat /etc/rc.d/rc.local ls -l /etc/rc.d/rc.local正常情况下文件内容就是一个 shebang 加注释。你需要做的第一件事是给它加上可执行权限并启用 rc-local 服务chmod x /etc/rc.d/rc.local systemctl enable rc-local systemctl start rc-local然后编辑 /etc/rc.d/rc.local把规则一条一条写进去。它的执行时机在系统网络就绪之后所以你不用担心网卡还没起来导致规则加不上的问题。示例内容如下#!/bin/bash # 开机自动加载 iptables 规则 iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT iptables -P INPUT DROP保存退出后重启验证即可。这里我需要特别说明一个容易出问题的细节rc.local 里的每一行命令都在系统启动的早期阶段执行如果哪一条写错了系统不会像服务那样给你报个明显的错误只会静默失败。所以脚本里建议加日志输出方便排查#!/bin/bash echo $(date %Y-%m-%d %H:%M:%S) iptables rules loading... /var/log/rc-local.log iptables -A INPUT -i lo -j ACCEPT ... iptables -P INPUT DROP echo $(date %Y-%m-%d %H:%M:%S) iptables rules loaded. /var/log/rc-local.log4.2 方式二适合哪些场景别用它硬扛复杂规则说实话方式二更适合规则较少、逻辑简单的场景。比如你只需要固定放行几个端口脚本里写上七八行就能搞定。它的优点是直观脚本本身就是规则文档想改哪条直接编辑文件不用关心 iptables-save 的格式。但如果你有几十条规则或者涉及 nat 表、自定义链、复杂的地址转换我强烈不建议用 rc.local 一行行去敲。原因有三个第一脚本执行失败没有自动回滚机制规则漏加一条可能引发线上故障第二规则之间如果有依赖关系你得在脚本里自己维护顺序很容易乱第三排查问题时你没法通过 systemctl status 快速定位脚本哪一步出错只能看日志或者手动跑一遍脚本模拟。4.3 两种方式的组合玩法兼顾灵活与稳定在实际生产环境里我经常看到一种组合用法把复杂的、需要动态计算的规则放在 rc.local 里生成生成完毕后统一执行 iptables-save 落盘再交由 iptables-services 管理。打个比方你需要在开机时根据当前网卡 IP 动态生成 SNAT 规则这种情况下脚本就比静态配置文件灵活得多。更常见的组合是日常规则维护全部走 iptables-services也就是方式一只有个别特殊需求比如开机时需要先等某个服务启动完成再放行对应端口才在 rc.local 里补一条轮询等待加规则的操作。这种组合的前提是你对两种机制的加载顺序有清晰认识rc.local 在系统启动后期执行而 iptables 服务在更早阶段启动。所以如果你想用 iptables 规则去拦某个服务的流量而服务又启动得很早那就要注意时序问题。5. 两种方式横向对比选型不再纠结5.1 从管理成本、可靠性、灵活性三个维度看为了让你一眼看清两者的差异我先把对比结论摆出来对比维度方式一iptables-services方式二rc.local 脚本规则存储位置/etc/sysconfig/iptables 标准配置文件脚本文件 /etc/rc.d/rc.local管理方式systemctl 统一管理可查看服务状态systemd 执行一次无状态管理规则格式iptables-restore 格式适合批量导入导出shell 命令格式直观但零散排错便利性systemctl status iptables 可快速定位需要日志或手动执行脚本排查动态能力弱配置文件是静态的强可以在脚本里写循环、判断适合规模适合规则较多、结构复杂的生产环境适合规则少、逻辑简单的场景从可靠性角度看方式一明显占优。systemd 会对 iptables 服务做启动失败检测如果配置文件语法有误服务会进入 failed 状态你开机后一眼就能发现。而方式二如果脚本执行失败了系统依然正常启动只是规则没加载你只能靠业务异常去被动发现排查成本高不少。5.2 我个人在真实项目里的选择标准我在维护服务器时默认永远优先使用方式一只有遇到下面这几种情况才会考虑用 rc.local 补一笔一是规则需要根据当前系统的网络状态动态生成比如自动获取公网网卡名称然后做 SNAT二是某些第三方软件自带的开机脚本里已经通过 rc.local 去管了我保持风格统一三是临时在测试机上调规则不想为一条规则单独建服务配置。还有一点要提醒无论用哪种方式改完规则后都必须有一个验证再上线的意识。我自己习惯的流程是先手动执行规则再开第二个 SSH 窗口测试业务是否正常确认没问题之后才做持久化操作。千万别改完规则直接保存然后重启万一规则写错了把自己 SSH 断掉又是在远程服务器上那就非常被动了。6. 常见问题与排查实录踩过的坑都在这6.1 iptables 命令找不到iptables-services 装不上有朋友反馈CentOS 7 最小化安装后敲 iptables -L 提示 command not found。这是因为最小化系统默认没有装 iptables 命令行工具firewalld 是用 nftables 后端实现的。解决办法很简单yum install -y iptables iptables-services 一起装上。装完后确认版本iptables --version如果提示 iptables v1.4.21就说明命令已经在系统里了。还有一个常见情况是 yum 安装 iptables-services 时报依赖错误。一般是因为没有更新 yum 源缓存先执行 yum clean all yum makecache 再装。如果源本身有问题就检查 /etc/yum.repos.d/ 下的仓库配置把 base 源和 epel 源都配上基本上能解决。6.2 规则保存了开机却不生效问题出在哪这种问题我排查过好多次最常见的三个原因按概率排序第一个原因是 iptables 服务没有真正 enable。你执行了 iptables-save 生成文件但 systemctl enable iptables 没做或者做了又被别的操作覆盖了。开机后服务根本没启动自然没有任何规则。排查命令systemctl is-enabled iptables输出应该是 enabled如果显示 disabled 或 static就重新 enable 一次。第二个原因是 /etc/sysconfig/iptables 文件权限或者格式出了问题。比如你自己手工编辑过文件把某个字段写错了iptables-restore 加载时会直接报错并跳过整个文件。这种情况可以手动执行 iptables-restore /etc/sysconfig/iptables 看报错信息一行一行排查。第三个原因最隐蔽系统里同时存在多个网络管理工具比如 NetworkManager 和 firewalld 的残留配置在启动过程中把 iptables 规则清掉了。虽然你已经 systemctl disable firewalld但如果没执行 mask某些网络变化事件可能把它重新触发。建议彻底 mask 掉然后重启验证。6.3 规则顺序和 conntrack 的坑很多人都会踩规则顺序的问题我在前面提过这里再给一个具体例子。有一条常规规则是放行已建立连接的回包iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT如果你把它放在最后而当时 INPUT 链默认策略已经是 DROP那么这条规则本身没问题但问题是它只放行 ESTABLISHED 状态。如果你在这条之前或者之后有更严格的限制比如丢弃所有发往本机 53 端口的 UDP 新连接iptables -I INPUT -p udp --dport 53 -m state --state NEW -j DROP用 -I 插入到最前面那么后续的放行规则永远匹配不到 53 端口的包。这就是规则顺序的魔力匹配是从第一条开始顺序执行的命中了就停止继续匹配。所以设计规则时我建议把放行已建立连接这类兜底规则放在最前面把丢弃特定新连接的规则放在其后把放行具体端口的规则排在最后最后再设置默认策略。另外一个 conntrack 相关的问题是模块没加载。使用 -m state 或 -m conntrack 时内核需要加载 nf_conntrack 模块。绝大多数情况下系统会自动加载但如果你在虚拟机里做了内核裁剪或者手动禁用了模块规则会报错。排查方式lsmod | grep conntrack modprobe nf_conntrack如果需要开机自动加载模块可以写入 /etc/modules-load.d/conntrack.conf内容就一行nf_conntrack。这样可以避免下次重启后规则因为模块缺失而加载失败。6.4 远程操作时把自己锁在门外怎么自救这是很多人都经历过的惊魂时刻。你在远程服务器上执行了 iptables -P INPUT DROP结果当前 SSH 连接不在放行规则里瞬间断连。这种时候如果你的云服务器有 VNC 或者控制台可以直接通过控制台登录救回来。如果没有控制台那就只能靠云厂商的安全组规则给自己留一条后路或者在改规则前先写一个定时任务比如五分钟后自动清空防火墙echo iptables -F iptables -P INPUT ACCEPT | at now 5 minutes这条命令可以让你在误操作后有 5 分钟缓冲时间重新连上。虽然糙了点但关键时刻真能救命。我的习惯是只要做涉及默认策略调整的操作一定先执行这条 at 命令兜底等规则确认无误后再取消它。另外改完规则马上开一个新 SSH 窗口测试确认能连再关掉旧窗口这个习惯能帮你规避绝大多数锁死风险。7. 实战中的几点心得能帮你少走弯路7.1 写规则前先备份或者至少导出留存每次改动规则集之前先执行一次 iptables-save 导出存档这个动作花不了几秒钟但出了问题时它能让你快速回滚。我习惯按日期命名备份文件iptables-save /root/iptables-backup-$(date %F).rules一旦新规则出现问题恢复就是一条命令的事iptables-restore /root/iptables-backup-2026-01-01.rules7.2 用 vim 编辑配置文件时注意别引入多余字符有些朋友喜欢直接编辑 /etc/sysconfig/iptables而不是用 iptables-save 生成。编辑本身没问题但要特别注意这个文件对格式要求极严多一个空格、少一个换行都可能让整份文件加载失败。我见过有人在 Windows 上编辑完再传回 Linux导致文件带了 \r 回车符iptables-restore 直接报错。解决办法是装 dos2unix 转换一下或者干脆别用图形化编辑器折腾这个文件。7.3 规则复杂了就把管理脚本化当你手上的规则超过二十条我建议不要再用命令行一条条敲了。写一个 shell 脚本把规则分模块组织好比如基础放行、业务端口、安全防护、NAT 转发每个模块一个函数。这样不光维护方便还能在脚本里加上注释说明每条规则的用途。脚本写好后最终仍然通过 iptables-save 落盘让 iptables-services 接管。从我个人的运维经验来说方式一的配置文件托管思路是 CentOS 7 下最稳妥的选择它把规则变成了一种可审计、可回滚、可批量复制的资产。方式二更像一个应急补丁灵活但缺乏约束力。理解了两者的定位和边界你在实际项目中就不会再为规则不生效这种事挠头了。下次再有人问你 CentOS 7 的 iptables 怎么永久生效直接把这两种方式的差异和适用场景甩给他比背十条命令管用得多。