CentOS 7 firewalld 白名单配置实战:从端口开放到IP限制

📅 发布时间:2026/9/18 21:21:05
CentOS 7 firewalld 白名单配置实战:从端口开放到IP限制
今天早上刚到办公室就看到群里有人在喊“MySQL 连不上了3306 端口不通。”我第一反应不是去看数据库而是先问了一句“你上个月是不是动过防火墙”对方沉默了半分钟回了个“好像是加过一条规则”。这种事在工程上太常见了配置防火墙从来不是写完就完事而是要搞清楚你写的规则到底在哪个层面生效、优先级是什么、保不保存——尤其是 CentOS 7 自带的 firewalld它的规则体系跟老一代 iptables 的写法差异不小很多人吃亏就吃在拿旧思路套新工具。这篇东西我磨了很久才决定写。CentOS 7 的 firewalld 说简单也简单无非是开放端口、限制 IP、加白名单这几件事说复杂也复杂因为里面有 zone、runtime/permanent、rich rule、source 和 port 这些概念绕在一起网上零散教程不少但能把“IP 白名单”和“端口白名单”组合起来讲透的不多。我打算把自己在服务器上实操过的完整思路、命令、踩坑记录都梳理出来从基础概念到 3306 端口只允许内网访问这种真实场景一次性讲明白。无论是刚入门的小白还是被防火墙坑过几次的老手这篇都能给你省点时间。1. 先搞清楚 firewalld 的底细再动手配白名单很多人上来就敲命令敲完发现不生效或者不该通的反而通了根本原因是没有理解 firewalld 的工作方式。CentOS 7 默认的防火墙已经从 iptables 换成了 firewalld底层虽然还是 netfilter但管理逻辑完全不同。你写的每一条规则本质上是“动态生效”的而且它引入了 zone区域的概念这决定了你的白名单配置最终会长成什么样。1.1 防火墙没启动时系统到底安不安全先说一个反直觉的事实如果你的 firewalld 服务处于 stopped 状态那服务器上是没有任何防火墙规则的等于所有端口裸奔。很多人以为“我没有启动防火墙那系统是不是默认拒绝所有连接”这个认知在 CentOS 7 上是错的。firewalld 只是一个管理工具它不运行就不加载任何规则。你甚至可以直接在命令行执行systemctl status firewalld查看如果输出是inactive (dead)那当前所有端口包括 22、3306、6379都是对外暴露的。所以验防火墙问题的第一步永远不是改规则而是先确认服务状态systemctl status firewalld systemctl is-enabled firewalld第一条看运行状态第二条看开机自启状态。我在实际排障中发现很多“端口不通”的问题根本不是白名单没配而是 firewalld 压根没启动或者启动了但规则临时清空了。这里补一句firewalld 正常启动后默认的 public zone 会放行 SSH22 端口以及 DHCP、ICMP 这些基础协议其他端口默认拒绝。你要是上手就把 public zone 改成 drop那 SSH 也可能断这是新手最容易犯的错。1.2 区域zone概念是白名单配置的地基firewalld 里的 zone 相当于一组策略的集合每个 zone 有自己的默认行为允许什么、拒绝什么、转发什么。系统默认有九个 zone最常用的是这三个Zone名称默认行为适用场景public只放行指定服务和端口其余拒绝外网服务器默认区域trusted所有连接都接受内网、可信网段drop直接丢弃所有入站连接极端安全场景配置白名单这件事本质上就是“把一个来源 IP 网段归属到某个 zone”或者“在某个 zone 里对特定来源 IP 放行特定端口”。很多人不理解 source 的优先级这里我强调一个关键点当一个连接同时命中了网卡绑定的 zone 和来源 IP 绑定的 zone 时来源 IP 绑定的 zone 优先级更高。比如网卡 eth0 默认在 public zone你额外把 192.168.1.0/24 加到了 trusted zone那么来自这个网段的流量会走 trusted 的规则而不是 public 的规则。这个特性特别适合白名单场景后面实战部分我再细说。2. 开放端口的基础操作别一上来就问白名单我遇到过不少人需求一开口就是“我要配白名单”但一问具体要放行什么服务自己都说不清楚。配白名单之前你至少得先知道端口怎么放行因为很多场景下“仅白名单可访问”就是在“开放端口”的基础上叠加来源限制。先把手动开放端口的操作垫扎实后面才能少踩坑。2.1 一条命令开放 TCP 端口但要注意 permanent开放单个端口的标准命令是firewall-cmd --zonepublic --add-port8080/tcp这条命令会立刻生效但只在当前运行时有效服务器重启或者 firewalld reload 之后就没了。想永久生效必须加上--permanent参数然后执行 reloadfirewall-cmd --zonepublic --add-port8080/tcp --permanent firewall-cmd --reload顺序上有个小细节如果是先执行了不带--permanent的命令再执行带--permanent的命令两个都会存在一个是 runtime 状态一个是 permanent 配置。你不 reload 还好一 reloadruntime 状态的规则就全丢了只有 permanent 的还在。所以我的习惯是要么全程只操作 runtime测试没问题后再全部加 permanent要么从一开始就统一带--permanent。最怕的是两条命令混着敲自己都分不清哪条是临时哪条是永久。2.2 端口范围、UDP 端口以及批量添加的讲究有时候你需要放行的不止一个端口比如 Web 服务的 8000 到 9000或者游戏服务器的 UDP 范围。firewalld 支持端口段写法firewall-cmd --zonepublic --add-port8000-9000/tcp --permanent firewall-cmd --zonepublic --add-port1000-2000/udp --permanent端口段和单个端口写法几乎一样只需要把端口改成起始-结束的格式。但需要注意TCP 和 UDP 是两条独立的规则你不能写--add-port53/tcpudp必须分别添加。批量添加多个不连续端口时firewalld 没有类似--add-ports的复数参数我是用 shell 循环处理的for port in 8080 8443 9090; do firewall-cmd --zonepublic --add-port${port}/tcp --permanent done firewall-cmd --reload这里有个经验之谈批量添加后列出所有规则时你会看到每个端口单独一条记录规则数量多了以后不太好维护。所以端口规划很重要能合并成端口段的尽量合并否则后面查问题的时候firewall-cmd --list-all输出的规则列表能刷你两屏。2.3 验证端口放行是否生效别靠“感觉”命令敲完规则也加了但端口到底通不通不能靠猜。我一般分三步验证第一步看端口是否在监听确认是服务本身在监听而不是防火墙的问题ss -lntp | grep 8080第二步本机验证防火墙规则是否已加载firewall-cmd --zonepublic --list-ports第三步从外部机器测试端口连通性telnet 192.168.1.10 8080 # 或者 nc -vz 192.168.1.10 8080这一步很多人容易忽略总在服务器上自己测自己其实测的是服务监听不是防火墙。正确做法是找一台别的机器去连或者用你本地的电脑执行 telnet。如果你服务器上没装 telnet也可以用curl -v telnet://192.168.1.10:8080测但最简单粗暴的还是 nc。验证这一步做好后面配白名单的时候才能确定问题到底出在哪一环。3. IP 白名单从单个地址到整段内网端口放行是“打开门”IP 白名单是“只让特定的人进门”。在 firewalld 里实现 IP 白名单有几种思路每种思路的适用场景不太一样配置方式也不同。我建议你先想清楚自己的需求是“某个来源 IP 可以访问服务器的所有服务”还是“某个来源 IP 只能访问某一个端口”这两种场景对应的命令差别很大。3.1 把单个来源 IP 加入白名单如果你只是想让某个固定的公网 IP 能访问服务器最简单的做法是把该 IP 加入白名单区域firewall-cmd --permanent --zonetrusted --add-source203.0.113.5 firewall-cmd --reload这样 203.0.113.5 这个 IP 访问服务器时流量会命中 trusted zone 的规则trusted 默认信任所有连接。这个命令看起来很省事但你得清楚一个副作用这个 IP 访问服务器的所有端口都会被放行不仅仅是 22 或 80。如果你只想让这个 IP 访问特定的服务千万别直接把 IP 扔进 trusted否则安全边界就没了。我见过有人为了省事把合作方的 IP 全加进 trusted结果对方能直接访问 Redis 端口还好 Redis 配置了 requirepass不然后果不堪设想。如果不想用 trusted 这种全放行的方式可以留在 public zone单独添加来源地址效果是“这个来源的流量按 public zone 的规则处理”firewall-cmd --permanent --zonepublic --add-source203.0.113.5 firewall-cmd --reload这样设置之后public zone 里放行的端口比如 80、443对这个 IP 有效其他未放行端口依然拒绝。这两种方式用哪个取决于你对“白名单”的定义是“全部放行”还是“按规则放行”。3.2 配置整段 IP 网段白名单很多时候你面对的不止一个 IP而是一个网段比如公司办公网可能是一个 192.168.1.0/24 的地址池。配置整段网段白名单和单个 IP 的语法几乎一样只需要把 IP 地址改成 CIDR 格式firewall-cmd --permanent --zonetrusted --add-source192.168.1.0/24 firewall-cmd --reload这里我特别提醒一个点192.168.1.0/24和192.168.1.1/24写成哪种都能命中整个网段因为 CIDR 的匹配只看网络位。但养成用.0作为网段地址的习惯更规范也方便别人看懂你的规则。如果你的内网分多个网段比如办公网 192.168.1.0/24、测试网 192.168.2.0/24、运维网 192.168.3.0/24你可以一次性加多个 sourcefirewall-cmd --permanent --zonetrusted --add-source192.168.1.0/24 firewall-cmd --permanent --zonetrusted --add-source192.168.2.0/24 firewall-cmd --permanent --zonetrusted --add-source192.168.3.0/24 firewall-cmd --reload这么配的优点是规则简单清晰运维同事一看就懂排查问题不用猜。缺点是 trusted zone 对这些网段全放行内部安全靠内网自己的安全措施去兜底。3.3 用网卡绑定 zone 来区分内外网还有一类场景比较特殊服务器有两张网卡一张接内网一张接外网。这种情况下最好的方案不是用 source 白名单而是让不同网卡归属不同 zone。内网网卡绑 trusted外网网卡绑 public这样所有从内网卡进来的流量天然可信外网卡继续保持严格过滤。查看当前网卡和 zone 的绑定关系firewall-cmd --get-active-zones把指定网卡绑定到指定 zonefirewall-cmd --permanent --zonetrusted --change-interfaceeth1 firewall-cmd --permanent --zonepublic --change-interfaceeth0 firewall-cmd --reload这种方式比单纯加 source 更细粒度而且在有多网卡的物理服务器或带多网口的云主机上非常实用。不过有个前提条件你要确认服务器到内网其他机器的流量确实走的是 eth1如果路由写得不清楚流量从 eth0 出去了那绑定 eth1 到 trusted 就白搭了。配置完可以用ip route看默认路由走哪张网卡再配合 tcpdump 抓包确认。4. 端口级白名单指定端口只允许指定 IP 访问前面几步讲的都是“放开某个 IP 或网段”接下来这个才是真正的重头戏某端口只允许指定的 IP 访问其他来源一律拒绝。比如 MySQL 3306 端口只允许应用服务器 IP 访问或者管理面板 9090 端口只允许公司办公网访问。这类需求靠普通的--add-port和--add-source配不出来需要用 firewalld 的富规则也就是 rich rule。4.1 rich rule 语法与常用写法rich rule 是 firewalld 提供的高级规则语法支持按来源地址、目的端口、协议、动作accept/reject/drop组合出一个完整的访问控制策略。最简单的写法如下firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address192.168.1.100 port protocoltcp port3306 accept firewall-cmd --reload这条规则表达的意思是来源 IP 为 192.168.1.100 的机器访问本机 TCP 3306 端口时允许通过。其他来源访问 3306 端口时如果 public zone 没有设置放行 3306会被默认拒绝。rich rule 的语法有几个细节容易踩坑。第一familyipv4和source address必须搭配如果你不写family规则默认只对 IPv4 生效这倒没问题但如果你写familyipv6又配上 IPv4 地址规则直接不生效。第二port和protocol必须同时出现protocoltcp和port3306两个参数缺一不可。第三rich rule 里的accept是优先级最高的动作一旦来源 IP 命中这个规则直接放行不会再看后面的默认策略。4.2 一个端口、多个白名单 IP 段怎么配端口级白名单遇到多个 IP 或网段时可以写多条 rich rule一条对应一个来源。比如数据库 3306 端口需要同时允许 192.168.1.0/24 和 10.0.0.5 访问firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address192.168.1.0/24 port protocoltcp port3306 accept firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address10.0.0.5 port protocoltcp port3306 accept firewall-cmd --reload这种方式直白每条规则独立后续要移除某个 IP 段也很方便firewall-cmd --permanent --zonepublic --remove-rich-rulerule familyipv4 source address10.0.0.5 port protocoltcp port3306 accept firewall-cmd --reload不过当白名单 IP 特别多的时候比如上百个逐条写 rich rule 就太啰嗦了。这种情况我建议用 ipset 来管理先在系统层面创建一个 ipset 集合然后把所有白名单 IP 都加进去最后在 rich rule 里直接引用这个集合。具体做法是ipset create allow_mysql_ips hash:ip ipset add allow_mysql_ips 192.168.1.100 ipset add allow_mysql_ips 10.0.0.5然后在 firewalld 里引用 ipsetfirewall-cmd --permanent --zonepublic --add-rich-rulerule source ipsetallow_mysql_ips port protocoltcp port3306 accept firewall-cmd --reloadipset 的优势是规则只写一条后续增删 IP 只需要操作 ipset不用频繁 reload 防火墙对生产环境很友好。4.3 先拒绝所有来源再放行白名单还有一类需求更严格某端口不允许任何来源访问除非来源 IP 在白名单里。这里的一个坑在于如果你之前用--add-port已经把该端口放开了那么 rich rule 里的reject可能并不会像你想的那样“覆盖”之前的放行规则。我实际操作中最稳妥的方式是两步走。第一步移除 public zone 里对该端口的通用放行规则firewall-cmd --permanent --zonepublic --remove-port3306/tcp第二步用 rich rule 放行白名单 IPfirewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address192.168.1.0/24 port protocoltcp port3306 accept这样处理之后public zone 里没有针对 3306 的通用放行规则默认拒绝所有来源而 rich rule 会让 192.168.1.0/24 网段的流量直接命中 accept。很多教程只讲第二步不讲第一步导致有人配完白名单之后发现其他 IP 还能访问端口其实就是因为端口本身已经被--add-port放行了。如果你希望操作更透明、可追溯我建议在配置端口级白名单前先用firewall-cmd --list-all看看当前 zone 里已有的规则确认端口有没有被通用放行。这个习惯能帮你省掉很多查问题的时间。5. 实战MySQL 3306 端口只允许内网网段访问很多后台系统的架构里数据库服务器和应用服务器是分离的应用服务器需要远程连接数据库但数据库绝对不能对公网开放。这种场景下你需要配置的就是“3306 端口只允许应用服务器所在网段的 IP 访问”这也是我运维生涯里遇到最多的白名单需求之一。5.1 需求拆解与方案设计假设当前环境如下数据库服务器 IP 为 172.16.0.10应用服务器网段为 172.16.1.0/24运维人员办公网段为 10.10.10.0/24root 用户通过堡垒机登录数据库服务器做日常运维。需求是 3306 端口只能被 172.16.1.0/24 访问运维人员如果需要直连数据库则通过堡垒机再跳转所以 10.10.10.0/24 不需要直接放行 3306。这种场景如果我直接给 172.16.1.0/24 配一个 trusted zone那等于这个网段的机器能访问数据库服务器上所有端口安全边界太粗。正确的设计是数据库服务器保持在 public zone通过 rich rule 只放行 172.16.1.0/24 访问 3306 端口其余流量继续走 public zone 的默认策略。这样的话即使应用服务器网段里有一台机器被入侵了它也只能访问 3306 端口无法横向触碰服务器上的其他服务。5.2 完整配置步骤执行前先确认当前防火墙状态和已有规则避免旧规则干扰systemctl status firewalld firewall-cmd --list-all如果当前已有对 3306 端口的通用放行规则先移除firewall-cmd --permanent --zonepublic --remove-port3306/tcp然后添加 rich rule放行应用服务器网段firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address172.16.1.0/24 port protocoltcp port3306 accept重载防火墙并确认规则已生效firewall-cmd --reload firewall-cmd --list-all firewall-cmd --zonepublic --list-rich-rules这一步我通常会多看一眼--list-rich-rules的输出确认规则没有被写错尤其是引号、空格这样的细节。rich rule 是整体作为一个参数传给 firewall-cmd 的一旦引号没配对命令要么报错要么写出一条和你预期完全不同的规则。还要注意如果服务器上装了 MySQL你确认端口在监听ss -lntp | grep 3306如果 MySQL 没启动或者监听在 127.0.0.1 而不是 0.0.0.0那防火墙配得再对应用服务器也连不上。5.3 验证与回滚配置完成后我从三台不同位置的机器分别测试连通性一台在 172.16.1.0/24 网段内、一台在 172.16.2.0/24 网段外、一台在数据库服务器本机。如果是 172.16.1.0/24 内的机器执行mysql -h172.16.0.10 -P3306 -uroot -p预期结果是能正常连接。如果是 172.16.2.0/24 的机器执行同样的命令预期结果是连接超时或直接被拒绝。这里注意如果连接超时的等待时间很长多半是流量被 drop 了如果立刻提示拒绝则是被 reject。firewalld 的默认动作里public zone 对未放行端口的处理是 reject会立刻返回 connection refused所以在外部测试时看到这个提示反倒说明防火墙规则是生效的。如果验证过程中发现规则有问题需要回滚操作也很直接把刚才添加的 rich rule 移除即可firewall-cmd --permanent --zonepublic --remove-rich-rulerule familyipv4 source address172.16.1.0/24 port protocoltcp port3306 accept firewall-cmd --reload我建议你在生产环境做类似操作时先把新规则加上并确认没问题后再用--reload切换这样如果规则写错了旧的 runtime 规则还在还能快速恢复。复杂防火墙变更前最好先把/etc/firewalld/zones/目录备份一下这条路径存放了所有自定义 zone 配置备份成本很低但出问题时能救命。6. 配置白名单时踩过的坑和排查思路firewalld 配置本身不难难的是出了问题怎么查、怎么恢复。我把自己这几年实际踩过的坑整理了一份排查清单按问题出现的频率排序你在生产环境操作时可以直接对照参考。6.1 配置了白名单但外网还是能访问先查这两处遇到这种问题我从来不去纠结防火墙命令有没有写对而是先执行firewall-cmd --list-all --zonepublic重点看两处一是该端口是不是已经通过--add-port放行了二是 rich rule 是不是真的在列表里。如果端口本身被--add-port放行rich rule 里的reject不会覆盖它流量依然能通过。如果 rich rule 在列表里那就要检查是不是配置到了错误的 zone比如你明明想限制 public zone但实际规则加到了 trusted zone。可以用下面命令查所有 zone 的规则firewall-cmd --list-all-zones还有一个容易忽略的点如果服务器上同时运行着 DockerDocker 的 iptables 规则可能会绕过 firewalld直接操作 NETWORK 层的规则。这种场景下即使你在 firewalld 里配置了拒绝规则Docker 容器发布到宿主机的端口可能依然能从外部访问。这个问题的根治方法比较麻烦涉及 Docker 的 iptables 管理方式但至少排查思路要清楚先看iptables -L -n有没有 Docker 插入的规则再判断是不是 firewalld 被架空了。6.2 permanent 和 runtime 规则不一致导致 reload 后规则丢失firewalld 启动时加载 permanent 配置运行过程中可以临时添加 runtime 规则。如果你忘记加--permanent直接执行了--reload运行时规则全部丢失。这个坑在操作频繁的服务器上特别常见尤其是先测试后落地的工作流里。我的建议很简单调整防火墙规则时要么全部带--permanent要么明确区分临时和永久。如果只是想临时放行某个端口测试五分钟那就不要带--permanent测完直接移除如果是正式配置就一步到位加--permanent然后 reload。最忌讳的是混着用还不做记录等出了事故你根本分不清当前规则哪些是永久的、哪些是临时的。6.3 远程操作防火墙导致 SSH 断连如何恢复这是防火墙配置里最经典的事故之一。你在远程服务器上执行了一条防火墙规则把 SSH 端口关了或者把当前 IP 拒绝了结果连接瞬间断开人直接进不去了。这种情况如果云厂商控制台有 VNC 或网页终端可以用那个登录如果没有那就只能重启服务器让防火墙重新加载 permanent 配置。不过更靠谱的是预防手段。第一确保 SSH 端口始终在 public zone 的 services 列表里firewall-cmd --permanent --zonepublic --add-servicessh第二如果你要限制 SSH 只允许特定 IP 访问建议先加白名单规则再加拒绝规则而且两步之间不要立即 reload确认没问题再 reload。我的习惯是把ssh这个 service 保留在 public zone 里用 source 白名单限制来源而不是直接把ssh从 service 列表里删掉这样即使白名单配置出错至少还有sshservice 在兜底。6.4 用 ipset 提升大量 IP 的管理效率前面提过 ipset这里再展开一点。当白名单 IP 数量上了几十个以后逐条写 rich rule 会变得很痛苦规则的显示输出也很长。ipset 的方案更优雅yum install -y ipset ipset create allow_ssh_ips hash:ip ipset add allow_ssh_ips 203.0.113.1 ipset add allow_ssh_ips 203.0.113.2然后在 rich rule 里引用 ipsetfirewall-cmd --permanent --zonepublic --add-rich-rulerule source ipsetallow_ssh_ips service namessh accept这样后续增加白名单 IP只需要执行ipset add不用动防火墙配置。而且 ipset 支持 hash:net 类型可以存网段ipset create allow_networks hash:net ipset add allow_networks 192.168.1.0/24对于经常变动的白名单列表ipset 的维护成本比 rich rule 低得多。不过需要提醒的是ipset 集合本身不持久化服务器重启后 ipset 列表会清空所以在生产环境中最好把 ipset 的创建和添加命令写进启动脚本或者用 systemd 服务来管理否则重启后白名单就失效了。6.5 清理规则的正确姿势先查后删最后再分享一个日常维护建议。防火墙规则是会累积的时间一长你自己都可能忘了当初为什么加某条规则。所以我的习惯是每隔一段时间就执行一次firewall-cmd --list-all-zones把所有规则过一遍看到已经不用的端口白名单、已经下线的网段就顺手清理掉。删除规则的命令和添加规则基本对称firewall-cmd --permanent --zonepublic --remove-rich-rulerule familyipv4 source address192.168.1.100 port protocoltcp port3306 accept firewall-cmd --permanent --zonepublic --remove-source192.168.1.0/24 firewall-cmd --reload在执行删除之前我建议先看一眼规则内容用firewall-cmd --zonepublic --list-rich-rules查全部富规则确认要删的是哪一条避免手滑把别的白名单误删了。清理完再列一次规则确保结果符合预期。这个习惯看着简单但真能在关键时刻避免事故——我就见过有人清理规则时把整个 zone 的规则清空了导致线上服务端口全开、裸奔了一晚上。