
很多人一遇到网络不通第一反应就是systemctl stop firewalld。我先给结论在绝大多数 Linux 服务器上firewalld 是你最后一道本地防线直接关掉等于把家门敞开。我跑了快十年运维线上事故里至少有三成和乱关防火墙或者乱改 iptables 有关。firewalld 并没有那么可怕它只是把原来散落在 iptables 里的规则做了一个动态管理框架核心概念不复杂但选 zone 和写 rich rule 的时候有不少细节今天我把日常用得上的东西掰开揉碎讲一遍。1. 先想清楚三个问题firewalld 到底在管什么1.1 它不是 iptables 的替代品而是一套动态管理框架很多人以为 firewalld 是 iptables 的替代品其实不对。firewalld 是一个守护进程它负责管理底层规则底层要么是 iptables要么是 nftables。RHEL/CentOS 7 时代它主要操作 iptablesRHEL 8/9、Rocky Linux 这些新系统上默认后端已经是 nftables但保留了一套叫firewall-cmd的用户态命令让你不需要直接去面对 nft 那堆链式语法。这个设计最大的价值是动态。你用iptables -A手动加一条规则规则立刻生效但要删掉某条规则得自己数行号你用 firewalld 加一条规则它会自动处理规则内部的排序、去重、加载和持久化。你改完以后可以--reload也可以不 reload运行时配置立即生效。改错了还能一键把运行时配置回滚到永久配置。这一点在割接窗口里非常重要谁都不想半夜在业务高峰期手滑删错一条链。我习惯把它类比成小区门卫制度。iptables 是一堆写在纸上的访客名单firewalld 则是一个前台系统你只要告诉前台今天放行送快递的它自己会去更新完整名单还会优先处理黑名单人员。rich rule 就是前台手里那支笔可以写比普通服务更细的规则。1.2 为什么直接关掉防火墙是个坑线上见到最多的一句话就是我防火墙关了还是不通或者反过来我把服务端口放开了外面还是连不上。这两种情况都说明一个问题你根本没搞清楚当前流量经过了哪些过滤点。systemctl stop firewalld只是关掉了 firewalld 这个管理进程它在内核里已经加载的规则会不会被清空我实测下来停止 firewalld 后它通常会 flush 掉自己管理的规则让防火墙变成一个放行所有的裸奔状态。听起来问题解决了但它同时带来三个隐患第一你原本对公网网卡 public 区做的限制全部失效。如果这台机器有外网 IP那等于所有端口都暴露在公网扫描器几分钟就能摸到你数据库端口。第二firewalld 被 stop 以后firewall-cmd --state会返回 not running。后续如果有人通过 Ansible 或监控脚本去检查防火墙状态得到的结论是异常于是半夜给你打电话。第三某些系统服务会在开机时重新拉起 firewalld你手工 stop 只对当前会话有效重启后又自动回来了。如果你真正想要的是临时放行某个端口用来调试正确做法是加一条运行时规则用完立刻删掉而不是把整个服务停掉。1.3 最容易忽略的两种隐藏规则即使你平时不用防火墙机器上可能还有两层东西在帮你挡流量。第一层是云平台的安全组第二层是 Docker 这类容器工具自己写的 iptables 规则。云安全组的优先级在虚拟机网卡之外它和操作系统内部防火墙叠加。很多人在腾讯云、阿里云上配了安全组放行 8080但机器内部 firewalld 没放行照样不通。反过来也一样安全组没放行你在机器里怎么配都没用。排查的时候先分清流量有没有到达网卡。Docker 则是另一个坑。它会在 iptables 的DOCKER链里写规则通过-p 8080:80映射出去的端口往往绕过了 firewalld 的 zone 限制。也就是说你配了 firewalld 禁止外部访问 8080但 Docker 的 nat 规则可能已经让这个端口对公网开放了。你以为是 firewalld 失灵其实是 Docker 在 netfilter 的另一个层级做了放行。这个问题下面还会细说。2. 核心配置拆解zone、服务、端口和 rich rule2.1 选 zone 比刷规则重要firewalld 的核心概念是 zone中文可以理解成安全域。每个网卡都可以归到一个 zone每个 zone 内部有一套自己的放行策略。流量进入哪块网卡就按哪个 zone 的规则执行。这个思路和华为、锐捷这些硬件防火墙上的安全域很像比如 untrust 对应公网trust 对应内网DMZ 对应对外服务区。刚接触 firewalld 的人容易犯一个错误永远只操作默认的 public zone所有规则全堆在 public 里。其实合理做法是让不同网卡进不同 zone内网网卡放行公司网段外网网卡只放行必要的服务。这样逻辑清晰也方便以后审计。常见 zone 的默认行为我整理成了一张表但注意不同发行版默认值会有差异你机器上以firewall-cmd --zonexxx --list-all为准。zone常见默认放行内容适用场景drop所有入站直接丢弃不回应隔离区、恶意流量隔离block入站拒绝IPv4 回 host-prohibitedIPv6 回 adm-prohibited需要明确告诉对方被拒绝的场景publicssh、dhcpv6-client公网网卡externalssh、IPv4 伪装路由器/网关外网口dmzssh对外服务器前置区workssh、dhcpv6-client、ipp-client办公网homessh、mdns、dhcpv6-client、samba-client、ipp-client家庭网络internalssh、mdns、dhcpv6-client、samba-client、ipp-client公司内网/可信内网段trusted全部放行完全可信的内网集群这里要特别留意--set-default-zonetrusted这种操作。如果你把所有网卡都丢进 trusted那 firewalld 基本等于没开。很多内网服务器图省事这么干一旦这台机器被攻破横向移动就完全没有阻挡。我建议默认 zone 用 public指定的内网网段再单独走 internal 或自定义 zone。2.2 服务、端口、端口转发一条条捋清楚最常用的命令是增删 service 和 port。service 是端口的别名比如 http 对应 80/tcphttps 对应 443/tcpssh 对应 22/tcp。用 service 的好处是你不用记忆端口号而且如果系统升级后某个服务的端口变了firewalld 内置的服务定义也会跟着变。# 查看当前默认 zone 的所有规则 firewall-cmd --list-all # 查看 public zone 放行了哪些服务 firewall-cmd --zonepublic --list-services # 放行 http 和 https并写入永久配置 firewall-cmd --permanent --zonepublic --add-servicehttp firewall-cmd --permanent --zonepublic --add-servicehttps # 重新加载永久配置 firewall-cmd --reload注意--reload不是重启服务它只是把永久配置重新刷到运行时里。运行时已经建立的连接不会断开这是它比--complete-reload温和的原因。--complete-reload会把所有规则清掉再重新加载可能导致已有连接中断生产环境尽量少用。如果你用的服务不在内置列表里比如 8080 或者 30000-32768 这种 RTP 端口段直接用 port 放行firewall-cmd --permanent --zonepublic --add-port8080/tcp firewall-cmd --permanent --zonepublic --add-port30000-32768/udp firewall-cmd --reload端口转发是另一种常见需求比如内网 MySQL 在 192.168.10.5:3306你想让外部通过主机的 33060 访问它。命令如下# 开启 IP 转发否则 DNAT 不生效 sysctl -w net.ipv4.ip_forward1 echo net.ipv4.ip_forward 1 /etc/sysctl.conf # 添加端口转发规则 firewall-cmd --permanent --zonepublic --add-forward-portport33060:prototcp:toport3306:toaddr192.168.10.5 firewall-cmd --permanent --zonepublic --add-masquerade firewall-cmd --reload这里有个很多人栽过的坑端口转发到其他内网主机时如果不加masquerade转发流量经常回不来因为目标主机看不到合理的源地址。加了--add-masquerade后主机会做 SNAT源地址变成主机内网 IP数据包就能正常往返。但 masquerade 本身会把 zone 内所有出站流量都伪装成主机 IP如果你的 zone 是 external 这类用于路由的场景没问题如果只是普通服务器建议转发规则和 masquerade 都放到同一个专用 zone避免影响其他业务。2.3 黑白名单别只盯着 iptablesrich rule 才是主力firewalld 的 rich rule 是一种比 service/port 更细粒度的规则可以用来做源地址白名单、黑名单、日志记录、限流。基本语法是一个 rule 块里面用引号包起来。先看一个最简单的黑名单丢弃来自某个 IP 的所有流量firewall-cmd --permanent --add-rich-rulerule familyipv4 source address203.0.113.66 drop firewall-cmd --reload再比如白名单只允许公司办公网段访问 SSH其他来源全部拒绝。拆解一下你要做两步。第一步把 public zone 默认的 ssh 服务移除否则等于所有来源都能访问 SSH第二步加入一条 rich rule允许指定网段访问 SSH。firewall-cmd --permanent --zonepublic --remove-servicessh firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address192.0.2.0/24 service namessh accept firewall-cmd --reload这里要理解 firewalld 的匹配逻辑。rich rule 的优先级高于普通的 service/port 规则所以即使你把 ssh 服务移除了指定网段的流量仍然会因为 rich rule 被接受。反过来如果你在 public zone 里留着 ssh 服务又没有在 rich rule 里显式 drop 其他来源那你移除 ssh 的操作可能就是多此一举。建议写完以后用firewall-cmd --list-all --zonepublic检查一下确认 ssh 服务不在列表里。如果黑名单 IP 很多一条条 rich rule 会非常难维护。这时候用 ipset 更合适# 创建一个 ipset firewall-cmd --permanent --new-ipsetblacklist --typehash:ip firewall-cmd --permanent --ipsetblacklist --add-entry203.0.113.66 firewall-cmd --permanent --ipsetblacklist --add-entry203.0.113.67 # 在 rich rule 里引用这个 ipset firewall-cmd --permanent --add-rich-rulerule source ipsetblacklist drop firewall-cmd --reloadipset 方式在大规模封禁时效率高而且你可以写脚本定期从威胁情报源拉取 IP 灌进去。rich rule 加日志也很有用后面排查章节会专门讲。3. 操盘实录把一台 Web 服务器从裸奔改造成规范防护3.1 场景与目标假设我现在有一台 Rocky Linux 9 服务器双网卡。eth0 接公网eth1 接内网。这台机器要跑一个 Web 服务对外提供 80/443同时需要运维人员通过 SSH 管理但 SSH 只能从办公网段访问另外还有一个内部管理后台跑在 8080 端口只允许内网访问。改造前这台机器处于裸奔状态firewalld 没开iptables 也没有规则。我要把它变成一个规范的防护状态。先梳理需求公网 eth0 所在 zone放行 http、httpsSSH 仅允许办公网段 203.0.113.0/24。内网 eth1 所在 zone放行 SSH、8080 管理后台内网网段 192.168.10.0/24 之间可以互相访问。其他所有入站流量默认拒绝。这个场景很典型既能覆盖 service/port 配置又能演示 rich rule 白名单还涉及多 zone 协同。3.2 配置步骤全记录先看一下当前状态确认网卡和 zone 对应关系ip addr show firewall-cmd --get-active-zones如果 eth0 默认已经被分配到 publiceth1 也被分配到 public那就要调整。我习惯把 eth1 切到 internal zone。# 把 eth1 永久切到 internal zone firewall-cmd --permanent --zoneinternal --change-interfaceeth1 # 把默认 zone 设置为 public避免新网卡随机落进 trusted firewall-cmd --set-default-zonepublic firewall-cmd --reload注意--change-interface对 NetworkManager 管理的网卡不一定永久生效最好用 nmcli 同步改一下网卡配置里的 zone 属性nmcli connection modify eth1 connection.zone internal nmcli connection up eth1接下来配置 public zone。先把默认的 ssh 服务删掉因为我们只允许办公网段访问 SSH。然后放行 http、https。最后加一条 rich rule允许办公网段访问 SSH。firewall-cmd --permanent --zonepublic --remove-servicessh firewall-cmd --permanent --zonepublic --add-servicehttp firewall-cmd --permanent --zonepublic --add-servicehttps firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address203.0.113.0/24 service namessh accept firewall-cmd --reload再配 internal zone。internal zone 默认放行 ssh、mdns、dhcpv6-client、samba-client 等我只需要保留 ssh把不需要的移除再放行 8080 端口。firewall-cmd --permanent --zoneinternal --remove-servicemdns firewall-cmd --permanent --zoneinternal --remove-servicedhcpv6-client firewall-cmd --permanent --zoneinternal --remove-servicesamba-client firewall-cmd --permanent --zoneinternal --add-port8080/tcp firewall-cmd --reload最后检查两份配置firewall-cmd --zonepublic --list-all firewall-cmd --zoneinternal --list-all我在实际操作中还会顺手看一眼生效的 nft 规则集确认配置真的加载到了内核nft list ruleset | grep -A 20 zone public这一步很多人会省但对排查很有用。firewalld 命令显示的是它自己视角的配置nft 显示的是内核实际规则两者应该一致。如果你发现内核规则里有什么多余的东西比如 Docker 写入的规则那就要小心了。3.3 重启与加载后的效果验证配置完以后不要直接走人至少做三轮验证。第一轮本机验证监听状态。Web 服务是否监听 80/443管理后台是否监听 8080SSH 是否监听 22。用ss -lntp一眼就能看到ss -lntp | grep -E :80|:443|:8080|:22第二轮从办公网段的一台机器测试 SSH、HTTP、HTTPS。办公网段里 SSH 和 Web 都应该通。如果 Web 通但 SSH 不通检查办公网段的源 IP 是不是真的在 203.0.113.0/24 里很多公司出口会有 NAT源地址不是规划中的办公网段这种情况要么调整网段要么改为允许公司出口 IP。第三轮从外部一个不该被放行的地址测试管理后台 8080。预期结果是被拒绝能通就是配置有问题。测试工具我习惯用 ncnc -vz 服务器公网IP 8080 # 预期输出类似 connect to ... port 8080 (tcp) failed: Connection refused 或无响应不要只依赖 ping。ping 走的是 ICMP防火墙对 ICMP 的策略和 TCP 端口策略不一定相同。下面专门讲故障排查。4. 双机热备场景下firewalld 该怎么配合4.1 双机热备不是防火墙自己会做而是要叠加高可用方案热词里经常看到防火墙双机热备、以上方案所用设备均为2套这类词大多出自硬件防火墙配置文档。但放在 Linux firewalld 场景下我的经验是firewalld 本身不具备故障切换能力它只是一个规则管理工具。真正做主备切换的是上层的高可用组件比如 keepalived、corosync/pacemaker或者云平台的浮动 IP。很多刚入门的人以为两台服务器同步配好 firewalld 规则就热备了其实不是。热备至少包含三件事一是 VIP虚拟 IP能在两台机器之间漂移二是业务进程在主备节点上都可用三是主备两个节点上的网络过滤规则保持一致。firewalld 只负责第三件事里的本地规则部分VIP 漂移要靠 keepalived业务探活要靠脚本两个都不归 firewalld 管。我建议把双机热备理解成共同承担一个逻辑 IP而不是两台机器都对外提供服务。正常情况下只有主节点持有 VIP备节点闲置。主节点挂了备节点立刻把 VIP 抢过来同时本机的 firewalld 规则早就准备好了所以流量切过来以后端口策略不变用户几乎无感知。4.2 keepalived firewalld 的联动规则keepalived 使用 VRRP 协议实现主备协商。VRRP 默认走 IP 协议号 112组播地址是 224.0.0.18。如果你把 firewalld 的 zone 规则收紧得很严格这组播流量会被挡掉主备节点之间互相看不到对方心跳就会出现两台都以为自己是主节点的情况脑裂随之而来。解决方法是放行 VRRP 协议。如果主备节点在同一个内网网段而且你把他们放在 internal 或 trusted zone可能本来就能通。但如果心跳网卡也归 public 管就必须显式放行。# 放行 VRRP 协议注意这里按 IP 协议号放行不是端口 firewall-cmd --permanent --zoneinternal --add-rich-rulerule protocol valuevrrp accept firewall-cmd --reload如果你的 VRRP 配置了单播比如使用unicast_peer指定对端地址那么更稳妥的写法是限制源地址firewall-cmd --permanent --zoneinternal --add-rich-rulerule familyipv4 source address192.168.10.2 protocol valuevrrp accept firewall-cmd --permanent --zoneinternal --add-rich-rulerule familyipv4 source address192.168.10.3 protocol valuevrrp accept firewall-cmd --reload另外还要确认业务服务的端口在备节点上也是放行的。很多人只在主节点用了--add-port8080/tcp备节点没配结果主备一切换VIP 漂到备节点以后8080 端口立刻不通。这是个非常低级的坑但我在客户现场见过不止一次。4.3 两套设备的规则同步与检查双机要做规则同步最简单的办法不是手动在两边敲同样的命令而是把主节点/etc/firewalld/zones/下所有 XML 文件直接同步到备节点然后 reload。先看主节点上有哪些 zone 配置ls -l /etc/firewalld/zones/常见的文件是public.xml、internal.xml、drop.xml这些。用 rsync 或者 scp 把整个目录同步过去scp /etc/firewalld/zones/*.xml 192.168.10.3:/etc/firewalld/zones/备节点上执行firewall-cmd --reload同步完以后我一般会跑一个 diff 脚本对比两台机器的 zone 配置和最终 nft 规则防止有人手工在备节点上临时加过规则。比如ssh 192.168.10.2 firewall-cmd --zonepublic --list-all --permanent /tmp/node1_rules.txt ssh 192.168.10.3 firewall-cmd --zonepublic --list-all --permanent /tmp/node2_rules.txt diff /tmp/node1_rules.txt /tmp/node2_rules.txt没有输出才是正常的。如果两个文件有差异十有八九是之前排查问题时有人直接在备节点上改了运行时规则没写永久配置也可能两边持久化规则不一致。统一规则以后再做切换测试才能保证主备切换不影响业务。5. 常见故障排查与避坑速查5.1 开启防火墙后 ping 不通 / 端口不通的排查顺序先说一个原则不到万不得已不要关防火墙来排查问题。关掉以后问题确实消失只能说明防火墙在挡流量但你不知道是哪条规则挡的也不知道有没有其他规则误伤。正确做法是按层级一步步缩小范围。第一步看主机有没有收到包。用 tcpdump 抓一下目标端口tcpdump -i eth0 tcp port 8080 -n -c 20如果完全抓不到包说明流量没到达主机问题在网络、路由或者云安全组跟 firewalld 无关。如果抓到了 SYN 包但客户端始终连接超时那才轮到 firewalld 背锅。第二步看 firewalld 当前状态firewall-cmd --state firewall-cmd --get-active-zones firewall-cmd --zonepublic --list-all重点确认目标端口或服务确实不在放行列表里。注意--list-all显示的是运行时配置如果你只加了--permanent却没有 reload它不会出现在运行时列表里外部流量自然进不来。这个操作顺序问题我会在下一节展开。第三步看内核里实际生效的规则。RHEL 9 这类新系统默认后端是 nftables所以用nft list ruleset查看。如果你是 RHEL 7 或早期的 CentOS可以看iptables -L -n -v。找一下有没有 REJECT/DROP 规则涉及目标端口。有时候 firewalld 没有直接拒绝但 Docker 或自定义脚本写了一条更靠前的 DROP 规则也能导致不通。第四步看服务本身是否监听在正确的接口和地址上。用ss -lntp确认不要只看 systemctl status。很多服务配置了只监听 127.0.0.1外部自然连不上这跟防火墙没关系。我在排查时最爱说的一句话就是别让防火墙给服务背锅。5.2 拒绝日志怎么开排查必备的 rich rule 技巧firewalld 默认的 drop 动作不写日志所以很多时候你开了防火墙以后流量被默默丢掉却没有任何记录可查。我的做法是在关键端口上主动加一条带日志的规则一旦有非法访问进来日志会明确告诉你哪个 IP 在什么时候被挡了。比如我要监控外部对 8080 的访问并拒绝来源不在白名单里的 IP可以在配置里加一条firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 port port8080 protocoltcp log prefixPORT8080-DROPPED: log levelwarning drop firewall-cmd --reload这条规则的意思是所有进入 public zone 的、目标是 8080/tcp 的包先记一条 warning 日志再执行 drop。日志会输出到系统日志里RHEL 系可以通过 journalctl 查看journalctl -k | grep PORT8080-DROPPED注意log levelwarning用的是系统日志的级别不是 firewalld 自己的等级概念。prefix 字段建议用大写且带业务标识方便 grep。日志规则有个副作用如果不限速日志量会非常大。攻击者扫描一次可能触发成千上万条日志把磁盘写满。我建议在 rich rule 里加limit value5/m让每分钟最多记录 5 条firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 port port8080 protocoltcp log prefixPORT8080-DROPPED: log levelwarning limit value5/m drop firewall-cmd --reload这样既能看到攻击来源又不至于日志爆炸。5.3 常见坑位速查表我把这几年遇到最多的几个问题整理成一张速查表。这张表不敢说覆盖全部场景但至少能帮你在公众号收藏夹里省下一个位置。现象可能原因排查/解决服务端口明明加了--permanentreload 后还是不通只加了永久配置但没 reload或者不加--permanent直接 reload 导致运行时规则丢失加规则时先--permanent写完统一--reloadping不通但业务端口能通云安全组或上游设备禁 ICMP不是 firewalld 的问题用tcpdump确认包是否到主机不要用 ping 做唯一连通性判断端口转发不生效没开ip_forward或没加 masquerade或 toaddr 不可达检查sysctl net.ipv4.ip_forward加--add-masquerade测试目标主机端口keepalived 主备脑裂VRRP 组播/单播被 firewalld 挡住--add-rich-rulerule protocol valuevrrp accept并确认心跳网卡 zone双机切换后服务不通备节点 firewalld 规则没有同步用 rsync/scp 同步/etc/firewalld/zones/*.xml然后 reloadDocker 映射端口对外网可访问Docker 直接写 iptables 规则绕过了 firewalld zone不要用-p映射到 0.0.0.0指定 127.0.0.1或用 docker network 前置网关rich rule 日志刷爆磁盘没加 limit 限速在 log 后加limit value5/mDocker 那条我再补充一句。如果你机器上既有 firewalld 又有 Docker建议至少理解 Docker 的 FORWARD 链优先级。Docker 服务启动时会往 iptables 的 DOCKER 链里插入规则它发布的端口在 nat 表里被 DNAT 到容器 IP流量可能根本不进你给 firewalld 配置的 INPUT 链。所以你不能说firewalld 里没放行 8080为什么 8080 还开着——先查iptables -t nat -L DOCKER看到 Docker 做了端口映射就很清楚了。这也是我坚持在每台服务器上线前写一份端口清单的原因文档比记忆靠谱。最后分享一个我自己常用的收尾习惯配置完一轮 firewalld 以后我习惯把变更后的完整规则导出来存成一份带日期的快照文件放在/root/firewalld-backup/。之后再改任何规则先 diff 一下当前和快照的差异确认不是误操作。这个习惯救过我很多次特别是半夜排查问题的时候一个diff就能看出上次加的那条 rich rule 是不是把来源地址写反了。firewalld 的命令本身容易掌握但真正让人头大的永远是规则很多、时间久了不知道哪条有用。所以不管你用什么管理工具定期把 zone 配置和 nft 规则集打快照绝对值得。