收到一台新服务器我一般不会急着敲firewall-cmd第一件事反而是先把/etc/firewalld下所有 XML 文件翻一遍。不是矫情是 firewalld 的精髓本来就藏在这一堆配置文件里。命令行只是前台入口真正的区域、服务、端口、富规则最终全部落在 XML 文件上。你如果只会firewall-cmd --add-servicehttp那只能算用了三成功力一旦遇到批量管理、故障排查、自动化运维配置文件才是那个让你一眼看穿全局的东西。这篇文章我不打算做成命令手册的复读而是站在“直接改配置文件的运维视角”把 firewalld 的配置文件布局、关键 XML 结构、修改流程、常见坑点全部拆开讲一遍。适合刚接手 Linux 服务器的新手也适合一直用命令但没深入过文件层的工程师。1. firewalld 配置文件的整体设计思路1.1 为什么直接改配置文件而不是只敲命令先说一个很多人误解的地方firewall-cmd加--permanent参数看起来是在“记规则”实际上它做的事就是往/etc/firewalld下的 XML 文件里写入内容。换句话说命令行改配置和直接编辑文件底层是同一件事。区别在于命令行适合快速调试。比如上线前临时放行一个端口测试敲完马上生效出问题随时删这个场景下直接敲命令是最高效的。但长期维护的场景我更推荐直接编辑配置文件。原因有三个一是可读性。打开public.xml你能看到这个区域开放了哪些服务、哪些端口、有没有来源地址限制所有规则都在一个文件里。命令行--list-all虽然也能看但内容一旦多起来一眼扫过去远不如 XML 直观。二是可回溯性。配置文件是可以纳入版本管理的。把/etc/firewalld交给 git 管理每次改动都有 diff出问题能快速回滚。纯命令行操作的话时间一长你根本记不清当时敲过什么。三是批量复制。新服务器上线时直接把旧机器上的zones、services目录拷过去再检查一下文件名和内容就能快速复刻整套防火墙策略。这比在新机器上一条条敲命令靠谱多了。当然我的习惯是“文件为主、命令为辅”。需要临时放行用命令需要长期固化写文件写完--reload验证。这应该成为所有运维的基本操作习惯。1.2 配置文件布局系统自带与用户自建的区别firewalld 的配置目录有两个这是新手最容易搞混的地方。/usr/lib/firewalld存放的是软件包安装时自带的默认配置。包括默认的区域定义、系统内置的服务定义ssh、http、https、dns 等。这个目录由 RPM 包管理升级 firewalld 软件包时可能会被覆盖所以正常情况下不应该去改它。/etc/firewalld才是管理员自己的配置目录。你手动新建的区域、服务、对默认配置的覆盖都应该放在这里。同名文件会覆盖/usr/lib/firewalld里的默认配置。想看完整的目录结构可以直接用ls -Rls -R /etc/firewalld/典型的目录结构是这样的/etc/firewalld/ ├── firewalld.conf ├── icmptypes/ ├── ipsets/ ├── services/ ├── zones/ └── helpers/services目录存放自定义服务定义zones目录存放区域配置icmptypes存放 ICMP 类型定义ipsets和helpers平时用的少但目录是存在的。记住一条黄金法则凡是需要长久生效的改动一律在/etc/firewalld下操作不要碰/usr/lib/firewalld。1.3 改完配置如何让规则生效修改 XML 文件后不会自动生效必须让 firewalld 重新加载。这里有两个容易混淆的命令firewall-cmd --reload是热加载它会保留当前运行时状态把永久配置重新应用一遍。已建立的连接不会被中断适合在服务运行期间应用配置变更。firewall-cmd --complete-reload则是完全重启 firewalld所有规则会从头加载已经建立的连接可能会被中断。非必要不用但改了/etc/firewalld/firewalld.conf这类全局配置后有时需要它才能彻底生效。另外推荐养成一个习惯改完文件先做语法检查再 reload避免把配置文件写坏了导致防火墙直接起不来。命令是firewall-cmd --check-config没有输出就代表配置文件没有问题。有问题会直接提示具体是哪个文件哪个标签写错了。2. 核心配置文件逐个拆解2.1 firewalld.conf全局行为的总开关/etc/firewalld/firewalld.conf是 firewalld 的全局配置文件控制的是整个防火墙服务的行为而不是具体的规则内容。这个文件平时改得不多但理解它很重要。我挑几个常用参数说明一下方便你按需调整参数默认值作用DefaultZonepublic新接口默认加入的区域MinimalMark100内部使用的 mark 值起始范围一般不要动CleanupOnExityes停止 firewalld 时是否清空规则Lockdownno开启后只允许 root 和 lockdown-whitelist 中的用户变更防火墙IPv6_rpfilteryes是否对 IPv6 启用反向路径过滤FirewallBackendnftables底层规则引擎RHEL 8 后默认 nftables这里提醒一点如果你改了DefaultZone修改后接口不会自动把现有的绑定关系迁移到新区域。需要手动把接口重新绑定或者重启 firewalld 服务否则容易出现在新默认区域下“规则全放行了”或“全拒绝了”的意外情况。我实际操作中遇到过几次都是因为改了默认区域后没处理接口绑定导致业务端口突然不通。2.2 zones/public.xml区域规则的主战场区域zone是 firewalld 的核心抽象。每个区域定义了一套适用的信任级别和安全策略接口或来源地址都要归属到某个区域。默认的public区域表示“不信任该网络上的其他主机”常见端口全部不放行。/etc/firewalld/zones/public.xml打开后大概长这样不同系统版本内容会有差异?xml version1.0 encodingutf-8? zone shortPublic/short descriptionFor use in public areas. You do not trust the other computers on networks.../description service namessh/ service namedhcpv6-client/ port protocoltcp port8080-8090/ /zone关键标签的含义我整理成了一张表标签作用short区域的显示名称不是区域名description区域描述target区域默认策略可填default、ACCEPT、DROP、REJECTservice name允许的服务名称对应 services 下的 XML 文件port protocol port直接允许的端口或端口段source address允许的来源地址rule富规则做精细控制时使用容易搞混的一点区域的真正名字是文件名本身不是 XML 里的short标签。你复制public.xml改成internal.xml那么新区域名就是internalshort只是给人看的别名。我见过有同事在short里写了个新名字然后一直在用旧名字 firewalld 去查查了半天查不到其实就是对机制理解有误。2.3 services/xxx.xml把端口封装成服务服务文件是 firewalld 最有设计感的地方。它把端口、协议、内核模块等封装成一个逻辑名称区域文件里只需要写service namehttp/不用关心底层开的是 80 还是 8080。系统服务定义在/usr/lib/firewalld/services/自定义服务放在/etc/firewalld/services/。看一下系统自带的http.xml?xml version1.0 encodingutf-8? service shortHTTP/short descriptionHTTP is the protocol used to serve Web pages.../description port protocoltcp port80/ /service服务名同样是文件名。新建myweb.xml服务名就是myweb。定义好之后需要先 reload 再使用firewall-cmd --reload firewall-cmd --permanent --zonepublic --add-servicemyweb服务文件里除了端口还可以声明需要的内核模块module namenf_conntrack_xxx/以及目标地址destination ipv4.../。不过日常用得最多的还是端口定义。我在实际项目里的习惯是凡是部署的新应用都先写一个专属服务文件而不是直接往区域文件的port标签里加端口。原因很简单服务文件可以在多个区域里复用统一管理起来逻辑更清晰出问题排查也方便。2.4 icmptypes、direct.xml 与辅助目录/etc/firewalld/icmptypes/目录存放 ICMP 类型定义比如echo-request.xml对应 ping 请求。限制别人 ping 你的服务器时可以在这里定义类型然后在区域里通过icmp-block标签引用。direct.xml是 firewalld 提供的一个“后门”允许管理员直接往 nftables/iptables 对应链里插入自定义规则。它的 XML 结构长这样?xml version1.0 encodingutf-8? direct rule priority0 tablefilter ipvipv4 chainINPUT-p tcp --dport 9999 -j ACCEPT/rule /direct我建议谨慎使用 direct 规则。它绕过了 firewalld 的区域模型和端口跟踪规则顺序完全依赖 priority 和你对底层链的理解。一个典型的坑是你在 direct.xml 里写了 ACCEPT 规则但如果所在的链在 firewalld 自身规则之前被处理那么区域里的 DROP 策略可能根本管不住它反过来也可能出现“明明区域里放行了端口却被系统原始链策略挡住”的困惑。如果只是需要放行端口或做简单的来源限制优先用区域文件 富规则别急着上 direct。它适合的场景是firewalld 本身无法表达的 nftables 规则比如自定义链跳转、特定状态匹配等。真有这种需求时一定要把 priority 规划清楚并做好注释。3. 实际修改流程与场景案例3.1 场景一给 Web 服务新建一个专用服务文件假设现在需要部署一套 Web 应用监听 8080 和 8443 两个 TCP 端口并且要求配置长期生效。我不会直接在 zone 里写两个port而是先建服务文件。第一步看一下系统有没有对应的服务文件。HTTP 服务有自带的但 8080/8443 显然不在里面所以新建/etc/firewalld/services/webapp.xml?xml version1.0 encodingutf-8? service shortWebApp/short descriptionCustom web application on port 8080 and 8443/description port protocoltcp port8080/ port protocoltcp port8443/ /service第二步语法检查并加载firewall-cmd --check-config firewall-cmd --reload第三步把这个服务加入 public 区域并验证firewall-cmd --permanent --zonepublic --add-servicewebapp firewall-cmd --reload firewall-cmd --zonepublic --list-services如果你不想用命令也可以在public.xml里直接加一行service namewebapp/然后 reload。效果一样看个人习惯。这里的经验是端口比较多或者应用要跨多个区域部署时服务文件的管理优势非常明显。比如同一套中间件在测试环境和生产环境的区域不同但服务定义是同一份直接复制webapp.xml到新机器即可。3.2 场景二在 public 区域直接开放端口段有些开发者申请的都是端口段比如“测试环境需要开放 1090-1100”。这种场景可以不用单独建服务文件直接在区域文件里写端口段。编辑/etc/firewalld/zones/public.xml加入一行port protocoltcp port1090-1100/保存后执行firewall-cmd --check-config firewall-cmd --reload firewall-cmd --zonepublic --list-ports看到1090-1100/tcp就说明已经生效了。这个方法虽然简单但有几个细节要注意。protocol属性的值必须是tcp、udp、icmp等合法协议名不能随便写。UDP 场景要单独加一行比如 DNS 服务就需要同时放行tcp/53和udp/53。另外端口段用短横线连接中间不要有空格。还有一个容易被忽略的问题如果只给开发人员提供了临时放行需求他们直接用命令行firewall-cmd --zonepublic --add-port1090-1100/tcp操作规则只存在于运行时状态一旦 reload 或者重启就没了。这是好事还是坏事取决于你怎么看。从管理角度说临时放行的规则不应该悄悄变成永久规则。所以我的做法是临时测试用命令进入正式变更流程时写文件并走 reload两条路分开规则清晰可审计。3.3 场景三修改默认区域与多区域切换假设业务上想把服务器的默认策略从public换成更严格的drop丢弃所有入站流量除非显式允许。最稳妥的做法是在/etc/firewalld/firewalld.conf里改DefaultZonedrop保存后 reloadfirewall-cmd --reload firewall-cmd --get-default-zone这里就是我前面强调过的坑点修改默认区域后现有接口和已有连接可能还属于旧区域。建议执行后立刻检查firewall-cmd --get-active-zones firewall-cmd --zonedrop --list-all如果你发现接口还挂在public下可以手动把它重新绑定firewall-cmd --permanent --zonedrop --change-interfaceeth0 firewall-cmd --reload绑定接口这个动作值得多说两句。区域和接口的绑定关系在 firewalld 里有两层状态运行时绑定和永久绑定。直接firewall-cmd --zonedrop --add-interfaceeth0只改了运行时重启后失效必须加--permanent或者配合--runtime-to-permanent才能持久化。调试网络问题时很多人忘了这一点结果一重启防火墙规则就“神秘还原”其实只是永久配置里根本没有这条绑定。3.4 场景四用富规则做精细化限制最典型的精细化需求是“数据库端口只允许内网某网段访问”。比如 MySQL 的 3306只想让192.168.1.0/24访问其他来源一律拒绝。在public.xml中加一条富规则rule familyipv4 source address192.168.1.0/24/ port protocoltcp port3306/ accept/ /rule这条规则的含义是来源为192.168.1.0/24的 IPv4 流量允许访问 TCP 3306 端口。这里需要理解 firewalld 区域中target的作用。public区域的默认target是default意思是未显式允许的流量会被拒绝。所以有了上面这条富规则放开内网来源后其他来源访问 3306 自然就被默认策略挡掉了不需要额外写拒绝规则。如果默认区域的target被改成了ACCEPT那情况就反过来了必须显式写拒绝规则否则所有来源都能访问。这也是我建议服务器默认区域不要轻易设成ACCEPT的原因最小开放才是安全底线。对应的命令行写法是firewall-cmd --zonepublic --add-rich-rulerule familyipv4 source address192.168.1.0/24 port protocoltcp port3306 accept我个人的建议是像这种长期规则直接写进 XML 文件管理。富规则语法复杂命令行里单引号、双引号、空格一多就容易出错写文件反而更清晰排错也更直观。3.5 顺手把运行时规则固化成永久配置很多时候我们会先用命令快速放行端口做验证验证通过后需要把它固化成永久配置。这里有一个非常好用的命令firewall-cmd --runtime-to-permanent它会把当前运行时的所有规则同步写入永久配置自动生成或更新/etc/firewalld下的 XML 文件。跑完之后检查一下生成的 zones 文件内容是否符合预期没有多余规则再收工。这个命令的价值在于省去了手动把每条临时规则转成文件配置的过程同时避免遗漏。我在处理一些临时变更比较多、又需要合规审计的环境里经常用它跑完立刻 git diff 看变更效率非常高。4. 常见问题与排查技巧4.1 改完配置不生效的几种原因这是我被问得最多的问题通常逃不出下面几个原因。一是忘了执行 reload。改完文件不重载永久配置只是躺在磁盘里运行时规则完全没变。这个最简单也最常见。二是改错了文件。有人把自定义规则写进了/usr/lib/firewalld/zones/public.xml不是不能生效而是软件包一升级就覆盖到时候规则全没了。所有永久改动都应该在/etc/firewalld下。三是区域搞错了。比如把服务加到了public区域但服务器的默认区域其实是dmz。这时候无论你 reload 多少次端口都不通。排查时先确认默认区域和接口绑定再确认规则加到了哪个区域。用下面三连命令快速定位firewall-cmd --get-default-zone firewall-cmd --get-active-zones firewall-cmd --list-all四是被系统其他安全机制拦截。firewalld 只是放行了端口不代表服务本身没有问题。SELinux 就是最常见的拦路虎端口明明已经放行但还是连不上检查一下audit2why输出和 SELinux 布尔值很多时候问题根本不在防火墙。4.2 XML 写错导致 firewalld 无法重启配置文件的语法错误是另一个高频故障。最典型的症状是执行 reload 时报错或者干脆systemctl status firewalld显示启动失败。遇到这种情况第一时间不要慌先跑检查命令firewall-cmd --check-config它会告诉你哪个文件哪一行有问题。另外也可以用xmllint直接检查 XML 文件xmllint --noout /etc/firewalld/zones/public.xml如果文件有语法错误xmllint会输出具体的错误位置。常见的错误包括标签没有闭合。比如写了port protocoltcp port8080却忘记写/port。属性拼写错误。protocol写成protofirewalld 解析的时候不会报致命错误但规则不会生效这种隐性错误最坑。文件编码问题。XML 声明了utf-8但文件实际保存成了 GBK 或带 BOM导致解析失败。建议所有配置文件统一 UTF-8 无 BOM。另一个重要的习惯修改/etc/firewalld下任何 XML 文件之前先把原文件备份一下。我的习惯是直接复制一份带日期的备份文件cp /etc/firewalld/zones/public.xml /etc/firewalld/zones/public.xml.bak.20250101改坏了随时恢复不至于手忙脚乱。4.3 reload 和运行时规则的取舍运行时规则和永久规则的混淆是新手绕不过去的坎。简单总结一下不带--permanent的firewall-cmd命令只修改运行时状态reload 后丢失。带--permanent的只修改永久配置要 reload 才生效。直接编辑 XML 文件修改的是永久配置同样需要 reload。我之前遇到过一次生产事故同事为了临时测试用命令放行了某个高风险端口验证完之后忘了回收。这个端口在运行时规则里一直开着但会话结束后大家都没注意。后来 firewalld 因升级重启规则自动丢失端口释放反而算是“自动修复”了。这件事给我的教训是临时放行规则必须在测试完成后立即删除最好在变更记录里标注预计回收时间。反过来也有一个坑如果你执行了firewall-cmd --reload运行时里那些临时添加的规则会全部消失因为 reload 会以永久配置为准重建运行时状态。所以不要在 reload 前依赖任何临时规则这是很多人“reload 之后网络就不通了”的原因。4.4 与 iptables/nftables 共存时的注意事项firewalld 在 RHEL/CentOS 7 系统上是默认的防火墙管理工具底层在 RHEL 8/9 上已经切换到了 nftables。这里有个容易踩的雷不要在同一台机器上同时启用 firewalld 和 iptables-services。两个服务同时运行大概率会出现规则互相覆盖、连接状态混乱的情况。你在 iptables 里加的规则可能被 firewalld 的重启直接清掉反过来也一样。如果你确实需要手动操作底层规则推荐使用direct.xml或firewall-cmd --direct来插入而不是另起一套 iptables 服务。另外要明白firewalld 重启时会重新生成整套 nftables 规则集手动执行nft或iptables命令添加的规则不会保留。这是个长期维护中经常被忽略的细节。4.5 常见问题速查表故障现象可能原因解决办法配置文件改了但端口不通没有 reload执行firewall-cmd --reloadreload 后规则消失之前用命令只加了运行时规则用--permanent或--runtime-to-permanent固化明明放行了端口还是连不上区域不对或 SELinux 拦截确认默认区域与接口绑定检查 SELinux 审计日志重启后自定义服务消失服务文件写到了/usr/lib下移到/etc/firewalld/services/reload 报语法错误XML 标签写错或编码不对运行firewall-cmd --check-config定位修改默认区域后网络异常接口还绑定在旧区域检查get-active-zones并重新绑定接口5. 一些长期维护的实操建议5.1 把 /etc/firewalld 纳入版本管理我强烈建议把/etc/firewalld纳入 git 管理。每次变更后提交一次注释写清楚改了什么、为什么改。这套做法带来的收益远超想象比如回滚配置时直接git revert排查问题时可以看历史记录多人协作时能知道是谁改的。初始化版本管理很简单cd /etc/firewalld git init git add . git commit -m initial firewalld config改完文件后git diff git add . git commit -m add webapp service on 8080/84435.2 明确命名规范并写好描述自定义服务文件和区域文件一定要有清晰的short和description。这个描述不只是写给别人看的也是写给自己看的。三个月后你再看一个没有描述的服务文件大概率想不起来当时为什么要开放这些端口。我的规范是描述里写清楚应用名称、用途、开放端口的原因、对应负责人。内容简短但信息量足够。运维这东西最难的不是技术而是交接和回溯。5.3 保持最小开放原则最后一个建议也是我处理过无数起安全事件后的总结默认区域一定要用最小开放策略。能限定来源 IP 就限定来源 IP能限定端口就限定端口能用服务文件就不要开放整段大范围端口。生产服务器默认区域用public甚至drop然后单独给管理接口开 SSH给业务端口开对应服务。规则越少攻击面越小排查问题也越简单。这套方法论救过我很多次尤其是在凌晨三点被叫起来处理服务器故障时打开配置文件比回忆之前敲过什么命令要靠谱得多。如果你也想把自己的服务器管得明明白白从今天开始试着把你的下一次改动写进 XML而不是只留在命令行里。文件才是真相。