☰
firewalld防火墙配置实战:zone机制、端口转发与排查技巧
2026/10/8 8:57:17 网站建设 项目流程

刚做完一批 Linux 服务器的安全加固,其中一台业务机器就栽在防火墙规则上:明明按文档放通了 8080 端口,可外部就是访问不了,排查到最后发现是 firewalld 的 zone 理解错了,规则写到了默认 zone,而实际流量走的是另一个 zone。像这类问题在实际运维中非常典型,而大部分新人卡住的原因,不是命令记不住,而是没搞懂 firewalld 这套“区域+服务+运行时/持久化”的设计逻辑。

这篇文章我就以自己的实操经验为主线,把第 15 章“防火墙配置(firewalld)”从头到尾拆开讲清楚,内容包括:firewalld 的设计思路、zone 的用法、日常增删改查命令、NAT 与端口转发、图形化工具、配置持久化,以及我踩过的坑和对应的排查方法。无论你是刚接触 Linux 运维的新手,还是被 firewalld 规则折腾过的老手,这篇文章都值得收藏备用。

1. 理解 firewalld 的设计逻辑:zone、runtime 与 permanent 到底是怎么回事?

1.1 zone(区域)不是安全等级,而是“网络信任边界”

很多新手第一次看到 firewalld 的 zone 列表都会懵:public、trusted、internal、dmz、work、home——这些名字看起来像安全等级,但实际含义是“对某个网络连接的信任程度”。public默认认为网络里的其他主机都不可信,而trusted则完全信任网段内的所有主机。这套设计借鉴了企业网络把交换机端口划分到不同 VLAN 的思路,只不过 firewalld 把它搬到了主机内部。

默认情况下,firewalld 把网卡分配到publiczone,整台服务器只会放行很少的端口(通常是 ssh 和 dhcp 客户端)。在配置前,我建议先运行下面这条命令,看看当前所有 zone 的配置全局面貌:

firewall-cmd --get-default-zone firewall-cmd --list-all-zones

把两台都跑起来对比输出,你会发现每个 zone 的services、ports和masquerade状态都不一样。理解好 zone 并且养成“想清楚流量该进哪个 zone 再动手”的习惯,后面写规则就不容易乱。

1.2 runtime 与 permanent:临时规则和永久规则的边界

firewalld 有两个“配置面”:runtime(运行中的配置)和 permanent(永久配置)。直接用firewall-cmd --add-port=8080/tcp修改的是 runtime,机器重启或执行firewall-cmd --reload后就会丢失。要写入永久配置,必须加--permanent参数。

实际运维中,建议遵循“先临时放通验证,再写入永久配置”的顺序。比如新上线一个 Web 服务,先用--add-port=80/tcp临时放通,确认页面访问正常后,再执行--runtime-to-permanent一条命令把当前 runtime 配置全部固化,干净又避免重复敲命令。如果临时验证失败,直接--reload就能把规则全部撤掉,不会留下垃圾规则。

1.3 为什么内核里有 iptables 还要用 firewalld?

firewalld 本质上是一个管理 netfilter 规则的前端服务,它通过调用nftables(或老的iptables后端)来实现数据包过滤。它带来的最大价值,是把底层链式规则的复杂度封装成了“zone + service + rich rule”这种相对好理解的语言。

举个例子,你想放行 MySQL 的 3306 端口。纯 iptables 需要写:

iptables -A INPUT -p tcp --dport 3306 -j ACCEPT

在 firewalld 里,你只要:

firewall-cmd --permanent --add-service=mysql firewall-cmd --reload

后者还自动帮你处理了重载时的规则衔接问题——firewalld 的规则变更是在运行时热加载的,不像 iptables 那样容易被粗暴刷掉。理解这层关系,你就不会在“明明改了规则却不生效”这个问题上浪费太多时间。

2. 基础配置实操:从查状态到放行端口,一步步搭起你的访问规则

2.1 先搞清楚服务状态和当前规则

动手前,先确认 firewalld 到底在不在跑,状态输出里Active: active (running)和running缺一不可:

systemctl status firewalld firewall-cmd --state

