接手过 CentOS7 服务器的人对 firewalld 应该都不陌生日常放行单个端口很简单一条firewall-cmd --permanent --add-port8080/tcp就结束了。可一旦需求变成“这批服务要开几十上百个端口”逐条敲命令就成了灾难。我前段时间就遇到一次给某数据平台中间件集群批量放行端口的需求前前后后试了几种方式最后发现直接操作 firewalld 的规则配置文件才是最稳妥、最好审计、也最容易批量化的做法。这篇文章就围绕 CentOS7 下 Firewalld 规则配置文件批量放行端口这件事把配置文件的组织方式、批量操作的几种写法、验证回滚流程和常见坑一次性讲清楚适合正在做服务器初始化、中间件部署、以及手工维护防火墙规则的人参考。1. 为什么我选择直接改规则文件而不是一条条敲命令先说那次经历。某天下午接到需求一个新环境要接入一套消息中间件集群三个节点每个节点需要放行 TCP 20000 到 20100、UDP 5300 到 5400外加 8080 和 8443。看起来端口段不多但同一套配置要原样复制到 6 台服务器上。如果用firewall-cmd一条条敲每台机器少说十几条命令还得小心 20000-20100 这种范围在命令行里的写法出一点错就得从头检查。命令行方案在批量场景下有三个明显的痛点。第一是慢端口越多命令越多一台机器敲完还要换下一台第二是难审计几十条命令执行下去事后根本说不清到底放开了哪些端口想对比两台机器配置是否一致只能肉眼碰运气第三是不好回滚临时反悔了要一条条删删漏一条就是安全隐患。直接改配置文件完全是另一套体验。/etc/firewalld/下的规则文件是纯文本可以放在 Git 里做版本管理可以复制到所有机器保持一致性改之前cp一份就是天然的回滚点。而且很多人不知道一点firewall-cmd --permanent --add-port这个命令的本质就是在改写这些 XML 文件只是通过 CLI 做了一层校验而已。所以直接编辑规则文件并不是什么旁门左道只是绕过了逐条校验需要对 XML 格式有把握。下面这张表是我自己选型时的对比场景就是“批量、多端口、多机器”对比项firewall-cmd 逐条执行直接编辑规则文件操作效率端口多时极低一次性写入高可审计性差靠日志或屏幕记录好文件可 diff多机一致容易遗漏或手滑文件原样分发即可回滚删除命令逐条执行恢复备份文件上手门槛低需要理解 XML 结构适用场景临时调试、少量端口正式需求、批量部署需要说明的是CLI 方式也有它的价值特别是临时开个端口调试问题firewall-cmd --add-port改运行时配置非常方便。我这里的建议是临时操作用命令正式、批量、要长期生效的规则用配置文件。两者不冲突很多服务器上长期规则和临时规则是并存的。2. 动手前先搞懂 Firewalld 的配置文件体系直接改文件之前必须先把/etc/firewalld/下那几类文件的分工搞清楚。改错文件是最冤枉的踩坑方式我见过有人把规则写进direct.xml结果完全没生效原因就是放错了地方。2.1 zone 文件常规端口放行的主战场正常情况下批量放行端口的主战场是/etc/firewalld/zones/目录下的 XML 文件。每一台 CentOS7 机器都有一组 zone区域比如 public、internal、dmz、work每个 zone 代表一种信任级别。系统会有一个默认 zone通常就是 public所有没有特别指定的网卡流量都走这个 zone。/etc/firewalld/zones/public.xml对应 public 区域。系统自带的初始文件大概长这样?xml version1.0 encodingutf-8? zone shortPublic/short descriptionFor use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted./description service namessh/ service namedhcpv6-client/ /zone这个文件里service namessh/表示放行 SSH 服务对应 22 端口service namedhcpv6-client/放行 DHCPv6 客户端所需端口。我们要做的批量放行就是在这个文件里增加port元素指定端口号和协议port port8080 protocoltcp/ port port20000-20100 protocoltcp/这里有一个容易忽略的点/usr/lib/firewalld/zones/下还有一份系统默认模板而/etc/firewalld/zones/下的文件会覆盖模板。如果你在/usr/lib底下改文件重启后可能被覆盖一定要改/etc/firewalld/zones/这一份。2.2 direct.xml直接写 iptables 参数的旁路通道/etc/firewalld/direct.xml是另一个入口允许你绕过 zone 的抽象直接塞 iptables 命令行参数。文件长这样?xml version1.0 encodingutf-8? direct rule ipvipv4 tablefilter chainIN_public_allow priority0-p tcp -m multiport --dports 30000:30050 -j ACCEPT/rule /directIN_public_allow是 public zone 内部实际使用的自定义链直接往这个链里塞规则语义上跟 zone 文件放行端口是一致的。但我一般不会首选direct.xml原因后面在选型对比里细说。它更适合常规 zone 语法表达不了的场景比如需要自定义匹配条件、多条件组合的规则。2.3 rich rule藏在 zone 文件里的复杂规则rich rule 不是单独的文件而是 zone XML 文件内部的rule元素。它支持更复杂的条件比如限定来源 IP 再放行端口rule familyipv4 source address192.168.1.0/24/ port port8443 protocoltcp/ accept/ /rule这条规则的意思是只有来自192.168.1.0/24的流量才允许访问 8443 端口。rich rule 功能强大但语法比port复杂批量堆端口时阅读性也差所以大批量放行场景中我不太用它后面会展开对比。2.4 运行时配置与永久配置的区别理解 firewalld 的关键是它有两套配置运行时runtime和永久permanent。运行时配置是当前生效的规则firewall-cmd --add-port不加--permanent时只改运行时重启即失效。永久配置存在/etc/firewalld/的 XML 文件里我们手动编辑文件改的就是永久配置。改完文件后要让规则立即生效必须执行重载firewall-cmd --reload--reload会把永久配置重新应用到运行时并且尽量保留已建立的连接是最常用的方式。另一个是--complete-reload它的动作更彻底会重新初始化整个防火墙已经建立的连接也会被重置一般只有规则完全乱掉的时候才用。值得提醒的是部分版本的 firewalld 会通过文件监听自动感知 zone 文件变化并触发重载但不同版本行为不一致发布流程里不要依赖这种自动行为手动执行 reload 才有确定性。3. 批量放行的四种姿势及选型对比搞清楚文件体系之后就可以聊批量放行的具体写法了。根据端口数量和场景我通常会在四种方式里选。3.1 Zone 文件里堆端口条目最直观的方式就是在 zone 文件里一条条堆port元素?xml version1.0 encodingutf-8? zone shortPublic/short descriptionFor use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted./description service namessh/ service namedhcpv6-client/ port port8080 protocoltcp/ port port8443 protocoltcp/ port port10000 protocoltcp/ port port10001 protocoltcp/ port port10002 protocoltcp/ /zone优点是结构清晰、任何会看 XML 的人都能一眼看懂缺点是端口多的时候文件很长。如果只有几十个端口我推荐这种方式配合版本管理非常直观。3.2 端口段 range 写法对于连续端口firewalld 支持直接写范围中间用短横线连接port port20000-20100 protocoltcp/ port port5300-5400 protocoludp/这是批量放行中最该优先使用的写法。一条规则就能覆盖 101 个端口iptables 底层会自动把它翻译成一个-m multiport --dports 20000:20100的匹配项。要注意的是协议必须写对TCP 服务写tcpUDP 服务写udp混用会导致服务仍然不通。这里有个性能相关的细节firewalld 会把 zone 文件里每个port元素展开成一条 iptables 规则。如果几百个不连续端口全部逐条列出来防火墙规则链会变得很长每次报文匹配都要走一遍链表。iptables 的 multiport 模块单条规则最多支持 15 个端口超过就得拆多条规则。所以能用范围就用范围实在是不连续端口也尽量合并成尽量少的范围段。3.3 用脚本批量生成 XML 片段当端口清单来自需求文档、数量上百而且不连续时手写 XML 不现实这时候用脚本生成片段是最靠谱的。我常用一个简单的 Python 脚本把端口列表自动合并成范围def to_ranges(ports): ports sorted(set(ports)) res [] start prev ports[0] for p in ports[1:]: if p prev 1: prev p else: res.append((start, prev)) start prev p res.append((start, prev)) return res # 示例8080、8443 以及 10000 到 10100 连续段 ports [8080, 8443] list(range(10000, 10101)) for a, b in to_ranges(ports): tag str(a) if a b else f{a}-{b} print(f port port{tag} protocoltcp/)输出结果port port8080 protocoltcp/ port port8443 protocoltcp/ port port10000-10100 protocoltcp/如果机器上没有 Python用 bash 也能做只是合并范围的逻辑要自己多写几行for p in $(seq 30000 30100); do printf port port%s protocoltcp/\n $p done脚本生成的好处是端口清单可以放在一个纯文本文件里维护脚本负责生成 XML格式永远统一不会出现手写时少个斜杠、多个空格的问题。3.4 direct.xml 处理极端场景少数极端情况我会用到direct.xml比如需要同时对多个不连续端口做一条规则或者需要自定义链的复杂跳转。例如direct rule ipvipv4 tablefilter chainIN_public_allow priority0-p tcp -m multiport --dports 30000,30002,30004-30010 -j ACCEPT/rule /direct用 direct 规则可以直接利用 multiport 的逗号组合语法在不连续端口场景下能控制规则条数。但代价是失去了 zone 的抽象firewalld 自动生成的规则和手动塞进去的规则混在一起排查问题时心智负担更重。我的建议是能用port解决就不用 directdirect 留给真正需要的时候。3.5 四种方式的选型逻辑我把选型逻辑总结成一个表格实际工作里基本照着这个判断场景推荐方式理由少量端口≤20zone 文件堆条目直观、可读性好端口有规律、连续zone 文件 range 写法规则条数少性能好端口多且不连续脚本生成 zone 条目避免手写错漏需要来源 IP 限制等复杂条件rich rule只能用它需要复杂匹配、自定义链direct.xml兜底选项4. 完整实战一次 160 个端口的批量放行全过程理论讲完来看一次完整的实际操作。我以某数据平台中间件接入为例需求是这样的新部署的中间件集群需要对外提供 TCP 10000-10100 共 101 个端口UDP 5000-5100 共 101 个端口另外还有 8080 和 8443 两个 Web 端口。所有服务器都是 CentOS7默认 public zone。4.1 第一步确认当前生效的 zone改文件之前先弄清楚流量到底走哪个 zone。很多意外都是因为想当然认为默认是 public结果服务器的接口绑到了别的 zone 上。# 查看默认 zone firewall-cmd --get-default-zone # 查看所有活跃 zone 以及绑定的网卡 firewall-cmd --get-active-zones如果--get-active-zones显示public绑定在eth0上那我们的目标文件就是/etc/firewalld/zones/public.xml。如果绑定的是internal或者自定义 zone就要改对应的文件。4.2 第二步备份规则文件任何一次修改前都做备份这是成本最低的保险。我用带日期的备份文件名方便回滚时辨认cp /etc/firewalld/zones/public.xml /etc/firewalld/zones/public.xml.bak.$(date %F)4.3 第三步用脚本生成端口 XML 片段按照上面的 Python 脚本思路把需求端口喂进去ports list(range(10000, 10101)) [8080, 8443] # 按协议区分 tcp_ports sorted(set(ports)) udp_ports list(range(5000, 5101)) def to_ranges(ports): ports sorted(set(ports)) res [] start prev ports[0] for p in ports[1:]: if p prev 1: prev p else: res.append((start, prev)) start prev p res.append((start, prev)) return res for a, b in to_ranges(tcp_ports): tag str(a) if a b else f{a}-{b} print(f port port{tag} protocoltcp/) for a, b in to_ranges(udp_ports): tag str(a) if a b else f{a}-{b} print(f port port{tag} protocoludp/)运行后输出port port8080 protocoltcp/ port port8443 protocoltcp/ port port10000-10100 protocoltcp/ port port5000-5100 protocoludp/80 个手工条目被自动压缩成了 4 条范围规则这就是批量放行的精髓。4.4 第四步把片段写入 zone 文件并校验 XML用编辑器打开/etc/firewalld/zones/public.xml把生成的片段插到/zone之前保存?xml version1.0 encodingutf-8? zone shortPublic/short descriptionFor use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted./description service namessh/ service namedhcpv6-client/ port port8080 protocoltcp/ port port8443 protocoltcp/ port port10000-10100 protocoltcp/ port port5000-5100 protocoludp/ /zone保存之前先做 XML 语法校验避免一个标签写错导致整个 zone 加载失败# 方法一xmllint需要安装 libxml2 xmllint --noout /etc/firewalld/zones/public.xml # 方法二没有 xmllint 时用 Python python -c import xml.dom.minidom,sys; xml.dom.minidom.parse(/etc/firewalld/zones/public.xml); print(xml ok)输出没有报错文件就算过了语法关。还要顺手确认一下文件属主和权限避免后面 firewalld 回写时出问题chown root:root /etc/firewalld/zones/public.xml chmod 640 /etc/firewalld/zones/public.xml4.5 第五步重载并确认规则firewall-cmd --reload # 确认没有报错然后查看 public zone 的完整配置 firewall-cmd --zonepublic --list-all正常输出里会看到public (active) target: default icmp-block-inversion: no interfaces: eth0 sources: services: ssh dhcpv6-client ports: 8080/tcp 8443/tcp 10000-10100/tcp 5000-5100/udp masquerade: no forward-ports: source-ports: icmp-blocks: rich rules:看到ports一行列出了我们添加的全部端口和范围这一步就算成功了。5. 改完之后的验证与回滚配置写对只是第一步端口到底通不通必须验证。而且安全的变更流程一定要有回滚预案不然改完上不了线连恢复的手段都没有。5.1 用 firewall-cmd 查看规则状态除了--list-all还有几个常用查询命令# 只查看已放行的端口 firewall-cmd --zonepublic --list-ports # 查询单个端口是否已放行 firewall-cmd --zonepublic --query-port8080/tcp--query-port返回yes表示端口已在放行列表中。对于 range 端口直接用10000-10100/tcp查询。5.2 用 iptables-save 核对底层规则firewall-cmd显示的是它自己维护的逻辑视图我更习惯再用iptables-save看一眼底层真实规则确认 firewalld 确实把规则下到了 iptablesiptables-save | grep IN_public_allow输出里能看到类似-A IN_public_allow -p tcp -m tcp --dport 8443 -m conntrack --ctstate NEW -j ACCEPT -A IN_public_allow -p tcp -m multiport --dports 10000:10100 -m conntrack --ctstate NEW -j ACCEPT -A IN_public_allow -p udp -m multiport --dports 5000:5100 -m conntrack --ctstate NEW -j ACCEPT看到 multiport 范围匹配正确出现说明规则已经真正生效。这一步还能发现一个常见问题范围没有被合并成一条 multiport而是展开成了几百条单端口规则那就说明 XML 里的 range 写法没有生效需要回头检查端口段格式。5.3 用实际连接测试做端到端验证规则层面没问题不代表服务本身通了。我一般会分两层验证先确认服务在监听再从外部确认端口可访问。# 在服务器本机查看监听端口 ss -lntup | grep -E :(8080|8443|10000|5000)\b # 用 nc 从本机探测 nc -vz 127.0.0.1 8080 nc -vz 127.0.0.1 10000如果服务还没启动或者监听的端口跟放行的端口不一致ss的输出对不上就要先查服务配置而不是查防火墙。从外部机器测试时把127.0.0.1换成服务器实际 IPping 通不代表端口通一定要用 TCP 层的探测工具。5.4 回滚操作万一改完之后发现端口放行范围有误或者中间件集群换方案了回滚只要两步# 用备份覆盖修改后的文件 cp /etc/firewalld/zones/public.xml.bak.2025-01-01 /etc/firewalld/zones/public.xml # 重新加载 firewall-cmd --reload回滚后再次执行firewall-cmd --zonepublic --list-ports确认端口列表已经恢复。这里要强调的是不要手忙脚乱去删 XML 里的条目直接恢复备份文件最可靠因为备份文件里一定是一个曾经正常工作的状态。6. 配置文件方案踩坑记录直接编辑配置文件这件事我前后踩过不少坑挑几个最典型的分享出来这些细节在官方文档里往往不会写。6.1 改错了 zone规则没生效最大的一次失误是改完public.xml后 reload端口就是不生效。排查了半天才发现那台服务器默认 zone 被改成了dmz网卡绑定在dmz上我一直改 public 当然没用。现在我的流程固定了先firewall-cmd --get-active-zones再决定改哪个文件绝不跳过这一步。6.2 XML 语法错误导致整个 zone 失效XML 对语法零容忍少一个闭合标签、多用半个引号整个文件解析失败。最麻烦的是有些版本在解析失败时会保留旧配置继续运行有些版本可能直接不加载新配置表现差异很大。我的解决办法是写完后一定先跑一遍xmllint --noout再 reload绝不跳过校验。另外注意别用中文引号、全角空格编辑器自动替换最容易埋这种雷。6.3 reload 用错了级别有一次我在执行变更时用了--complete-reload结果正在跑的业务连接全部断了一遍。那台机器上有长连接任务本来只改个端口用--reload就够了一不留神用了更粗暴的完整重载代价是业务抖动。现在我的原则是默认只用--reload只有防火墙状态已经完全混乱、需要彻底重置时才考虑--complete-reload。6.4 文件权限和属主被改乱我遇到过一次手动编辑后文件属主变成了当前登录用户firewalld 在回写文件时直接报权限错误。建议对/etc/firewalld/zones/下的文件统一执行一次chown root:root和chmod 640并养成修改后检查权限的习惯。6.5 端口范围太大导致规则异常用过一次从 1 写到 65535 的极端范围虽然语法上没问题但 firewalld 处理起来并不愉快而且这种全开行为本身就有很大安全风险。iptables 的 multiport 单条规则最多 15 个端口范围按 2 个端口计。范围太大时要么拆段要么考虑用 rich rule 限定来源 IP而不是把整个防火墙打开。6.6 忘了区分 TCP 和 UDP同样的端口号TCP 和 UDP 是两套完全独立的规则。中间件有些服务用 UDP 做服务发现我只放了 TCP结果客户端一直注册不上。查问题的时候一度以为是应用配置出错最后看到 XML 里 UDP 端口根本没写。批量放行时建议脚本里按协议分别输出人眼检查时也先数数量再对协议。6.7 SELinux 在防火墙之外又拦了一道防火墙放行只是第一层。CentOS7 默认开启 SELinux如果服务要监听一个非标准端口而系统的 SELinux 端口策略里没有这个端口即使防火墙放行了服务也可能起不来。典型场景是自定义端口上的 HTTP 服务。这种情况需要给端口打上对应的 SELinux 标签这跟 firewalld 是两码事排查端口不通时如果防火墙规则看着都对记得往这个方向想。7. 几个让批量放行更省心的管理习惯文章最后分享几个我实际工作中沉淀下来的管理习惯算不上什么高深技巧但确实能省不少事。第一把端口清单当作跟代码一样的资产来维护。我习惯在项目仓库里放一个ports.txt一行一个端口加协议加用途说明比如10000/tcp # 消息中间件主端口。需要放行时直接用脚本从这个文件生成 XML规则始终跟需求文档一一对应不会出现“这个端口到底谁让加的”这种历史谜案。第二配置文件尽量少写注释。firewalld 自己会回写这些 XML 文件每次回写都可能把注释丢掉你精心写的注释可能过几天就没了。有需要说明的内容写在 Git 提交信息或者变更单里比写在配置文件里靠谱。第三批量部署到多台机器时优先用模板化的配置管理工具。把public.xml做成模板通过自动化工具分发到各节点再在任务里统一执行 reload。相比手动逐台修改模板化能保证所有机器规则完全一致后续加端口只要改一处模板重跑一遍几台、几十台的效率差距非常明显。回到最初那个中间件集群需求用配置文件方案后6 台服务器的规则在十几分钟内全部就位每台机器的 XML 文件内容完全一致备份和上线记录都留在 Git 里。后来运维同事要查某台机器到底开了哪些端口直接看仓库里的文件就行比登录服务器敲命令翻输出舒服得多。批量放行端口这件事核心其实不是“放行”本身而是让规则的变更变得可见、可复现、可回滚配置文件正是把这三件事做到位的最直接途径。
阅读完成 · 觉得有帮助?