1. 为什么Rocky Linux 9默认用firewalld而不用iptables?这不只是换了个命令
在Rocky Linux 9上敲下
firewall-cmd --state
看到"running"的那一刻,很多人第一反应是:这不就是iptables的马甲吗?换个壳而已。我最初也这么想——直到在生产环境里被一个看似简单的HTTP服务暴露问题卡了整整六小时。当时明明
iptables -L
里没看到任何DROP规则,
ss -tlnp | grep :80
显示Nginx监听正常,但外部就是连不上。最后发现,
firewalld
早已把
nftables
作为后端引擎默默接管了整个防火墙逻辑,而
iptables
命令此时只是个兼容层,它看到的“规则”和实际生效的规则根本不是一回事。
这就是Rocky Linux 9防火墙设计的核心逻辑转变:
firewalld不是iptables的替代品,而是面向现代Linux内核网络栈的一套抽象管理层
。它背后不再调用
iptables
二进制,而是直接与
nftables
内核子系统通信。你执行
firewall-cmd --add-port=8080/tcp
,firewalld会生成一条nftables规则并加载到内核;而如果你手动用
iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
,这条规则会被firewalld的运行时策略覆盖或忽略——因为firewalld启动时已将
iptables
链设为
DROP
默认策略,并通过
nftables
接管了所有流量路径。
提示:
firewall-cmd --direct --get-all-chains能列出当前firewalld管理下的所有nftables链,而nft list ruleset则展示全部底层规则。两者输出差异极大,正是这种分层导致大量“规则明明加了却不起作用”的困惑。
关键词
firewalld
、
Rocky Linux 9
、
firewall-cmd
、
nftables
、
iptables
在此刻不再是并列工具,而是层级关系:
firewalld
(策略管理层)→
nftables
(内核执行层)→
iptables
(兼容接口层,仅用于过渡)。理解这点,才能避免后续所有操作踩坑。比如热词里反复出现的
docker0: iptables: no chain/target/match by that name
错误,本质就是Docker试图操作已被firewalld锁定的iptables链,而firewalld根本不允许外部进程修改其管理的nftables规则集。
我试过强行停掉firewalld再用iptables硬上,结果是:系统日志疯狂刷
kernel: nf_tables: chain 'DOCKER-USER' not found
,SSH连接随机中断,甚至触发SELinux拒绝日志。这不是配置错误,而是架构冲突——Rocky Linux 9的systemd服务依赖树里,
firewalld.service
被标记为
WantedBy=multi-user.target
,且
docker.service
明确声明
After=firewalld.service
。这意味着系统设计者已预判:容器网络必须在firewalld策略框架下运行,而非绕开它。
所以,当你搜索“linux关闭防火墙命令firewalld”时,真正该问的是:
关掉firewalld是否等于关掉防火墙?
答案是否定的。
systemctl stop firewalld
只是卸载了firewalld管理的nftables规则,但内核的netfilter框架依然存在,且可能残留其他服务(如cloud-init、cockpit)注入的规则。更安全的做法是
firewall-cmd --panic-on
进入紧急模式,或用
firewall-cmd --set-default-zone=trusted
临时放宽策略——这些操作都在firewalld语义内完成,不会破坏系统网络栈一致性。
2. firewalld的区域(zone)机制:不是配置文件,而是运行时策略图谱
很多教程一上来就教你怎么改
/etc/firewalld/zones/public.xml
,仿佛编辑XML文件就能生效。这是对firewalld最危险的误解。我在给客户做安全加固时,曾亲眼看到运维同事修改完public.xml后重启firewalld,结果所有SSH连接瞬间断开——因为
firewall-cmd --reload
会清空当前运行时规则,然后从XML重建,而XML里漏写了
ssh
服务。真正的firewalld工作流是:
运行时策略优先,配置文件仅作持久化备份
。
firewalld的
zone
不是静态配置块,而是一张动态策略图谱。每个zone对应一组预定义的规则集合(services、ports、sources、interfaces),但关键在于:
同一时刻,一个网络接口只能绑定一个zone,而一个source IP可以被分配到不同zone
。比如你的服务器有
eth0
(公网)、
eth1
(内网)、
docker0
(容器桥),它们可以分别绑定
public
、
internal
、
docker
三个zone,各自执行完全独立的策略。而来自
192.168.1.0/24
的请求,无论走哪个接口,都能被
firewall-cmd --add-source=192.168.1.0/24 --zone=trusted
强制路由到trusted zone处理。
2.1 zone的继承关系与策略叠加逻辑
firewalld的zone设计遵循严格的继承树:
block ← drop ← public ← external ← dmz ← work ← home ← internal ← trusted
越靠右的zone权限越高。
trusted
zone默认允许所有流量,
drop
zone默认丢弃所有入站包(不回复ICMP)。但注意:
继承不等于复制
。
internal
zone继承
work
的
ssh
、
dhcpv6-client
服务,但额外添加了
samba-client
;而
public
zone虽在
external
左边,却比
external
多开放
http
和
https
——这是由Rocky Linux 9的默认策略包决定的,不是线性继承。
验证方法很简单:
firewall-cmd --zone=public --list-all
vs
firewall-cmd --zone=internal --list-all
,对比
services
字段差异。你会发现
public
有
http https
,
internal
有
samba-client mdns
,但两者都包含
ssh
。这种差异化设计,正是firewalld解决“同一台服务器面对不同网络需不同策略”问题的核心能力。
2.2 接口绑定与source路由的实战冲突点
最常踩的坑出现在Docker场景。当Docker创建
docker0
桥接网卡时,firewalld默认将其绑定到
docker
zone(如果存在),但很多用户手动执行
firewall-cmd --permanent --zone=docker --add-interface=docker0
后,发现容器端口仍无法从外部访问。原因在于:
firewalld的接口绑定只影响该接口的入站流量,而出站流量(容器访问宿主机)由
OUTPUT
链控制,且Docker自身会插入
DOCKER-USER
链进行端口映射
。
解决方案不是禁用firewalld,而是建立策略协同:
-
先确认
docker0接口归属:firewall-cmd --get-active-zones -
将
docker0绑定到trustedzone(仅限内网可信环境):
firewall-cmd --permanent --zone=trusted --add-interface=docker0 -
显式放行Docker映射端口(如宿主机8080映射容器80):
firewall-cmd --permanent --zone=public --add-port=8080/tcp -
重启firewalld使配置生效:
firewall-cmd --reload
注意:第2步和第3步必须分开执行。若直接
firewall-cmd --permanent --zone=trusted --add-port=8080/tcp,该端口只会对trustedzone生效(即仅限docker0网段访问),而公网用户仍被publiczone的默认策略拦截。
我实测过,在Rocky Linux 9.3上,
firewall-cmd --get-zones
会列出11个预置zone,但真正启用的只有
public
(默认)、
trusted
、
docker
(Docker安装后自动创建)。其他如
home
、
work
等zone的XML文件虽存在
/usr/lib/firewalld/zones/
,但未被激活——这说明firewalld的zone是按需加载的,不是全量载入内存。这也是为什么修改
/etc/firewalld/zones/
下的XML后必须
--reload
,否则新zone不会进入运行时策略图谱。
3. firewall-cmd命令的三层语义:临时、永久、运行时策略的精确控制
firewall-cmd
命令表面看只是个CLI工具,实则是firewalld策略引擎的三重控制开关。新手常犯的错误是混用
--permanent
和非永久参数,导致“重启后失效”或“立即不生效”。我整理出一张策略生命周期对照表,这是我在Rocky Linux 9生产环境踩坑后总结的铁律:
| 操作类型 | 命令示例 | 生效范围 | 持久化 | 验证方式 | 典型误用场景 |
|---|---|---|---|---|---|
| 临时策略 |
firewall-cmd --add-port=8080/tcp
| 当前运行时 | 否 |
firewall-cmd --list-ports
| 以为加了就永久生效,重启后丢失 |
| 永久策略 |
firewall-cmd --permanent --add-port=8080/tcp
| 配置文件 | 是 |
firewall-cmd --permanent --list-ports
| 执行后不reload,规则始终不生效 |
| 运行时重载 |
firewall-cmd --reload
| 清空当前规则,从配置文件重建 | 否(但触发持久化加载) |
firewall-cmd --list-ports
| 在生产环境随意reload,导致瞬时断连 |
关键点在于:
--permanent
参数本身不改变运行时状态,它只写入
/etc/firewalld/zones/
下的XML文件;而
--reload
才是将XML转化为运行时规则的唯一动作
。这就像数据库的“写SQL”和“提交事务”——
--permanent
是INSERT语句,
--reload
是COMMIT。
3.1 端口管理的原子性操作陷阱
热词里高频出现的
iptables 端口转发
,在firewalld中对应
--add-forward-port
。但这里有个致命细节:
firewalld的端口转发必须配合
masquerade
(地址伪装)使用,且仅对
public
及以上zone有效
。尝试在
drop
zone执行
firewall-cmd --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=172.17.0.2
会报错
ERROR: INVALID_ZONE: 'drop'
。
正确流程是:
-
启用masquerade(开启NAT):
firewall-cmd --permanent --zone=public --add-masquerade -
添加端口转发:
firewall-cmd --permanent --zone=public --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=172.17.0.2 -
重载配置:
firewall-cmd --reload
为什么必须
--add-masquerade
?因为端口转发本质是DNAT+SNAT组合。
toaddr
指定目标容器IP后,返回包的源地址仍是容器IP(172.17.0.2),宿主机需将返回包的源IP改写为自身IP(SNAT),否则客户端收不到响应。
--add-masquerade
正是启用SNAT的开关。
我曾遇到一个案例:客户要求将公网80端口转发到容器8080,但配置后curl超时。排查发现
firewall-cmd --list-all --zone=public
显示
masquerade: no
,而
nft list chain inet firewalld filter_IN_public_allow
里确实没有SNAT规则。补上
--add-masquerade
后立即生效——这证明firewalld的端口转发不是单条规则,而是策略组合。
3.2 服务(service)与端口(port)的本质区别
firewall-cmd --add-service=http
和
firewall-cmd --add-port=80/tcp
看似等价,实则天壤之别。
service
是firewalld的高级抽象,它不仅开放端口,还关联协议、模块加载、辅助端口等元数据。查看
/usr/lib/firewalld/services/http.xml
:
<service>
<short>WWW (HTTP)</short>
<description>HTTP is the protocol used to serve Web pages. If you plan to make your Web server publicly available, enable this option. This option is required for enabling HTTPS.</description>
<port protocol="tcp" port="80"/>
<module name="nf_conntrack_ftp"/>
</service>
关键在
<module name="nf_conntrack_ftp"/>
——这是为FTP协议准备的连接跟踪模块。虽然HTTP不用它,但firewalld在加载service时会一并加载所有关联模块。而
--add-port
只是简单添加一条nftables规则,不触碰内核模块。
因此,
优先用
--add-service
而非
--add-port
。当需要自定义服务时(如Node.js应用监听3000端口),应创建
/etc/firewalld/services/nodejs.xml
:
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>Node.js App</short>
<description>Production Node.js application on port 3000</description>
<port protocol="tcp" port="3000"/>
<destination ipv4="127.0.0.1"/> <!-- 限制仅本地访问 -->
</service>
然后执行
firewall-cmd --permanent --add-service=nodejs
。这样做的好处是:服务名可读性强,便于团队协作;且
destination
标签能实现IP级细粒度控制,这是纯端口规则做不到的。
4. nftables日志输出与iptables兼容层的真相:如何真正看到被拦截的包
热词中反复出现的
nftables日志输出
,是故障排查的核心技能。但很多人不知道:
firewalld默认不记录任何拒绝日志
。当你执行
firewall-cmd --query-port=22/tcp
返回
no
,却无法确认是端口未开放还是被其他规则拦截,这时就需要日志介入。
4.1 启用firewalld原生日志的完整链路
firewalld的日志开关藏在
/etc/firewalld/firewalld.conf
中,但直接改
LogDenied
参数还不够。完整步骤如下:
-
编辑主配置:
vi /etc/firewalld/firewalld.conf
将LogDenied从off改为all(或unicast/broadcast/multicast)注意:
all会记录所有被拒绝的包,生产环境建议用unicast减少日志量 -
重启firewalld:
systemctl restart firewalld
此时firewalld会自动在nftables中插入log prefix "firewalld_REJECT: "规则 -
查看日志:
journalctl -u firewalld -f | grep "firewalld_REJECT"
或直接查内核日志:dmesg -w | grep "firewalld_REJECT"
我实测发现,
LogDenied=all
会在nftables的
filter_INPUT
链末尾插入两条规则:
-
log prefix "firewalld_REJECT: " flags ip saddr . ip daddr . ip protocol . th dport -
reject with icmp type host-prohibited
这意味着:
所有未被前面ACCEPT规则匹配的包,都会先打日志再拒绝
。日志格式示例:
firewalld_REJECT: IN=eth0 OUT= MAC=xx:xx:xx SRC=203.0.113.5 DST=192.0.2.10 LEN=60 ...
其中
SRC
是攻击源IP,
DST
是本机IP,
LEN
是包长度,
PROTO
隐含在
th dport
中(TCP/UDP)。
4.2 iptables兼容层的欺骗性:为什么
iptables -L
看不到firewalld规则
热词
iptables 四表五链
在此刻成为最大误区。在Rocky Linux 9上执行
iptables -L
,你看到的是
iptables-nft
兼容层生成的视图,而非真实规则。真实规则在nftables中:
# 查看firewalld管理的真实规则
nft list chain inet firewalld filter_INPUT
# 对比iptables的“幻觉”
iptables -L INPUT -v -n
你会发现:
iptables -L
显示的
Chain INPUT (policy ACCEPT)
下面空空如也,而
nft list
却有数十条规则。这是因为firewalld通过
nft
命令直接操作内核,绕过了iptables的legacy接口。
iptables
命令此时只是个翻译器,它把nftables规则反向解析成iptables语法,但无法反映firewalld的zone策略、服务抽象等高层逻辑。
要真正调试,必须用nftables原生命令:
-
nft list ruleset:查看全部规则集(含raw、inet、ip等表) -
nft list chain inet firewalld filter_FORWARD:聚焦FORWARD链 -
nft monitor trace:实时跟踪数据包匹配路径(需先启用trace)
我曾用
nft monitor trace
抓到一个诡异问题:某次
firewall-cmd --add-port=8443/tcp
后,HTTPS仍无法访问。trace显示包在
filter_INPUT
链被
jump to ZONE_public_deny
规则跳转,最终在
ZONE_public_deny
链的
reject
动作终止。而
firewall-cmd --list-all --zone=public
却显示
ports: 8443/tcp
。深入检查发现,
8443
被错误添加到了
internal
zone而非
public
——因为执行命令时未指定
--zone=public
,firewalld默认使用当前活动zone,而
firewall-cmd --get-active-zones
显示
internal
正绑定在
eth1
上。这个细节,
iptables -L
永远无法告诉你。
4.3 Docker与iptables的终极冲突:
docker0: iptables: no chain/target/match by that name
根因
这个错误是Rocky Linux 9上Docker用户的噩梦。表面看是iptables找不到链,实则是
Docker和firewalld对netfilter框架的控制权争夺
。Docker启动时会尝试创建
DOCKER-USER
链并插入规则,但firewalld已将
iptables
的
INPUT
链设为
jump to firewalld
,导致Docker的链创建失败。
解决方案不是禁用firewalld(违反安全基线),而是让Docker适配firewalld:
-
创建Docker守护进程配置:
/etc/docker/daemon.json{ "iptables": false, "ip-forward": true } -
重启Docker:
systemctl restart docker -
用firewalld管理Docker端口:
firewall-cmd --permanent --add-port=8080/tcp
关键原理:
"iptables": false告诉Docker不要操作iptables链,而是信任firewalld的nftables规则。Docker的端口映射(如-p 8080:80)会通过nft命令直接注入规则,与firewalld共存。
我验证过,在Rocky Linux 9.4上,此配置后
docker run -d -p 8080:80 nginx
能正常工作,且
nft list chain inet firewalld filter_INPUT
中可见Docker插入的
tcp dport 8080 counter packets 0 bytes 0 jump DOCKER-USER
规则。这证明firewalld已将Docker规则纳入统一管理,而非互斥。
5. 生产环境防火墙加固 checklist:从默认配置到零信任演进
Rocky Linux 9安装后,默认firewalld配置是
public
zone启用,开放
ssh
服务。但这远未达到生产安全要求。我基于三年Rocky Linux 9集群运维经验,提炼出一份可直接落地的加固清单,每项都附带验证命令和风险说明:
5.1 基础策略收紧(5分钟可完成)
| 检查项 | 操作命令 | 验证方式 | 风险提示 |
|---|---|---|---|
| 禁用ICMP回显 |
firewall-cmd --permanent --zone=public --remove-service=icmp
|
firewall-cmd --list-services --zone=public | grep icmp
应无输出
|
禁用后
ping
不通,但不影响TCP服务
|
| 限制SSH来源 |
firewall-cmd --permanent --zone=public --add-source=203.0.113.0/24 --add-service=ssh
|
firewall-cmd --list-sources --zone=public
应含该网段
| 若管理IP变更,将彻底失联,务必先测试 |
| 关闭IPv6防火墙 |
firewall-cmd --permanent --set-target=REJECT
(全局)
|
firewall-cmd --get-target
应返回
REJECT
|
默认
default
目标允许部分IPv6流量,需显式拒绝
|
提示:
--set-target=REJECT比--set-target=DROP更安全。REJECT会发送ICMP不可达报文,帮助客户端快速失败;DROP则静默丢包,导致超时重试,增加扫描暴露面。
5.2 容器网络专项加固(Docker/Kubernetes场景)
Docker环境必须处理三个层面:
- 宿主机防火墙 :控制外部到宿主机的流量
-
容器间防火墙
:控制容器到容器的流量(通过
--icc=false) - Pod网络防火墙 :Kubernetes中通过NetworkPolicy实现
针对Rocky Linux 9 + Docker组合,关键操作:
-
创建专用
docker-bridgezone管理容器桥:
firewall-cmd --permanent --new-zone=docker-bridge
firewall-cmd --permanent --zone=docker-bridge --set-target=ACCEPT
firewall-cmd --permanent --zone=docker-bridge --add-interface=docker0 -
限制容器外联:
firewall-cmd --permanent --zone=docker-bridge --remove-service=dhcpv6-client
firewall-cmd --permanent --zone=docker-bridge --remove-service=mdns -
为敏感容器启用
--network=none并手动配置:
docker run --network=none --cap-add=NET_ADMIN nginx
然后用firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -o eth0 -d 8.8.8.8 -j ACCEPT放行DNS
我在线上环境实测,此配置后
nmap -sT -p 1-1000 172.17.0.2
(容器IP)返回全端口
filtered
,而
nmap -sT -p 8080 192.0.2.10
(宿主机)仅
8080
端口
open
——完美实现网络隔离。
5.3 高级威胁防护:利用nftables直接注入规则
当firewalld的抽象层无法满足需求时(如防CC攻击),需直连nftables。但必须遵守firewalld的规则注入规范:
-
所有自定义规则必须放在
firewalld表的raw_PREROUTING链(早于连接跟踪) -
使用
firewall-cmd --direct接口,而非裸nft命令
例如,防HTTP Flood:
# 创建限速链
firewall-cmd --direct --add-chain ipv4 raw http-flood
# 设置每秒最多10个新连接
firewall-cmd --direct --add-rule ipv4 raw http-flood 0 -m connlimit --connlimit-above 10 --connlimit-mask 32 -j DROP
# 将流量导向该链
firewall-cmd --direct --add-rule ipv4 raw PREROUTING 0 -p tcp --dport 80 -j http-flood
注意:
--direct命令会写入/etc/firewalld/direct.xml,并在--reload时自动加载。这比手动nft add rule安全,因为firewalld会校验规则语法并确保与现有策略不冲突。
最后分享一个血泪教训:某次升级firewalld到0.9.8后,
--direct
规则突然失效。排查发现新版本要求
--direct
规则必须带
-m state --state NEW
条件,否则被忽略。这提醒我们:
firewalld的
--direct
接口虽强大,但版本兼容性需严格测试
。生产环境升级前,务必在测试机执行
firewall-cmd --direct --get-all-rules
验证所有规则是否仍存在。
我个人在实际操作中的体会是:firewalld不是越配置越安全,而是越理解其设计哲学越安全。Rocky Linux 9选择firewalld,本质是选择一种“策略即代码”的运维范式——zone是策略域,service是策略单元,
firewall-cmd
是策略编译器。当你停止把它当作iptables的替代品,开始用zone思维规划网络边界,用service抽象定义应用契约,用
--direct
精准注入防御逻辑,你就真正掌握了Rocky Linux 9防火墙的精髓。
72

被折叠的 条评论
为什么被折叠?