如果输出not running,先启动并设置开机自启:

systemctl enable --now firewalld

日常检查规则集,最常用的是:

firewall-cmd --list-all

这条命令会展示当前默认 zone、放行的服务列表、端口列表、伪装状态和富规则。如果觉得不够直观,可以加--zone=dmz --list-all查看指定 zone 的完整规则。我一般会在改规则之前先执行一次,把规则快照留在终端里,方便后面滚动对比。

2.2 放行服务与端口:--add-service和--add-port的选择

firewalld 预定义了很多服务名,位于/usr/lib/firewalld/services/目录,每个服务对应一个 XML 文件。比如http服务对应放行 80/tcp,https对应 443/tcp,mysql对应 3306/tcp。使用服务名的好处是,你不用记住每个应用的端口号,而且服务变更时只需改一个地方。查看系统支持哪些服务:

firewall-cmd --get-services

但如果服务没有预定义(比如某个私有应用的端口是 9000),就直接按端口放行:

firewall-cmd --permanent --add-port=9000/tcp firewall-cmd --reload

这里有一个新手特别容易踩的坑:只写了端口号没写协议。firewalld 的端口规则必须指明tcp或udp,写成--add-port=9000会直接报错。另外,放行 UDP 时要格外谨慎——UDP 没有连接状态,规则一旦放错,攻击面会比 TCP 大得多。

2.3 移除规则的两种姿势:--remove与--remove-service

清理规则用--remove-port或--remove-service,要跟添加时的写法完全对应:

firewall-cmd --permanent --remove-port=9000/tcp firewall-cmd --permanent --remove-service=http

如果临时移除,不加--permanent,这样 reload 后规则又回来了。很多文档没写这一点,导致有人用--remove-port删了端口,reload 后端口又神奇地放开了,误以为是没生效。其实就是 runtime 和 permanent 的区别没搞透。

2.4 把某网段或网卡绑定到指定 zone

不同网段走不同信任策略,是 zone 的核心能力。比如把内网网卡ens160绑定到internalzone,把外网网卡ens192绑定到publiczone:

firewall-cmd --permanent --zone=internal --change-interface=ens160 firewall-cmd --permanent --zone=public --change-interface=ens192 firewall-cmd --reload

绑定后,内网访问者默认就能获得internalzone 里放行的服务,公共 zone 的规则只影响外网访问。这里建议先用firewall-cmd --get-active-zones确认绑定结果,再做下一步。

2.5 配置永久生效的完整套路

下面是一条完整的操作链路,供直接抄作业。场景:服务器有两个网卡,内网走internal,公网走public,需要放行 8080 端口给业务、放行 443 给 HTTPS,同时限制公网只能访问 443:

# 1. 查看当前状态 systemctl status firewalld # 2. 绑定网卡到 zone firewall-cmd --permanent --zone=internal --change-interface=ens160 firewall-cmd --permanent --zone=public --change-interface=ens192 # 3. internal 放行 8080 和 http firewall-cmd --permanent --zone=internal --add-port=8080/tcp firewall-cmd --permanent --zone=internal --add-service=http # 4. public 只放行 https firewall-cmd --permanent --zone=public --add-service=https # 5. 重载生效 firewall-cmd --reload # 6. 验证 firewall-cmd --zone=internal --list-all firewall-cmd --zone=public --list-all

这套流程走下来,既完成了需求,又保证了最小权限,是生产环境的标准姿势。

3. 富规则与高级配置:按来源 IP、协议做精细化管控

3.1 为什么需要富规则?

单纯的 service 和 port 只能按“目标端口”放行,做不到“只允许某个 IP 访问某个端口”。比如一台数据库服务器,只希望办公网段的192.168.10.0/24能连 3306,其他 IP 一律拒绝。这种需求,用富规则(rich rule)就能精确控制,而且语法看着复杂,实际拆解后非常简单。

3.2 富规则的完整语法拆解

通用格式如下:

