说实话,Linux服务器上最容易被折腾出问题的,就是防火墙。尤其是CentOS用户,刚装完系统高高兴兴把服务跑起来了,结果外部访问死活不通,要么就是各种“操作不当”把远程连接给整断了,只能灰溜溜地去机房或者VNC控制台救火。今天就把CentOS防火墙的这些事一次性聊透,从原理到实操,从常见踩坑到排查链路,都摊开讲清楚。
这篇内容主要解决几类问题:CentOS上的防火墙到底怎么开、怎么关、重启后怎么保证配置还在;端口开放后为什么还是连不上;防火墙规则和iptables、SELinux是什么关系;以及日常运维里我见到频率最高的那些“玄学问题”的真实原因。不管是刚接触Linux的新手,还是干了几年运维需要查漏补缺的老手,这篇都能给你一些有价值的东西。
1. CentOS防火墙到底是什么:先搞清firewalld和iptables的关系
很多人一提到防火墙就是“iptables”,一说防火墙操作就是“service iptables stop”,这个习惯到了CentOS 7以后其实已经过时了。CentOS 7开始,系统默认的防火墙管理工具换成了firewalld,它和iptables不是对立关系,firewalld底层依然会调用iptables内核模块来下发规则,只是在用户态换了一套更友好的管理接口。换句话说,你用firewalld配置规则,最后还是落在netfilter上,但规则的管理方式、持久化方式、zone模型都和以前完全不一样了。
1.1 两代防火墙机制的区别与选择
CentOS 6时代,大家习惯用iptables命令直接增删规则,然后通过service iptables save把规则写入/etc/sysconfig/iptables文件。这套方式的逻辑是“顺序匹配”,规则一行一条,从上往下走,匹配到就执行对应动作。优点是灵活强大,缺点也很明显:规则一多,脑子里得维护一张“规则执行顺序图”,稍不注意就会把规则加错位置,导致流量被错误拦截或者错误放行。
CentOS 7之后默认的firewalld采用了“zone”模型,官方预置了trusted、home、internal、work、public、dmz、block、drop等几个区域。每个zone定义了不同的信任级别,比如public默认拒绝绝大多数入站流量,只放行跟出站连接关联的回包;trusted默认放行所有流量。这种模型的好处是,你不需要操心复杂的规则链,只需要想清楚“这个网卡信任程度是哪个级别”,然后往对应的zone里加规则就行。
那能不能在CentOS 7上继续用iptables?可以。你有两种选择,一种是把firewalld停掉然后安装iptables-services;另外一种是继续保留firewalld,直接用iptables命令临时写规则,但这样规则不会持久化,重启就丢。我的建议是,除非你有历史脚本和规范必须沿用iptables,否则新环境直接习惯firewalld,因为它对规则的处理确实更友善。而且大多数云厂商的安全组规则也是类似“放行清单”的思路,上手没有违和感。
1.2 防火墙的核心概念:zone与默认行为
zone是理解firewalld的门槛。系统里默认区域是public,你可以通过firewall-cmd --get-default-zone查看当前默认zone。如果你的服务器只有一块网卡,而且没有特别指定过zone,那么这块网卡就属于默认zone。换句话说,你平时执行firewall-cmd --add-port=8080/tcp,实际是在给public这个区域增加放行规则。
每个zone自己维护一张规则清单,包括放行的服务、端口、协议、源地址等。不同zone之间的规则互相独立,比如你在public放行了8080,但网卡如果绑定的是internal区域,那你访问8080还是不通。这点特别容易被忽视,很多人配置了半天规则,最后发现网卡挂在别的zone上,那前面做的全是无用功。
另外还要澄清一点:firewalld的默认策略是“放行出站、拦截入站”,也就是说服务器主动往外发连接不受影响,但外部主动访问服务器的连接默认会被拒。这也是为什么你开了服务,本地测试没问题,外部就是连不上——最大嫌疑就是防火墙没放行对应端口。理解了zone和默认策略之后,我们再来看具体怎么操作,思路就会清晰很多。
2. 防火墙基础操作:开启、关闭、重启与开机自启
防火墙的基础操作看着简单,但很多人对“临时操作”和“永久操作”分不清,导致改完配置重启服务器之后又回到原样,或者因为直接关了防火墙导致服务全裸奔。这一节把服务管理的各种细节展开讲,照着做基本不会出岔子。
2.1 管理防火墙服务状态的核心命令
在CentOS 7及以上的系统里,firewalld是一个系统服务,通过systemd来管理。查看当前状态用systemctl status firewalld,如果只想知道防火墙功能本身是否在运行,可以执行firewall-cmd --state,它会直接输出running或者not running,比翻一堆journal日志更直观。
启动防火墙用systemctl start firewalld,停止用systemctl stop firewalld,重启用systemctl restart firewalld。这些命令大家应该不陌生,但有一个细节很多人会踩坑:如果你修改了firewalld的配置,或者手动编辑了zone的XML文件,想让它生效,不一定需要restart,执行firewall-cmd --reload就够了。这个reload会重新加载永久的配置,而且关键是不会中断已经建立的连接,在线上环境非常实用。
还有一个细节是systemctl stop firewalld和firewall-cmd --state之间的矛盾。某些情况下,你明明执行了systemctl stop firewalld,但再执行firewall-cmd --state时发现它提示running,这是怎么回事?多半是因为firewalld服务启动了自恢复机制,或者你使用了一些直接操作iptables的工具导致状态判断异常。遇到这种情况别慌,看systemctl status firewalld为准,必要时systemctl mask firewalld彻底禁用,防止其他进程把它拉起来。
2.2 开机自启与临时关闭:为什么建议用永久配置而不是一直disable
把防火墙关了确实能一了百了,端口直接全通,但代价是服务器彻底暴露在网络上。如果机器是内网环境,或者仅仅是本地开发机,也许还能接受;如果是公网服务器,关了防火墙等于把大门敞开,任何端口扫到就进,这是拿安全性换省事,风险极高。
如果你只是需要临时调试某个服务,可以用systemctl stop firewalld关闭,调试完再systemctl start firewalld恢复。切忌用systemctl disable firewalld来“永久关闭开机自启”,除非你有充足的网络层面隔离方案。disable的意思不只是本次关闭,而是以后每次开机都不加载防火墙规则,这个状态一旦忘了恢复,服务器基本等于裸奔。
正确的做法是保持防火墙服务常开,用zone和端口规则来做精细化放行。这样即使以后有未知的新服务启动,也不会因为你忘了关某个口子而直接被外部扫描到。在实际生产中,我更倾向于在云平台上用安全组做第一层过滤,再用系统防火墙做第二层防护,双重保险比单靠哪个都稳妥。安全组负责大范围的进出口控制,系统防火墙负责本机精细化规则,比如只允许某个IP访问某个端口,这种粒度安全组也能做,但系统防火墙做起来同样顺手。
3. 开放端口实战:从命令到配置文件的完整路径
这部分是大多数人最关心的。开放端口用命令无非就是firewall-cmd --add-port=端口/协议,但这里面的门道有不少。直接说结论:不加--permanent的规则是临时规则,重启或者reload之后就会消失;加了--permanent才是永久规则,但需要reload之后才会生效。这个机制让不少人懵过:明明加了--permanent,为什么执行了--add-port之后马上又用--query-port查询显示没有放行?因为永久规则在reload之前不会加载到runtime环境里。
3.1 最常用的firewall-cmd端口操作
放行一个TCP端口的标准写法是:
firewall-cmd --permanent --add-port=8080/tcp firewall-cmd --reload第一条命令把8080端口的TCP协议加入永久规则,第二条命令让规则生效。如果你想放行UDP协议,把tcp换成udp即可,比如DNS解析常用的53端口就是UDP。
查看当前已经放行的端口列表:
firewall-cmd --list-ports这个命令输出的是runtime环境里已经生效的端口规则。如果你刚加了--permanent但还没reload,此时是看不到新规则的,这属于正常现象,别急着怀疑命令没执行成功。
查询某个具体端口是否放行:
firewall-cmd --query-port=8080/tcp输出yes表示放行,no表示没有放行。这个命令同样查询的是runtime状态,不是永久配置。很多人在这个细节上栽跟头:永久配置明明写了,但查询为no,原因就是没reload。
删除端口规则:
firewall-cmd --permanent --remove-port=8080/tcp firewall-cmd --reload删除之后记得也要reload,否则runtime里仍然保留着放行规则。这里我建议删规则的时候慎重,尤其是公网服务器,删了之后外部访问就会断,最好在业务低峰期操作。
3.2 通过配置文件开放端口:手写XML与直觉式编辑
firewall-cmd命令最终会把永久规则写入/etc/firewalld/zones/目录下的XML文件。以默认的public区域为例,对应的文件是/etc/firewalld/zones/public.xml。如果你不想用命令,也可以直接编辑这个XML文件加端口。
一个典型的public.xml内容如下:
<?xml version="1.0" encoding="utf-8"?> <zone> <short>Public</short> <description>For use in public areas.</description> <service name="ssh"/> <service name="dhcpv6-client"/> <port port="8080" protocol="tcp"/> </zone>在<zone>标签里增加<port port="8080" protocol="tcp"/>,保存后执行firewall-cmd --reload就可以生效。
不过说实话,我不太推荐手动编辑XML文件。原因有几个:第一,XML语法有严格规范,标签写错了半个字符,firewalld直接起不来,到时候只能进控制台恢复,非常狼狈;第二,命令行的--add-port本身就是在帮你维护这个XML,没必要自己去碰。但理解这个文件的机制是有用的,比如你需要批量迁移防火墙规则、或者写脚本批量添加端口时,直接操作XML反而更高效。
如果你真的想手改XML,我建议先备份原文件,修改后用firewall-cmd --reload验证,如果firewalld没有报错,说明语法没问题,再检查firewall-cmd --list-ports能否看到新规则。一旦firewalld启动失败,立刻用备份文件恢复。
3.3 端口区间、服务名与富规则:进阶开放方式
除了单个端口,firewalld还支持端口区间、服务名和富规则。
端口区间写法:
firewall-cmd --permanent --add-port=10010-10020/tcp firewall-cmd --reload这段命令开放了从10010到10020之间的所有TCP端口,适合一些使用动态端口范围的程序,比如FTP被动模式、RTP媒体流服务等。
服务名方式比端口号更语义化。比如你想开放HTTP服务,不用记80端口,直接:
firewall-cmd --permanent --add-service=http firewall-cmd --reloadfirewalld内置了很多常用服务定义,用firewall-cmd --get-services可以查看完整列表。用服务名的好处是,以后如果标准端口变了(虽然不太可能),你只需要更新系统服务定义,不用改业务规则。
富规则(rich rule)是firewalld里最灵活的手段,适合做“只允许特定IP访问特定端口”之类的精细控制。比如只放行192.168.1.100这台机器访问本机的3306端口:
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100/32" port protocol="tcp" port="3306" accept' firewall-cmd --reload富规则的语法比较啰嗦,但是表达能力强。类似的场景还有把某个IP段拉黑:
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" drop' firewall-cmd --reload使用富规则的人相对少,因为很多公司直接用云安全组替代了这个功能。但在纯物理机或者内网环境,富规则是唯一不需要额外工具就能实现“IP白名单”的方式。
4. 配置留存与迁移:让规则在重启后依然生效
防火墙规则最怕的就是“配置一时爽,重启火葬场”。很多人用--add-port放行端口后,服务立刻通了,一切正常,结果某天重启服务器,端口又连不上了。原因就是漏掉了--permanent参数。这一节我们把持久化和迁移的细节讲透。
4.1 --permanent的作用与reload机制
firewalld内部管理着两套规则:一套是runtime规则,也就是当前内存中生效的规则;另一套是permanent规则,也就是持久化在磁盘XML文件里的规则。firewall-cmd --add-port默认只改runtime,不加--permanent就意味着规则不会写入XML文件,重启服务或者重启系统之后就会丢失。而加了--permanent只是写入磁盘,不会立刻在runtime里生效,需要reload或者重启firewalld服务。
理解这个机制之后,你应该能推断出几种操作的结果。如果只执行了--add-port=8080/tcp,当前能访问,重启后失效;如果只执行了--permanent --add-port=8080/tcp,磁盘上有了记录但当前没生效,需要reload;如果两个都执行了,这是最标准的“当前生效+重启保留”方案。有些习惯用iptables的老手一开始很不适应这个两段式逻辑,但用熟之后会发现它其实更安全——你可以在命令行里试错,确认没问题了再永久化,不至于一次误操作就把环境搞乱。
firewall-cmd --reload和firewall-cmd --complete-reload是有区别的。前者保留运行时状态,只是应用一下永久配置,不会断掉现有连接,日常用这个就够;后者是完全重新加载,等于把所有规则丢弃再重新加载一遍,会导致现有连接全部中断。线上环境千万别用--complete-reload,除非你有足够的心理准备面对一波工单。
4.2 备份与迁移防火墙规则
服务器防火墙规则的管理应当纳入变更流程,就像代码一样需要可回滚。firewalld的规则都集中在/etc/firewalld/目录,最简单粗暴的备份方式就是备份整个目录:
cp -r /etc/firewalld /root/firewalld-backup-$(date +%F)恢复时把备份目录里的文件覆盖回去再reload即可。这个操作我建议在改动较大规则集之前做一次,尤其是生产环境,有了备份,就算把规则改崩了,几分钟之内就能恢复。
如果要在多台机器之间迁移规则,可以先在源机器上导出,再到目标机器导入。可以用firewall-cmd --list-all-zones把所有zone的当前规则全部列出来,但这只是人眼查看,不适合直接导入。更靠谱的方式是直接拷贝XML文件。例如把public区域的规则从机器A同步到机器B:
scp /etc/firewalld/zones/public.xml root@目标机器IP:/etc/firewalld/zones/拷贝完成后在目标机器上执行firewall-cmd --reload,规则就过去了。注意,如果目标机器上原来有自定义zone,也要一并拷贝,否则引用缺失zone的网卡可能报错。另外,如果两台机器的网卡接口名不同,还得检查网卡和zone的绑定关系是否正确,绑定关系配置在/etc/firewalld/zones/里的XML中,或者在/etc/sysconfig/network-scripts/ifcfg-网卡名里通过ZONE=字段指定。
5. 常见问题与排查技巧实录
防火墙这东西,出问题时的表象千奇百怪,但症结往往就那么几个。我把这些年遇到的高频问题整理了一下,方便你遇到状况时快速定位。
5.1 端口不通的排查链路
开放了端口还是连不上,别急着重启防火墙,按照下面的顺序逐步排查,基本都能找到原因。
第一步,确认服务本身在监听。执行ss -tlnp | grep 端口号,看对应端口是否有LISTEN状态。如果压根没有监听,那问题不在防火墙,先把服务启动起来再说。另外注意,有些服务默认监听在127.0.0.1上,比如某些框架的开发模式,外部网络访问这个IP是永远连不上的,需要把监听地址改成0.0.0.0才能对外提供服务。这一步很多人会忽略,明明防火墙全放开了还是不通,最后发现是应用监听地址的问题。
第二步,确认防火墙放行状态。用firewall-cmd --query-port=端口/tcp检查runtime规则。如果输出no,用--add-port或者--permanent --add-port放行。这里提醒一点,如果服务是用UDP协议通信的,排查时要带上/udp,很多人只放行了TCP导致UDP端口一直不通。
第三步,检查SELinux。CentOS默认开启SELinux,它和防火墙不是一回事,但同样会拦截网络访问。查看状态用getenforce,如果是Enforcing状态,可以临时用setenforce 0关闭再测试;如果确认是SELinux拦截,可以通过设置对应的布尔值来解决,而不是直接关掉SELinux。比如允许httpd访问网络:
setsebool -P httpd_can_network_connect 1第四步,检查云平台安全组。如果你用的是云服务器,除了系统防火墙,控制台里的安全组也会做过滤。很多情况下系统防火墙已经放行了,但安全组入方向没放行对应端口,一样连不上。这个排查点特别容易被忽略,甚至有些老运维也会在这里卡住。云服务器的访问链路是“外部网络 -> 安全组 -> 系统防火墙 -> 应用”,安全组挂在最外层,系统防火墙规则做得再完美,安全组不放行也是白搭。
第五步,测试网络连通性。本地试一下ping 服务器IP,不通的话可能是网络路由或者ICMP被拦截;用telnet 服务器IP 端口测试指定端口是否通,通的话会显示连接建立提示,不通会卡住然后报错。有条件的话用手机流量试,能排除本地网络环境的问题。
整个排查链路我整理成了一张表,方便你收藏:
| 排查步骤 | 命令/操作 | 可能原因 |
|---|---|---|
| 服务监听检查 | ss -tlnp | 服务未启动、监听地址错误 |
| 防火墙规则检查 | firewall-cmd --query-port | 未放行端口、未reload |
| SELinux检查 | getenforce | SELinux拦截 |
| 安全组检查 | 云控制台 | 入方向未放行 |
| 网络连通性 | ping、telnet | 路由不通、ICMP被禁 |
5.2 规则不生效的典型原因
明明添加了永久规则,也执行了reload,为什么端口还是不通?这类问题桌面运维也遇到过。常见原因有三类。
第一类是网卡没有绑定到你配置规则的zone。用firewall-cmd --get-zone-of-interface=你的网卡名查看网卡归属。比如你的网卡是eth0,但eth0被分配到了internal zone,而你是在public里加的规则,那这个规则跟eth0一点关系都没有。可以通过firewall-cmd --zone=public --change-interface=eth0把网卡绑定到public,或者直接往网卡所在zone加规则。
第二类是规则顺序问题,虽然firewalld的zone模型不像iptables那样严格要求顺序,但富规则的优先级是高于普通端口规则的。如果你写了一条drop的富规则,哪怕后来又用--add-port放行了端口,drop规则依然会先拦截掉。所以在有富规则的场景下,要检查断言关系,别让两条规则“打架”。
第三类是服务本身没有监听在所有网卡上。排查方法前面提过,看ss -tlnp的监听地址。如果只监听127.0.0.1,那无论防火墙怎么放行都没用,因为数据包在到达应用之前,系统根本不会把外部流量转发到loopback地址上。这种情况要去改应用的监听配置,而不是折腾防火墙。
5.3 防火墙要不要关:安全与便利的平衡
“防火墙关闭有影响吗”这个问题,说实话,得看场景。如果是开发环境、内网测试环境,可以考虑在可控网络内关闭防火墙,提升调试效率。但如果机器有公网IP,哪怕只是挂一个简单的服务页面,也不建议关防火墙。现在网络上的扫描工具横行,全开放的服务器被入侵的案例太多了,我见过不少因为图省事关防火墙,结果数据库被人扫到弱密码直接拖库的悲剧。
如果觉得防火墙规则太麻烦,一个折中方案是:保留防火墙开启状态,设置默认拒绝所有未知入站流量,只放行必要的服务和端口。常见的Web服务器,放行80和443;SSH登录,放行22或者自定义端口;数据库,只对特定的内网IP段放行。这样即使某个服务有漏洞,攻击者也无法从外部立刻访问到这个端口,多了一层有效的缓冲。
还有一个小技巧,如果你经常在本地用SSH连接服务器,而服务器IP又不是固定的,可以考虑给SSH端口做成IP白名单。当然这会增加管理负担,IP变了就得去控制台或者用其他方式重新配置规则,所以大多数场景下不建议把所有端口都做IP限流,保留服务端口开放,用强密码加key登录更实际。
6. 一些经验之谈:关于防火墙的运维习惯
最后说点个人体会,我觉得比命令本身更值钱。
一是养成“改前备份、改后验证”的肌肉记忆。不管是加规则还是删规则,先备份/etc/firewalld目录,操作后立刻用firewall-cmd --list-ports、firewall-cmd --list-all验证,最好再用外部网络环境实际连接一次,确认没有误伤。
二是不要盲目复制网上的命令。网上的教程质量参差不齐,有些命令直接照抄会出大问题。比如网上常看到一句话“永久关闭防火墙”,直接systemctl disable firewalld,这个在生产环境用非常危险。你要先判断自己的业务形态、网络环境,再决定要不要执行。命令本身没有对错,执行的人得清楚后果。
三是定期审查防火墙规则。很多服务器上的防火墙规则是历史遗留物,早期开放了某个端口做测试,后来业务下架了,规则却一直留着,变成隐形风险。我建议每季度或者半年做一次规则审计,把不用的端口规则删掉,保持最小化原则。虽然麻烦,但对安全的意义很大。
四是把防火墙操作记入变更记录。这个习惯适合团队协作的场景。在配置变更时记一笔:谁改的、改了哪个端口、为什么改、什么时候改的、对应的服务是什么。有时候一个问题排查不出来,翻一下变更记录,立刻能定位是哪次改动引入的问题,比抓瞎强太多。
如果你在配置防火墙时踩过什么奇怪的坑,欢迎在评论区留个言,大家一起避雷。