CentOS 7防火墙从iptables到firewalld:核心概念与配置实践
1. 从iptables到firewalldCentOS 7防火墙的演进逻辑如果你跟我一样是从CentOS 6时代一路走过来的运维那对新系统里的防火墙一定有过一段适应期。CentOS 6用的是iptables规则写起来直来直去一条INPUT、一条OUTPUT改完service iptables save就完事。到了CentOS 7系统默认防火墙换成了firewalld一开始真有点不习惯——又是zone又是service概念一堆命令也从iptables -A变成了firewall-cmd --add-port感觉像是从命令行世界突然被扔进了一个图形化管理器的命令行前端。但用了两三年之后我得承认firewalld这套设计比iptables更适合现代服务器运维的场景。这篇文章我就把这个东西从头到尾捋一遍从它的核心概念、策略模型到具体规则怎么配、排错怎么搞把我踩过的坑和验证过的方法全部写出来。适合刚接手CentOS 7服务器、被firewalld搞懵的新手也适合以前用iptables、想搞明白firewalld到底在做什么的老运维。先说一个关键认知firewalld不是抛弃了iptables而是把iptables包装了一层。它底层仍然走的是netfilter框架规则最终还是要落到内核的包过滤链上。只不过firewalld把它们组织成了更高层的抽象——zone、service、rich rule——让你不需要逐个写链上的规则而是声明“这个区域开放哪些服务”然后由daemon进程动态管理规则集合。这个区别很本质。iptables的规则集合是静态的你自己维护firewalld是动态的它维护的是一套策略按需生成iptables规则。所以你直接iptables -L看的时候看到的规则跟firewalld配置里的内容并不是一一对应的初次排查会有点懵这个我们后面详聊。2. 核心概念拆解zone、service、rich rule到底在说什么2.1 区域zone给网络接口分个“势力范围”zone是firewalld的基石。你可以把zone理解成一套预设的信任级别——不同的网络环境该开放的端口和服务自然不同。firewalld默认有九个zone从完全信任到完全拒绝排列区域名称默认信任级别适用场景trusted允许所有连接内网可信任环境、集群内部通信internal / home允许部分入站服务家庭或企业内部网络work允许部分入站服务办公环境public仅允许极少数入站服务公网服务器、不信任的网络external允许转发且做伪装NAT充当路由器边缘设备dmz仅允许特定服务进入隔离区DMZ区域的服务器block拒绝所有入站连接可疑网络drop丢弃所有入站连接无响应高威胁环境每个zone都有自己的规则集合开了哪些端口、允许哪些服务、哪些来源地址可以转发。网络接口比如eth0同一时间只能绑定一个zone默认情况下所有接口都挂在public上。如果你想给某个网卡单独设规则就得把它换到其他zone。我最早困惑的就是为什么我改了public的规则对我的服务器不起作用后来才意识到我服务器上有两个网卡流量走的是另一个网卡绑定的zone。所以配置之前先搞清楚你哪个接口对外服务它现在属于哪个zone这是第一优先级的事。2.2. 服务service端口和协议的逻辑集合service是firewalld提供的一个贴心抽象。比如HTTP服务配置层面上你只需要记住一个名字http而不是去记80/tcpHTTPS是https对应443/tcp。所有内置服务定义存在/usr/lib/firewalld/services/目录下一个个xml文件。用户自定义的服务放在/etc/firewalld/services/。一个服务文件通常长这样?xml version1.0 encodingutf-8? service shortMyWeb/short descriptionCustom web service on port 8080/description port protocoltcp port8080/ /service如果你不想自己写xml也可以直接用命令添加服务firewalld会帮你生成对应文件。后面实操部分会演示。2.3 富规则rich rule绕过简化模型的直达通道zoneservice的组合能覆盖绝大多数场景但总有例外你要针对某个IP开放指定端口要限制同一IP的并发连接数要记录特定来源的访问日志……这时候就该rich rule上场了。rich rule可以让你写出接近iptables语法的规则但它保持在firewalld的框架内由firewalld统一管理。它支持的动作包括accept、reject、drop、log还可以配合limit做速率控制。举个例子如果我想允许来自192.168.1.0/24网段的访问走3306端口其他来源一律拒绝用rich rule就是firewall-cmd --permanent --add-rich-rulerule familyipv4 source address192.168.1.0/24 port protocoltcp port3306 accept firewall-cmd --permanent --add-rich-rulerule familyipv4 port protocoltcp port3306 reject firewall-cmd --reload注意顺序很重要——第一条accept必须排在第二条reject前面因为firewalld对rich rule的匹配是顺序敏感的。这个“谁在前谁生效”的问题我第一次配的时候就搞反了结果所有人都被拒之门外大半夜调试了很久才明白过来。3. 策略配置实操从零开始配置一台服务器的防火墙3.1 环境准备与基础检查动手之前先看看你现在处于什么状态。登录服务器后我习惯先跑这几个命令摸清底细# 查看firewalld服务状态确认是否在运行 systemctl status firewalld # 查看默认zone及所有zone的简要信息 firewall-cmd --get-default-zone firewall-cmd --list-all-zones # 查看当前生效的zone和规则 firewall-cmd --get-active-zones如果你刚装的系统firewalld是默认开机启动的。如果没启动先把它拉起来并设为开机自启systemctl start firewalld systemctl enable firewalld这里有个极易踩的坑firewalld和iptables服务不能同时启用。CentOS 7里如果手动把iptables服务打开两个服务会争抢netfilter规则表现出的症状就是防火墙规则时灵时不灵非常诡异。正确的做法是只保留firewalld或者如果你实在习惯iptables就把firewalld mask掉。我个人推荐直接用firewalld——毕竟你已经装了系统也默认它没理由绕开。3.2 runtime与permanent为什么改了配置总是失效这是firewalld最核心、也最让人迷惑的特性之一。Runtime配置立刻生效但重启firewalld或系统后消失。Permanent配置持久化保存但不会立即生效要--reload之后才加载。所有firewall-cmd命令默认改的是runtime配置除非你加了--permanent参数。这个设计的初衷是好的——临时调试用的规则不需要写进配置文件规则设错了firewall-cmd --reload就能回到上次的持久化状态倒是个不错的回滚机制。但正因为它这种两级分离新手上路最容易出问题加了--permanent --add-port8080/tcp发现端口没通就以为是命令没用然后reload了一下发现刚才没加--permanent的端口规则的却不见了一脸懵。实操里我建议的步骤是先不带--permanent加runtime规则验证功能没问题后再带--permanent加一次最后reload。比如开放Nginx的80端口# 第一步runtime模式测试 firewall-cmd --add-servicehttp # 第二步验证端口确实通了 curl -I http://localhost # 第三步持久化 firewall-cmd --permanent --add-servicehttp # 第四步重载让持久化配置接管 firewall-cmd --reload这套流程虽然多敲几条命令但有效杜绝了“配了等于没配”的情况。3.3 常用配置命令速查端口、服务、IP和转发我用一张表整理一下日常高频命令方便你抄作业配置目标Runtime命令Persistent命令加--permanent开放TCP端口firewall-cmd --add-port8080/tcpfirewall-cmd --permanent --add-port8080/tcp开放UDP端口firewall-cmd --add-port53/udpfirewall-cmd --permanent --add-port53/udp开放一个服务firewall-cmd --add-servicehttpsfirewall-cmd --permanent --add-servicehttps删除端口firewall-cmd --remove-port8080/tcpfirewall-cmd --permanent --remove-port8080/tcp修改默认zonefirewall-cmd --set-default-zoneinternal同左绑定接口到zonefirewall-cmd --zoneinternal --change-interfaceeth1firewall-cmd --permanent --zoneinternal --change-interfaceeth1查看已开放的所有项firewall-cmd --list-all—重新加载配置firewall-cmd --reload—有个实用的细节指定端口时既可以用--add-port8080/tcp也可以定义服务来管理。端口和服务各有优势——端口简单直接服务则可以在多个zone之间复用规则一旦需要修改端口号只改服务定义文件就行不用挨个zone去改。我个人的习惯是单台服务器上偶发用的端口直接写--add-port需要跨多台服务器统一管理的端口就定义成服务。3.4 用富规则做精细化访问控制前面那张表解决的是“端口的开关”问题。但要处理“谁可以用、谁不可以用”的访问控制就得用rich rule。一个典型的场景数据库服务器只允许应用服务器网段连接3306运维管理要求从跳板机的固定IP连22。写法如下# 允许应用网段访问3306 firewall-cmd --permanent --add-rich-rulerule familyipv4 source address10.0.0.0/24 port protocoltcp port3306 accept # 拒绝其他一切3306访问 firewall-cmd --permanent --add-rich-rulerule familyipv4 port protocoltcp port3306 reject # 允许跳板机IP访问22其他SSH来源拒绝 firewall-cmd --permanent --add-rich-rulerule familyipv4 source address10.20.30.40 port protocoltcp port22 accept firewall-cmd --permanent --add-rich-rulerule familyipv4 port protocoltcp port22 reject firewall-cmd --reload注意reject和drop的区别reject是拒绝并返回错误响应客户端能立刻感知连接被拒绝drop是直接把包丢掉客户端会一直等到超时。公网上防扫描我喜欢用drop显得你“不存在”内部网络定位问题用reject反馈比较快。rich rule还有几个进阶参数比如给规则加日志。我可以把被拒绝的3306访问记录下来firewall-cmd --permanent --add-rich-rulerule familyipv4 source address0.0.0.0/0 port protocoltcp port3306 log prefixDB_ACCESS_DENIED levelwarning reject这样所有被拦的访问都会在/var/log/messages里留下带DB_ACCESS_DENIED前缀的日志配合grep就能做分析。3.5 NAT配置和流量转发firewalld还支持NAT和端口转发。如果你的服务器要充当网关或者给内网做端口映射这几个命令会比较常用。给外网接口开启NAT伪装让内网流量通过这个接口出网firewall-cmd --permanent --zoneexternal --add-masquerade把本机的80端口转发到内网某台机器的8080端口firewall-cmd --permanent --zonepublic --add-forward-portport80:prototcp:toport8080:toaddr192.168.1.100这两个操作背后对应的就是iptables的SNAT和DNAT规则。跨主机转发时流量经过的网卡要允许forwardfirewalld的zone默认策略对forward是允许的但如果你的/etc/sysctl.conf禁掉了ip_forward那也要开一下sysctl -w net.ipv4.ip_forward1 echo net.ipv4.ip_forward 1 /etc/sysctl.conf这个组合在实验室里模拟一个公司出口网关时我验证过配置不复杂但链路上一堆请确认的点内网接口绑internal、外网接口绑external、内网机器的网关指到这台服务器、外网接口起NAT捋熟了整个链路一次就能通。4. 策略设计思路zone选型与配置文件的层次关系4.1 如何为你的服务器选zonezone的工作模式决定了它要按信任边界来划分网络而不是按服务划分。你自己做主机的服务器通常一台机器一个zone就够做网关系统的才会用到多个zone。给你一个实际的选型参考服务器角色推荐zone理由公网Web服务器public只向外暴露80/443最小可用原则内网应用服务器internal对内提供服务允许内网网段信任访问数据库服务器internal rich rule基础开放给内网精确白名单控制访问来源网关/路由器external作为外口、internal作为内口外口做NAT内口提供内网服务跳板机/运维堡垒机drop为默认、逐个IP加白默认全关只放行运维来源IP4.2 配置文件在磁盘上的分布firewalld运行时的配置分为两层目录/usr/lib/firewalld/系统自带的默认配置不管你对不对它动手升级系统时都可能被覆盖。正常情况下不要去改这里。/etc/firewalld/系统管理员的自定义配置这个目录会覆盖系统默认值。你执行的每一条--permanent命令最终都会写到这里。如果你要手工修改配置文件直接编辑/etc/firewalld/zones/下的xml文件。比如public.xml看起来是这个样子?xml version1.0 encodingutf-8? zone shortPublic/short descriptionFor use in public areas. You do not trust the other computers on networks.../description service namessh/ service namehttp/ port port8080 protocoltcp/ /zone改完文件后执行firewall-cmd --reload让配置重新加载。这里要提醒一句尽量少手工编辑文件多用命令行操作。命令行会校验语法出错当场报错手改xml文件如果标签配错了reload之后不会立即报错而是等你访问出问题了才暴露排查链路特别长。我只在改服务定义这类命令行不方便操作的东西时才动xml。4.3 默认的public zone到底开了什么我记得第一次跑firewall-cmd --list-all看到默认zone Public下面的规则时有点意外——居然默认就开了ssh和dhcpv6-client。firewall-cmd --list-all # 输出示例 public (active) target: default icmp-block-inversion: no interfaces: eth0 sources: services: ssh dhcpv6-client ports: protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules:这个设计其实挺合理的SSH保证你还能连上服务器管理DHCPv6保证你的机器能拿到IPv6地址。如果你确认自己不需要可以从服务列表里移除但SSH我建议一直保留——否则你一个reload把自己锁外面了那真的只能去机房/控制台物理操作了。4.4 Direct Rules最后的手段firewalld提供了一个direct rules的机制允许你写原生的iptables命令。代价是这些规则firewalld在概念上看不懂只能原样传给内核由你全权负责。firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 9999 -j ACCEPT0代表这个规则插入到INPUT链的位置。我的态度非常明确能不用direct rule就不用。它绕过了firewalld的zone体系跟其他规则不在一个体系里管理排查时容易遗漏。真有特别复杂的规则需求优雅一点的方案是自己定义service或写rich rule。direct rule只能作为最后的逃生通道用了就要自己承担维护风险。5. 实战排错firewalld最常见的五个坑5.1 “我明明开了端口怎么还是连不上”这是我被问过最多的问题没有之一。排查顺序如下# 第一步确认firewalld真的在运行 systemctl status firewalld # 第二步确认当前生效的zone是哪个实际接口绑在哪 firewall-cmd --get-active-zones # 第三步确认你要的端口确实在列表里 firewall-cmd --list-ports --list-services经常出的问题就是端口是加了但加在了runtime配置里一个--reload就丢了或者根本没加--permanent。还有一种情况是系统里没别的防火墙了但SELinux还开着它把进程的端口访问给拦了。这种时候你去关firewalld是没用的得看SELinux的audit日志grep denied /var/log/audit/audit.log | tail -20SELinux对网络监听的限制没有对文件访问那么强但确实是会让特定端口无法访问的量——总之排查时把它当成一个可能因素。5.2 重启后规则全部丢失多半是因为所有规则都只加了runtime模式没有--permanent。把这句牢牢记死**不带--permanent的规则重启必然消失。**反之带--permanent但没reload的规则进程还在但旧的生命周期也没看到新配置。二者缺一不可。5.3 多网卡服务器规则对某一个IP不生效服务器有两个网卡一个对外一个对内你给public加了端口白名单怎么走内网IP就是不通因为内网网卡绑定的zone根本不是public。看下这个firewall-cmd --get-active-zones如果eth1在internal里那你要给internal加规则。还有一种情况是你用了--add-interface绑定网卡结果新网卡热插拔后firewalld自动把它接到默认zone而默认zone又不容忍对应协议于是出现奇怪的行为。5.4 rich rule顺序引发规则“不生效”rich rule按顺序匹配一旦命中就执行对应动作后面的规则不会再判。我之前那个先reject后accept的坑还记得吧如果reject写在accept前面那所有人都会被拒绝后面的accept永远不会执行到。所以配置的顺序就是策略优先级先写最具体的白名单再写兜底的拒绝。5.5 firewalld与docker、k8s的端口冲突容器网络是运维同行们普遍抱怨的一个点。Docker默认会直接操作iptables的DOCKER链firewalld的reload会重置某些iptables链导致容器端口映射失效。这里给几条实测有效的建议如果服务器只跑Docker可以考虑把firewalld停止直接用Docker的iptables管理能力。如果服务器同时还有常规服务需要firewalld管那配置好端口转发时把Docker的映射端口也加到firewalld的规则里避免两者打架。生产环境用Docker尽量配合--network host模式跑服务或者用docker swarm/k8s自带的外层负载均衡器不要依赖-p端口映射和firewalld这套体系硬碰硬。我在某个模拟项目X里负责维护一套跑在CentOS 7上的旧版单机Docker经常出现“容器起来了但外部访问不了”的问题最后排查发现就是firewalld reload重置了FORWARD链。后来把需通行的流量规则写成direct rule并固定插入位置才稳定下来。6. 经验总结firewalld与iptables的共存判断标准这篇文章核心的部分都写完了最后说一点我个人的实践经验。firewalld在CentOS 7里不是摆设也不是一个多余的抽象层。它解决的痛点是iptables规则难以组织、难以理解、难以批量维护。zone这套模型把访问控制从网络五元组上升到了业务语义层面对多网卡、多环境、多服务的场景提升非常显著。但如果你对netfilter的底层机制越熟悉你在用firewalld的时候就越有底气。遇到诡异问题看一眼iptables的nat表和filter表立刻就知道firewalld到底翻译出了什么东西。我建议所有CentOS 7的运维都至少花半天时间搞明白netfilter的链、表和iptables的基础用法再来用firewalld就游刃有余了。至于什么时候该放弃firewalld回到iptables我个人的判断标准是如果你整天要写一堆自定义链、做高度定制化的包处理、搞队列控制、做内核模块级别的过滤那你确实不需要firewalld这层封装。但这种情况在普通服务器运维里极少发生——大部分人的需求zoneservicerich rule完全够用。最后再给大家分享一个小技巧新装一台CentOS 7服务器别急着配业务先把防火墙策略想清楚。默认zone选什么、SSH来源IP白名单是什么、要对外暴露哪些服务这几点想明白再动手能省掉后面很多故障时间。我就是吃过“先跑业务、再补防火墙”的亏——业务上线后补策略总担心漏配端口影响线上服务压力完全不在一个量级。