firewall-cmd [--zone=zone] --add-rich-rule='rule [family="ipv4|ipv6"] [source address="IP或网段" [invert="true|false"]] [destination address="IP或网段"] service name="服务名" | port port="端口" protocol="协议" [log [prefix="日志前缀"] [level="日志级别"]] [audit] [accept|reject|drop|mark] '

这个语法不难记,只要抓住几个关键点:rule开头,然后指定source、destination,接着写service或port,最后是动作accept/drop/reject。family 表示 IP 协议族,只在桥接等特殊场景下必须显式设置,常规 IPv4 环境不写也行。

3.3 实际场景:只允许办公网段访问 SSH

比如把 SSH 从默认 22 改到 22022,同时只允许10.0.0.0/8访问:

firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="22022" protocol="tcp" accept' firewall-cmd --reload

注意顺序:drop和accept的求值顺序与规则写入顺序有关,firewalld 会按规则的从前往后顺序匹配,后写的规则优先。实际配置时,我会把“允许”规则写在“拒绝”规则之前,避免被更早的拒绝规则提前命中。

3.4 富规则优先级和与普通规则的配合

富规则的优先级高于普通--add-port规则。也就是说,即使你先放行了 3306 端口,后面又写了一条来源为10.0.0.0/8拒绝访问 3306 的富规则,后者也会优先生效。这给工作带来的启示是:不要让富规则和简单端口规则表达互相矛盾的意图,尽量把同类策略集中到同一种规则类型中,否则排障时会很痛苦。

富规则还支持日志记录,比如要记录来自某个 IP 的所有被拒绝的 SSH 尝试:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100" service name="ssh" log prefix="SSH_BLOCK: " level="info" drop'

日志会打到系统的/var/log/messages,配合journalctl -f可以实时观察攻击来源。

4. NAT、端口转发与伪装:让 firewalld 帮你做流量跳板

4.1 masquerade(伪装)到底解决什么问题?

内网机器想通过这台服务器上网,或者想让外部访问内网服务,核心是两个字:转发。firewalld 里打开masquerade就是开启源地址伪装,效果类似 iptables 的SNAT,它会把从这里出去的所有数据包的源 IP 改写成这台服务器的出口 IP。

在企业场景里这很常见:一台 Linux 服务器作为网关,内网网段172.16.1.0/24的机器把默认网关指到这台服务器,然后服务器上执行:

firewall-cmd --permanent --zone=internal --add-masquerade firewall-cmd --reload

别忘了开启内核转发:

sysctl -w net.ipv4.ip_forward=1 echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf

很多人以为开了 masquerade 就能转发,实际上内核转发参数不开,包根本不会往上层走,只是被丢弃。这个顺序问题至少坑过我三次,务必记住。

4.2 forward-port:把公网端口映射到内网主机

端口转发(port forwarding)是 NAT 的核心能力。比如内网有台 Web 服务器 IP 是192.168.1.10,你想让外网通过这台防火墙的 8080 端口访问它,配置如下:

firewall-cmd --permanent --zone=public --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=192.168.1.10 firewall-cmd --reload

语法拆解:port是外网监听端口,proto是协议,toport是内网目标端口,toaddr是内网目标 IP。如果只想转发到本地端口(比如本机 80 转发到 8080),toaddr可以省略。这条命令在网络模拟器环境里尤其常用——比如通过 ensp 搭了一套企业网络拓扑,核心交换机负责三层转发,但最终要对外发布的业务服务器上,一样跑着 firewalld 做端口映射。很多网络工程师习惯只在路由器上做 NAT,实际生产环境里,很多场景是防火墙服务器配合硬件防火墙一起完成内外网交付的。

再补充一个关键点:--add-forward-port并不会自动开启 masquerade,但在某些场景下需要配合开启。如果目的地址在本机(端口转发到本机其他服务),不需要 masquerade。如果转发到内网其他机器,则建议同时开启 masquerade,否则回程包可能因为路由问题丢失。我踩过一次:转发配置完全正确,但客户端一直超时,最后在防火墙上抓包才发现回包根本没回到这台机器上。

搭配永久配置的完整示例:

