很多年前我第一次在 CentOS 7.9 上部署业务,遇到一个特别磨人的现象:服务进程明明起来了,端口也在监听,可外面怎么都访问不到。后来查了一圈,发现是防火墙默认规则把端口挡了。那时候我才认真意识到,Linux 服务器上“装好服务”只是第一步,把防火墙规则理清楚,才是服务真正能对外提供价值的前提。
这篇文章就围绕 CentOS 7.9 的防火墙做一次完整梳理,从底层原理到常用命令,从区域机制到线上排错。内容适合两类人:刚接触 Linux 服务器运维的新手,以及被“端口不通”“规则不生效”这类问题反复折磨过的人。读完你至少能搞明白:firewalld 和 iptables 到底什么关系、默认规则为什么挡流量、放行端口该用哪条命令、生产环境有哪些坑不能踩。
1. 先搞清楚CentOS 7.9上运行的到底是哪一套防火墙
1.1 firewalld和iptables的关系:很多人的理解是反的
在 CentOS 7 之前,系统默认用的是 iptables 服务来管理防火墙规则。到了 CentOS 7,默认换成了 firewalld,CentOS 7.9 自然也是。但很多教程和论坛贴子还是在讲 iptables,这就容易让新手产生一个误解:觉得 firewalld 是一套独立的东西,和 iptables 互不相干。
实际上不是这样。firewalld 本身并不做数据包过滤,它只是一个管理框架,真正干活的是内核里的 netfilter 模块。firewalld 在用户态把规则组织好,然后调用 iptables、ip6tables、ebtables 这些命令行工具把规则下发到内核。也就是说,firewalld 是“前台经理”,iptables 是“执行者”,netfilter 才是“真正做事的工人”。
这个关系一旦搞清楚,很多问题就变得顺理成章了。比如:你用 iptables 命令手动加了一条规则,用 firewall-cmd --list-all 却看不到,因为 firewalld 只是把它管理的规则交给 iptables,但不负责展示所有系统里的规则;反过来,你用 firewall-cmd 加的规则,在 iptables -L 里是能看到的,只是它会以 chain 的形式出现在 FORWARD、INPUT 等位置。
提示:在 CentOS 7.9 上,如果你需要看“真实生效的规则”,请执行 iptables -L -n 来查看内核里的实际规则链;如果你需要看“firewalld 管理的规则”,执行 firewall-cmd --list-all。两条命令查到的信息并不完全等价。
1.2 系统里可能同时存在多套规则
CentOS 7.9 有个比较隐蔽的点:即使你禁用并停止了 firewalld,系统里可能还残留着之前 iptables 添加的规则;反过来,如果 firewalld 正常启动,它会把自己管理的规则注入到 iptables 中,此时如果你再手动去跑 iptables 命令加规则,就会出现“两条线”同时存在、互相干扰的情况。
我见过不少线上事故,都是因为两边同时操作导致的:
- 运维在初始化文档里写了
systemctl stop firewalld,后来又出于安全要求重新开启了 firewalld,结果发现规则全变了,业务端口被挡。 - 有人在 firewalld 的富规则里允许了某个 IP,但又用 iptables 在更靠前的位置加了一条 DROP,结果允许规则始终不生效。
所以我的建议很直接:一台服务器上,明确选择一个管理方式,二选一。现在 CentOS 7.9 默认是 firewalld,那就老老实实用 firewall-cmd 管理;除非你有很特殊的理由要用 iptables service,否则别两边混着写规则。混着写,排查成本远大于省下的那几分钟。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 日常防火墙操作最常用的命令清单
2.1 查看状态与启停服务
CentOS 7.9 上管理 firewalld 服务本身,依赖 systemd,所以第一组命令是:
- 查看运行状态:
systemctl status firewalld或firewall-cmd --state - 启动 / 停止 / 重启:
systemctl start firewalld、systemctl stop firewalld、systemctl restart firewalld - 开机自启 / 取消开机自启:
systemctl enable firewalld、systemctl disable firewalld
两处细节值得注意:
firewall-cmd --state 的输出很直接,只有两个词:running 或者 not running。它适合写进脚本里做判断。而 systemctl status firewalld 的输出非常冗余,不适合脚本解析,但适合人看详细状态。
另一个是 systemctl disable firewalld 并不等于停止服务。disable 只表示开机不启动,如果你需要立刻停止,还得再执行 systemctl stop firewalld。这两个命令经常有人只执行一半,结果服务器重启后又弹出一堆防火墙规则。
2.2 放行、删除、重载规则
firewalld 的规则操作以 firewall-cmd 为核心,我会从实际效果出发,把最常用的命令分成三类。
第一类,查看当前规则:
code复制firewall-cmd --list-all
这条命令会列出默认区域的完整配置,包括放行的端口、服务、富规则等。如果你想看所有区域,可以加 --zone=public 指定区域,或者用 --list-all-zones 一次看全。
第二类,临时放行一个端口:
code复制firewall-cmd --add-port=8080/tcp
这条命令执行后立即生效,但有一个重要前提:firewalld 重载或者服务重启后,这条规则会消失。因为它是“运行时规则”,没有写入配置文件。所以真正要放行一个端口,通常得写成:
code复制firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload
注意 --permanent 表示写入永久配置,但不会立即生效;真正让永久配置生效的动作是 --reload。所以正确姿势是:先加永久规则,再 reload。如果你只执行了第一条没有 reload,当场测试端口还是不通,这时候不能怀疑命令写错,只是规则还没加载。
第三类,删除规则:
code复制firewall-cmd --permanent --remove-port=8080/tcp
firewall-cmd --reload
如果你想删除临时规则,去掉 --permanent 后再执行一次即可。删除临时规则后也不需要 reload,因为运行时规则删除是立刻生效的。
2.3 把临时规则持久化
有时候你已经用 firewall-cmd --add-port=8080/tcp 加了一堆临时规则,业务正在跑,又怕重启后规则丢了。这时候不需要把所有命令重新敲一遍,只要执行:
code复制firewall-cmd --runtime-to-permanent
这条命令会把当前运行时的所有规则写入到永久配置文件中,等于把“临时工”转正。这个命令在线上调整环境时特别实用,比如你昨晚为了排查问题加了好几条临时放行规则,确认没问题后一条命令全部保留下来,比逐一重新添加省事得多。
不过它也有个副作用:它会把当前的运行时规则整体覆盖到永久配置,而不是增量追加。所以如果你在运行时删掉了一些原本的永久规则,执行 --runtime-to-permanent 后,这些被删的规则在永久配置里也会消失。执行前最好先 firewall-cmd --list-all 检查一遍。
3. 区域(zone)机制:为什么很多规则“配了不生效”
3.1 什么是zone,默认区域如何决定网络行为
firewalld 最核心的设计是 zone,也就是“区域”。它把网络接口或来源 IP 划分到不同的信任级别,每个区域有不同的默认策略。CentOS 7.9 安装完成后,系统会预置这样几个常用区域:
| 区域名 | 默认信任程度 | 说明 |
|---|---|---|
| drop | 最低 | 丢弃所有入站流量,没有任何响应 |
| block | 很低 | 拒绝所有入站流量,回复 icmp-host-prohibited |
| public | 低 | 默认区域,只允许少量服务如 ssh,其余端口拒绝 |
| external | 低 | 适用于开启了 NAT 的外网口,比 public 更严格 |
| internal | 中 | 适用于内网环境,信任程度较高 |
| trusted | 最高 | 放行所有流量,一般来说只在完全可信的内网段使用 |
默认情况下,系统所有网卡都会被划分到 public 区域。public 区域的默认行为是:允许某些基础服务(比如 ssh、dhcpv6-client),拒绝其他所有入站连接。这就是为什么你装好 Nginx 或 MySQL 后,本机访问没问题,外部访问却不通——因为默认策略根本就没给 80、443、3306 放行。
想查看默认区域,执行:
code复制firewall-cmd --get-default-zone
想查看当前哪些网卡属于哪个区域,执行:
code复制firewall-cmd --get-active-zones
3.2 查看与调整网卡对应的区域
很多“规则配了不生效”的问题,最后都出在区域跟网卡没对上。比如你把 8080 端口放行在了 trusted 区域,但网卡 ens33 属于 public 区域,那规则根本管不到这个网卡上的流量。
查看网卡归属区域:
code复制firewall-cmd --get-zone-of-interface=ens33
临时把网卡切换到某个区域:
code复制firewall-cmd --zone=internal --change-interface=ens33
永久把网卡固定到某个区域:
code复制firewall-cmd --permanent --zone=internal --change-interface=ens33
firewall-cmd --reload
这里有个经验:不要把内网网卡和公网网卡放在同一个区域,尤其是不要把连接公网的网卡放到 trusted。常见做法是:内网网卡划分到 internal 或 trusted,公网网卡保持 public 或 external,然后在各自区域里按需放行端口。这样即使公网网卡被扫描到端口,也会被默认策略拒绝,不会把内网服务直接暴露出去。
3.3 区域白名单的查看方式
热搜词里有个问题很典型:centos7防火墙怎么查看防火墙白名单区域。其实就是问怎么查看某个区域当前放行了哪些东西。
完整查看一个区域的配置,用:
code复制firewall-cmd --zone=public --list-all
输出大概长这样:
code复制public (active)
target: default
icmp-block-inversion: no
interfaces: ens33
sources:
services: ssh dhcpv6-client
ports: 8080/tcp
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
看到 interfaces: ens33 表示 ens33 挂在这个区域;services 列出的是放行的服务;ports 列出的是放行的端口;rich rules 就是富规则。这个输出内容是理解当前白名单最直接的入口。
如果只想看端口列表,可以执行:
code复制firewall-cmd --zone=public --list-ports
只想看服务列表:
code复制firewall-cmd --zone=public --list-services
注意:
--list-all显示的是“当前运行时生效的规则”。如果你修改了永久配置但还没 reload,临时输出里看不到新规则,这个时候不要急着下“规则没生效”的结论。
4. 端口、服务、富规则:三种放行方式怎么选
4.1 端口直通:适合快速开发和临时调试
端口直通是最简单的放行方式,格式为“端口/协议”,协议一般是 tcp 或 udp:
code复制firewall-cmd --permanent --add-port=3306/tcp
firewall-cmd --reload
端口直通的特点是干净、直观、好排查。你放行了 3306/tcp,那这个端口就是通的,没有任何多余条件。它适合在开发环境调试,或者在临时需要外部测试时使用。
但端口直通有一个明显短板:它不区分来源 IP。只要网络能到达这台服务器,放行了的端口就完全暴露。生产环境中如果把 MySQL、Redis 这类服务的端口直接放行到 public 区域,基本等于把数据库裸奔在公网,这是非常危险的操作。
所以端口直通在线上只适合用于像 Nginx 的 80/443、业务应用的对外端口这类确实需要公开访问的端口。
4.2 服务放行:适合系统预置的标准服务
firewalld 预置了很多服务定义,比如 ssh、http、https、mysql、postgresql 等。查看所有预置服务:
code复制firewall-cmd --get-services
放行一个预置服务:
code复制firewall-cmd --permanent --add-service=http
firewall-cmd --reload
使用服务放行的好处是:你不用去记 HTTP 是 80/tcp、HTTPS 是 443/tcp,firewalld 已经帮你把“服务名到端口”的映射关系写好了。但是如果你的服务跑在非标准端口上,比如 Nginx 跑在 8080,那你应该用端口直通,而不是把 http 服务放行,因为服务定义里映射的是 80 端口。
如果你想给自研服务做一个自定义服务定义,方法也不复杂。在 /etc/firewalld/services/ 下创建一个 XML 文件,比如 myapp.xml:
code复制<?xml version="1.0" encoding="utf-8"?>
<service>
<short>myapp</short>
<description>My custom application service</description>
<port protocol="tcp" port="18080"/>
</service>
创建完成后,执行 firewall-cmd --reload 让 firewalld 识别新服务,然后就可以用 --add-service=myapp 来放行它了。这个方式适合团队内部有固定端口规范、想让规则更易读的场景。
4.3 富规则与黑白名单控制:精确到来源IP和动作
富规则(rich rule)是三种方式里最灵活、也最接近“真·黑白名单”功能的手段。它可以在一条规则里同时指定来源 IP、端口、协议和处理动作。
只允许内网网段访问 MySQL 的 3306 端口,拒绝其他来源:
code复制firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port protocol="tcp" port="3306" accept'
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="0.0.0.0/0" port protocol="tcp" port="3306" reject'
firewall-cmd --reload
第一条规则放行内网网段访问 3306,第二条规则拒绝所有来源访问 3306。因为防火墙规则是顺序匹配的,匹配到第一条直接 accept,后面不会再看,所以这个组合实现了“白名单优先”的效果。
如果你只是想把某个恶意 IP 拉黑,可以这样:
code复制firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.7" drop'
firewall-cmd --reload
富规则还支持更复杂的条件,比如限定时间、按照 mac 地址匹配、设置记录日志等。比如给被拒绝的流量记录日志:
code复制firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.7" log prefix="dropped-bad-ip" level="info" drop'
对于“防火墙黑白名单”这个需求,我的结论很明确:不要指望防火墙本身做成一个完整的 WAF 黑名单系统,但用富规则拉黑几个明显的攻击源 IP、限制管理网段访问,完全够用,而且比在应用层做限制更前置、更省业务资源。
4.4 实际操作中的选择建议
我在线上部署服务时,端口放行会按这个标准来选:
| 场景 | 放行方式 | 原因 |
|---|---|---|
| Nginx/前端对外端口 80/443 | 端口直通或服务放行 | 确实需要全网访问,规则越简单越好 |
| 内网数据库 3306/6379 | 富规则限定来源网段 | 只允许应用服务器网段访问,减少暴露面 |
| 跳板机管理端口 | 富规则限定来源 IP | SSH 端口仅对办公网或跳板机开放 |
| 临时联调开端口 | 端口直通,不加 permanent | 联调结束后可以快速收回 |
这个思路的核心是:能限定来源就一定限定来源,不要图省事把所有端口都放成全网通。
5. 生产环境里最容易踩的防火墙坑
5.1 重启和reload的区别:临时规则为什么会“丢”
CentOS 7.9 的 firewalld 有“运行时配置”和“永久配置”两套概念,所有规则问题几乎都绕不开这个区分。
firewall-cmd --add-port=8080/tcp修改的是运行时配置,立即生效,但重启服务或 reload 后丢失。firewall-cmd --permanent --add-port=8080/tcp修改的是永久配置,不会立即生效,需要 reload 或重启服务。firewall-cmd --reload会用永久配置覆盖运行时配置。
“reload 会丢临时规则”这个行为,经常被运维当成故障来排查。比如你在线上临时放行了一个端口,过了一会发现端口又不通了,回想一下,是不是有人执行了 firewall-cmd --reload 或者重启了 firewalld。如果确实是这个原因,而你还需要这条临时规则,执行 firewall-cmd --runtime-to-permanent 把它固化即可。
5.2 同时管理firewalld和iptables引发冲突
CentOS 7.9 上 firewalld 正常工作时,会接管 iptables 规则链。如果你在这时候手动启动 iptables 服务,等于两套管理工具在写同一份内核规则,经常会出现:
- firewalld 放行了某个端口,但 iptables 里先加了 DROP,流量被丢弃。
- 你执行
service iptables save把当前规则保存了,重启后 firewalld 的规则和 iptables 保存的规则互相覆盖,行为变得无法预测。
我的建议是,生产环境中除非有明确审计要求,否则不要把 iptables 服务打开。如果必须用 iptables 查看实际规则,请使用 iptables -L -n 命令检查,而不要通过 systemctl start iptables 启动 iptables 服务。
5.3 与Docker的端口映射冲突
Docker 场景下的防火墙冲突是个高频问题。Docker 默认会操作 iptables 的 DOCKER 链来实现端口映射,比如你运行了 docker run -p 8080:80,Docker 会自动在 iptables 里加映射规则,让外部访问宿主机的 8080 转发到容器的 80。
但如果你同时用 firewalld 放行了 8080/tcp,并不意味着一定通。因为 Docker 的流量是经过 FORWARD 链转发的,而 firewalld 对 FORWARD 链也有管理。CentOS 7.9 中 firewalld 对 Docker 的支持其实一直很微妙,实际部署中常见的问题是:
- 容器内部服务正常,但宿主机从外部访问端口不通。
- 防火墙区域里明明放行了端口,curl 还是 connection refused 或 timed out。
排查 Docker 相关防火墙问题,优先看 iptables 里 DOCKER-USER 链:
code复制iptables -L DOCKER-USER -n -v
DOCKER-USER 链是 Docker 专门留给用户管理自定义规则的地方,它在 Docker 自动规则之前生效。也就是说,你在 DOCKER-USER 链里加的规则会优先于 Docker 自动生成的映射规则。如果你要对 Docker 映射端口做来源限制,正确的做法不是在 firewalld 里加端口放行,而是在 DOCKER-USER 链里配置:
code复制iptables -I DOCKER-USER -p tcp --dport 8080 -s 192.168.10.0/24 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 8080 -s 0.0.0.0/0 -j DROP
这组命令让 DOCKER-USER 链先允许内网网段访问 8080,再拒绝其他来源。注意这个场景下别在 firewalld 里做重复放行,容易把限制逻辑绕过去。
5.4 修改SSH端口时把自己锁在外面
这大概是 CentOS 防火墙最有“现场感”的坑:你修改了 sshd 的监听端口,却忘了在防火墙里放行新端口,然后手一抖 reload 了防火墙规则,新端口被挡,旧端口已经不监听了,整个远程会话直接断掉。
更合理的操作流程是:
- 先在 tmux 或 screen 里开一个会话,确保即使 SSH 断开也能重连。
- 修改 sshd 配置并测试语法正确:
sshd -t - 先放行新端口:
firewall-cmd --permanent --add-port=2222/tcp && firewall-cmd --reload - 再重启 sshd 服务:
systemctl restart sshd - 开一个新 SSH 会话测试连接到 2222 端口,确认没问题后,再删除旧端口规则。
这个顺序不能乱。先放防火墙,再动服务,是修改远程管理端口时必须遵守的原则。否则哪怕你只是顺序反了,也可能把自己锁在机器外面,之后只能去机房或者通过带外管理口处理。
6. 一个完整的防火墙排错链路
6.1 排错思路与命令顺序
端口访问不通,先把问题按层拆解。我的排查顺序一般是:
- 第一步:确认服务本身在监听。执行
ss -lntp | grep 端口,看看进程是否真的监听在 0.0.0.0 或对应网卡上。有时候服务只监听了 127.0.0.1,外部当然连不上,这跟防火墙没关系。 - 第二步:确认防火墙状态和区域。执行
firewall-cmd --state、firewall-cmd --get-active-zones、firewall-cmd --zone=public --list-all。 - 第三步:确认规则是否放行。核心是查看
ports、services、rich rules三块输出。 - 第四步:在服务器本机测试回环访问:
curl 127.0.0.1:端口。如果本机通、外部不通,基本可以确定是防火墙或者云安全组的问题。 - 第五步:在外部机器上用
telnet 宿主机IP 端口或者nc -vz 宿主机IP 端口测试,确认网络层面是否被拒。
这套顺序可以把问题范围快速缩小到“应用配置”“防火墙规则”“云安全组”这三块中的某一处。
6.2 实例:外部访问不了8080端口
之前给一台 CentOS 7.9 服务器部署 Java 后端服务,后端监听在 8080,启动后本机 curl 正常,但从办公网访问始终超时。我开始怀疑是云平台安全组问题,但检查后发现安全组规则没限制 8080。
然后在服务器上看防火墙状态:
code复制firewall-cmd --state
输出是 running。再看当前放行列表:
code复制firewall-cmd --list-all
发现 ports 里确实没有 8080/tcp。问题基本定位了:服务正常,防火墙没放行。执行:
code复制firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload
再访问,通顺了。
这个例子看起来很基础,但正是这类问题在线上出现频率最高。很多时候不是不会配置,而是忘了把“服务跑起来”和“防火墙放行”当成两个独立环节来看待。
6.3 遇到“配置了还是不通”时的下一步
如果你确认端口已经加入 --list-all,但外部还是不通,那要按这个顺序继续查:
- 查区域是否按预期加载:确认网卡挂在哪个区域,规则是否加在了同一个区域。比如你在 internal 区域放行了端口,但网卡实际挂在 public,那等于白配。
- 查优先级更靠前的 DROP 规则:执行
iptables -L -n --line-numbers | grep -i drop,看看有没有比放行规则更靠前的拒绝规则。 - 查云厂商安全组:这个不属于服务器内部防火墙,但在云环境里很常见。比如阿里云、腾讯云、华为云的安全组规则和系统防火墙是两层独立的结构,安全组没放行,系统防火墙放行也没用。
- 查 Docker/网桥规则:如果目标服务跑在容器里,DOCKER-USER 链和 FORWARD 链的规则可能比 INPUT 链的规则更早生效。
提示:firewalld 的规则写在 INPUT 链里的部分,只对进入本机的流量生效;如果你要转发流量,还要关注 FORWARD 链。这也是为什么 iptables -L -n 能帮你看到更完整的链路。
日常巡检时,我也建议每周抽时间看一眼服务器上的防火墙规则是否有冗余。规则越堆越多之后,出问题的概率会指数上升。我之前遇到过一次诡异故障,一个端口始终 ping 不通,排查很久才发现是早前测试留下来的两条富规则叠在一起把流量 DROP 掉了,而端口本身是放行状态。所以保持规则简洁,定期清理,比任何“高深配置”都更重要。
最后再分享一个我自己的习惯:每次修改防火墙规则前,先用 iptables-save > /root/firewall-backup-$(date +%F).rules 备份一份当前规则。虽然 firewall-cmd 本身有配置文件可以还原,但备份一份完整内核规则,在事故现场恢复起来会更快,也更安心。