firewall-cmd --permanent --zone=public --add-service=https firewall-cmd --permanent --zone=public --add-forward-port=port=8443:proto=tcp:toport=443:toaddr=192.168.1.10 firewall-cmd --permanent --zone=public --add-masquerade firewall-cmd --reload

4.3 HTTP/HTTPS 反向代理与端口转发的选型思路

有些场景里,你其实不一定需要 firewalld 的端口转发。比如应用层已经用了 Nginx 做反向代理,那你只需把 80/443 放给 Nginx,由 Nginx 决定转发到哪个后端。这种情况下再叠加 firewalld 的forward-port反而会造成多一层跳转、多一次带宽损耗,还让故障定位变麻烦。

我的经验是三层分流:“网络层能解决的(如按 IP 限制)交给 firewalld;应用层能解决的(如路径分发、超时控制)交给 Nginx;需要改变端口或 IP 的场景才用端口转发。” 遵循这个原则,你的防火墙规则和 Web 配置都会清爽很多。

5. 配置持久化、备份迁移:把防火墙规则当成代码来管理

5.1 推荐的“临时→验证→固化”三步法

生产环境最忌讳一上来就写--permanent,因为一旦规则写错,重载后你可能直接把自己关在服务器外面。正确姿势是:

# 第一步:临时放通 firewall-cmd --add-port=8080/tcp # 第二步:验证业务正常 curl http://127.0.0.1:8080/health # 第三步:固化 firewall-cmd --runtime-to-permanent

--runtime-to-permanent会把当前运行时配置全部写入永久配置文件。使用前建议先执行firewall-cmd --list-all检查一遍,确认没有临时调试用的垃圾规则,再固化。

5.2 备份与迁移:防止服务器重建后规则丢失

每次大改规则之前,我都习惯把配置目录打包备份:

mkdir -p /backup/firewalld cp -r /etc/firewalld /backup/firewalld/$(date +%F)

迁移到新机器时,可以直接把整个/etc/firewalld目录拷过去,然后重载:

systemctl restart firewalld

注意:/etc/firewalld里保存的是用户自定义配置,系统默认配置在/usr/lib/firewalld,两边不要混淆。自己手动改过/etc/firewalld/zones/public.xml的话,迁移时重点盯这个目录。

5.3 用命令行工具批量处理规则的实用技巧

大量重复规则(比如放行 1000 个端口)不要一条条手敲,可以直接写一个循环脚本:

for port in $(seq 8000 9000); do firewall-cmd --permanent --add-port=${port}/tcp done firewall-cmd --reload

但我不建议真的放行 1000 个端口,除非业务确实需要。更合理的做法是把业务拆分成服务文件(/etc/firewalld/services/myapp.xml),然后通过--add-service=myapp一键放行一组端口,后续维护只需要改这个 XML 文件。服务文件示例:

<?xml version="1.0" encoding="utf-8"?> <service> <short>MyApp</short> <description>My application ports</description> <port protocol="tcp" port="8080"/> <port protocol="tcp" port="8443"/> </service>

放到/etc/firewalld/services/后执行:

firewall-cmd --permanent --add-service=myapp firewall-cmd --reload

这种方式比散落一堆--add-port规则要清晰得多,审计时也是看文件,比翻命令历史舒服。

5.4 使用 firewall-config 图形工具与命令行的分工

如果服务器带了图形界面,firewall-config可以让你用鼠标完成大部分配置。它有两个 Tab:运行时和永久,切换时界面会显著变灰提示你。但我的建议是:生产服务器尽量用命令行,因为脚本化、可审计、可复现;图形工具更适合学习阶段理解 zone 和策略之间的关系。用图形工具改完,实际生效的还是那套底层 XML,所以不会和命令行冲突。

6. 常见问题与排查技巧实录:别再被“规则不生效”折磨了

6.1 规则不生效的五层排查法

当服务无法访问时,不要只看 firewalld。我通常按下面这个顺序排查:

层级命令/工具检查内容
应用层ss -lntp确认端口是否真的在监听
firewalld 层firewall-cmd --list-all确认规则是否已加载
nftables 层nft list ruleset确认底层规则链是否匹配预期
路由层ip route、ping确认回程路由是否正常
物理层ping、telnet确认链路和防火墙设备未拦截

很多“firewalld 配置了却没用”的案例,最后都发现是服务根本没启动、监听端口不对、或者策略落在了错误的 zone。这五层逐层往下查,基本能定位 99% 的问题。

6.2 排查 zone 分配问题的两个核心命令

firewall-cmd --get-active-zones ip addr show

前者告诉你网卡当前属于哪个 zone,后者告诉你网卡 IP 是否与你设想的一致。有一次我在publiczone 里放行了端口,但业务流量从ens192出去,而ens192被分配到了internalzone,自然不生效。改完网卡绑定后问题立刻解决。

另外要警惕“默认 zone”和“接口绑定 zone”不一致的情况。--get-default-zone返回的是新接口进入时的默认区域,但如果接口被显式绑定到某个 zone,后者的优先级更高。访问某个端口前,先想明白它从哪个接口进来,再决定检查哪个 zone。

6.3 重载规则导致已有连接断掉怎么办?

firewall-cmd --reload会重新加载所有规则,这可能导致已经建立的连接被中断。对于数据库这类长连接服务,影响会比较明显。解决办法是使用--reload以外的另一种方式:如果只是修改服务或端口,firewalld 通常会尽力维持已有连接状态;但如果有富规则变了,连接还是会被切断。更稳妥的做法是使用--complete-reload之外的方案:不要随便用--complete-reload,它会彻底清空所有状态。生产环境里宁可用--reload然后重新建连,也不要为了“彻底”而把正在运行的业务全踢下线。

真的遇到了连接中断,最快的恢复方式就是让客户端重连。但对于无法接受断连的关键进程,建议把规则变更安排在业务低峰期,并且先备份旧规则。

6.4 富规则“先拒绝后允许”的陷阱

富规则的求值是按写入顺序的,但很多人以为是“最后一条生效”,实际是“按列表从上到下的顺序依次匹配”。如果你先写了一条drop的富规则,再写一条accept的富规则,那么后写的accept不会拯救先前被drop的流量,因为第一条已经命中并结束了流程。要避免这个问题,把更精确的accept规则放在前面,把兜底的drop规则放在后面。

6.5 常用工具确认规则是否真的生效

nft list ruleset能直接看到内核态规则,这条命令查出来如果与你期望的不一致,那就是 firewalld 没有正确加载。这时候先执行:

firewall-cmd --reload systemctl status firewalld

如果还是不生效,再检查/etc/firewalld/firewalld.conf里的FirewallBackend配置。有些系统装的 firewalld 版本较老,或者手工动过 iptables 服务,会与 firewalld 冲突。此时不要双轨混跑,统一用 firewalld 管理,把iptables和ip6tables服务设为 disabled。

6.6 从网络设备到 Linux 主机:锐捷、ensp 与 firewalld 的联动思考

最近和培训机构的一位朋友聊起,很多学网络的人在用ensp做华为路由器/防火墙的实验,比如配置 Web 登录、配置安全策略。这类硬件防火墙的配置思路和 firewalld 有相通之处:都有“区域/安全域”的概念,都强调策略放行的最小化,都需要考虑 NAT、路由和会话状态。但两者也有明显差异:华为 USG 防火墙的策略是配置在接口/安全域上的,firewalld 的 zone 则更偏主机内部软件定义。

实际企业交付中,网络设备(锐捷、华为等)负责边界防护和 NAT 映射,Linux 服务器上的 firewalld 负责主机层防护,两者是互补关系。在模拟器环境里做完防火墙策略实验后,建议把同样的“源 IP 限制、目标端口限制、最终动作”在 firewalld 上复现一遍。你会更深刻地体会到:策略的本质是“谁在什么条件下可以访问什么”,无论底层是硬件芯片还是 netfilter 框架,思路完全一致。

6.7 通过日志观察被拦流量

排查被拦截的流量,日志是重要依据。firewalld 的 drop 动作默认不一定打日志,需要显式在富规则里加log。日常运维我会对可疑 IP 加一条带日志的 drop 规则:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" log prefix="DROP_SCAN: " level="info" drop'

然后实时观察:

journalctl -f | grep DROP_SCAN

根据日志前缀快速确认是不是自己的规则拦的,还是服务本身异常,这能省下大量排查时间。

7. 实操过程复盘与后续建议

在这个环节,我把自己最近一次线上防火墙调整的完整过程复述一遍。某台 Nginx 反向代理服务器,业务要求:办公网段可以访问 80/443 和 22 端口,其他来源只能访问 443,另外要把两内网服务的端口映射到这台公网服务器上来。我的执行记录如下:

# 1. 查看现有配置 firewall-cmd --list-all # 2. 设置默认 zone 为 drop,拒绝一切未明确放行的流量 firewall-cmd --set-default-zone=drop # 3. 放行办公网段 SSH firewall-cmd --permanent --zone=drop --add-rich-rule='rule family="ipv4" source address="192.168.20.0/24" service name="ssh" accept' # 4. 放行所有来源的 443 firewall-cmd --permanent --zone=drop --add-service=https # 5. 放行办公网段 80(内部访问,可能走 HTTP) firewall-cmd --permanent --zone=drop --add-rich-rule='rule family="ipv4" source address="192.168.20.0/24" service name="http" accept' # 6. 配置端口转发 firewall-cmd --permanent --zone=drop --add-forward-port=port=8443:proto=tcp:toport=443:toaddr=10.10.1.88 firewall-cmd --permanent --zone=drop --add-forward-port=port=2222:proto=tcp:toport=22:toaddr=10.10.1.99 # 7. 开启内核转发 sysctl -w net.ipv4.ip_forward=1 echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf # 8. 开启伪装 firewall-cmd --permanent --zone=drop --add-masquerade # 9. 重载并验证 firewall-cmd --reload firewall-cmd --list-all

这套配置的思路是“默认全拒、按需放行”,对公网服务尤其合适。因为默认 zone 设置为 drop,所有未匹配流量都会被丢弃,攻击面被压缩到最小。这里需要注意:--set-default-zone=drop只影响“新的接口/连接”会被分配到 drop zone,但已经有网络连接或已经绑定 zone 的接口不会自动切换,必须逐个接口重新--change-interface或重启。这是我在验证时踩的小坑,特意标注出来。

这个过程中我犯过的一个错误是:忘记开启ip_forward,导致端口转发的数据包到了内核就丢了。排查时用tcpdump -i any port 8443能抓到入方向的包,但完全没有发出到内网,顺着链路往下排查才找到根因。

做这类改动时,强烈建议你保留一个“逃生窗口”:开一个临时放行的富裕 IP 来源规则(比如办公网段 full access 或至少保留 22 端口),避免规则一重载就把自己锁在门外。如果真的运气不好把自己锁在外面,可以联系机房或带外管理,通过控制台登录把 firewalld 停掉或恢复默认配置。

8. 最后分享两个实用习惯

firewalld 用到现在,我觉得真正提升效率的不是某个命令,而是两个习惯。

第一个习惯是“配置即代码”。我会把防火墙规则按服务器角色整理成单独的 shell 文件,比如web-firewall.sh、db-firewall.sh,放在/root/scripts/下,每次改动后更新脚本,再手动执行或走自动化平台下发。这样无论是审计还是新机器初始化,都能快速复现同一套规则,避免凭记忆敲命令造成两台机器规则不一致。

第二个习惯是“变更前留快照,变更后留验证”。快照不止是cp -r /etc/firewalld,还要负责记录当时的--list-all输出。变更后,我会把两条命令的 diff 拿出来看一遍,核对“预期变更”和“实际变更”是否一致。这个做法看起来很笨,但在多人维护的环境中,真的能减少大量来回扯皮。

如果你正在学习华为设备的防火墙配置,或者刚从网络模拟器环境切到真实 Linux 服务器运维,我的建议是:先花一个下午把 firewalld 的六个 zone 用意和一个 zone 的配置完整过一遍,再回到模拟器里对比一下,你会发现从网络设备到主机防火墙的抽象层次虽然不同,但安全策略的灵魂完全一样。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询