恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Linux防火墙firewalld只允许特定IP访问配置与排查实战
首页
资讯中心
/
Linux防火墙firewalld只允许特定IP访问配置与排查实战
Linux防火墙firewalld只允许特定IP访问配置与排查实战
发布时间:2026/9/17 1:38:45
1. 需求先拆清楚什么场景下才叫只允许特定IP访问去年帮一个朋友收拾一台被暴力破解的测试机登进去一看/var/log/secure里刷了几万条失败登录记录来源IP换着花样来。他当时的诉求很朴素这台机器只给自己公司的两台办公电脑和一台跳板机连其他一律别进来。这就是Linux防火墙firewall只允许特定IP访问最典型的落地场景——不是把服务关掉而是把访问来源收窄到一个可信集合里。这件事看着简单实际动手时坑不少。有人上来就firewall-cmd --add-rich-rule结果发现原来的规则没删放行还是全开有人改完没加--permanent重启后白名单消失还有人配置完发现规则确实生效了但本机上的容器网络、监控采集、备份同步一起挂了。所以我在动手前习惯先把需求拆清楚搞清楚到底是限制某个端口还是限制整台机器是限制入站还是连出站一起管这决定了后面用哪种方案。这篇文章适合三类人看一是运维新手手上有一台公网Linux主机需要收敛访问来源二是开发同学本地或测试环境的服务不想被同事以外的人扫到三是有一定基础但被 firewalld 的 zone、source、rich rule 绕晕过的人。我会把机制讲透把配置步骤写全把踩过的坑摊开说尽量做成一份可以直接照着抄的作业。1.1 三种容易混淆的访问控制需求只允许特定IP访问这句话落到具体配置上至少对应三种不同粒度的需求混在一起想就容易做错。第一种是限制单个服务的来源。比如只让 192.168.10.20 这台机器连 SSH22端口其他端口该开还开Web 服务照样对全网提供。这种需求最精细通常用 rich rule 精确到端口和源地址或者把服务搬到一个专门的 zone 里。第二种是限制整台主机的全部入站。典型是内网数据库服务器只允许应用服务器网段访问其他任何端口都不对外开放。这种更适合用 zone 的 source 绑定把允许的源 IP 划进一个 zone接口默认落在拒绝策略的 zone 上。第三种是限制出站方向。有些环境要求服务器只能主动访问指定的几个地址防止被入侵后往外回传数据。这种在 firewalld 里要做的是 egress 方向的 zone 和 direct rule复杂度比入站高得单独设计。我见过最多的失败案例是把第一种需求用第三种思路去配写了一大堆出站规则结果入站还是敞着的。所以第一步永远是把自己的需求对号入座写下来我要限制的是___服务端口是___允许的来源是___其他流量应该被拒绝还是被丢弃。1.2 从五元组视角看防火墙到底在判断什么很多人配防火墙是靠背命令不理解判断逻辑一出问题就抓瞎。其实防火墙判断一个包看的就是五元组源IP、目的IP、源端口、目的端口、传输层协议。你在 firewalld 里写的每一条规则本质上都是在描述这五个字段的匹配条件。拿 SSH 举例一个来自 192.168.10.20 的登录请求五元组大致是源IP192.168.10.20、目的IP本机地址、源端口某个随机高端口、目的端口22、协议TCP。你要放行的规则就是匹配源IP是192.168.10.20、目的端口22、协议TCP这些条件。反过来来自其他地址的同类请求匹配不上规则就落到 zone 的默认策略上被丢弃。这里有个容易被忽略的点源端口是随机的。客户端每次连接用的源端口都不一样所以你写规则时基本只约束目的端口不要试图去限制源端口否则规则永远不会命中。这也是为什么很多白名单规则写出来看着没问题实际一个包都匹配不上——方向搞反了。提示写规则前先在脑子里过一遍五元组把谁访问谁、走哪个端口、用什么协议这四件事说清楚规则基本就不会写错方向。1.3 为什么这套场景我更倾向用 firewalld 而不是纯 iptablesLinux 上做访问控制底层其实是 netfilterfirewalld 和 iptables 都是它前面的管理工具。CentOS 7 之后默认装的是 firewalldRed Hat 系、Rocky、Alma 这些发行版基本都是这个路子。既然底层一样为什么还挑 firewalld原因很实际。iptables 的规则是一次性快照iptables -A INPUT ...加进去之后只要你没写保存脚本重启就没了规则多了以后插入顺序、链的跳转关系维护起来非常痛苦。firewalld 是动态管理有 runtime 和 permanent 两层配置改完--reload就能生效还能用 zone 把规则按场景归类可读性好得多。另一个好处是 firewalld 自带 ipset 集成。当你需要放行的来源 IP 有几十上百个时用 iptables 就得一条条写规则表膨胀得很快firewalld 可以定义一个 ipset 集合一条规则引用整个集合性能和维护成本都好很多。当然也不是没有代价。firewalld 的抽象层多出问题时你要多绕一层去查真实的 nftables/iptables 规则而且它和 Docker 这类自己直接操作 iptables 的程序容易打架这个后面会专门讲。但综合来看日常运维场景我还是首选 firewalld除非环境里已经有成熟的 iptables 管理体系。2. firewalld 的匹配逻辑与方案选型理解了需求接下来要搞清楚 firewalld 内部是怎么匹配规则的。这块如果只记命令不理解顺序配出来的规则很可能看起来对、用起来不对白名单加了个寂寞。2.1 zone 的匹配优先级source 先于 interfacefirewalld 把规则组织成一个个zone区域每个 zone 是一套独立的策略集合里面可以有允许的服务、端口、源地址、目标策略。系统里默认有 public、trusted、drop、block 等若干 zone也可以自己 new 一个。关键问题是一个进来的包到底按哪个 zone 的规则走firewalld 的匹配顺序大致是这样如果包的源地址匹配了某个 zone 里配置的 source就走这个 zone否则如果包进入的网卡接口绑定了某个 zone就走这个 zone都不匹配走默认 zone一般是 public。这个顺序是整个白名单方案的基石。source 的优先级高于 interface意味着你可以让同一张网卡同时属于多个策略来自可信 IP 的流量走宽松的 zone其他流量走默认 zone 被拒绝。这正是只允许特定IP访问最优雅的实现方式——不用删掉原来的服务配置只需要让可信来源走另一条路。我在实际配置里经常这么干接口留在 publicpublic 里该开的 Web、监控端口照常开但把 SSH 从 public 里删掉只在一个专门的ssh-whitelistzone 里开 SSH并把这个 zone 的 source 设成允许的 IP。这样即使 public 里其他端口暴露SSH 这个最敏感的入口也是收着的。2.2 target 决定了没被允许的流量是什么下场zone 里有个很重要的属性叫target它决定了没有命中任何放行规则的流量如何处理。常见取值有target 值行为适用场景default拒绝入站、允许出站ICMP 有部分例外普通公网主机ACCEPT全部放行trusted 这类完全信任区域%%REJECT%%主动拒绝并回一个拒绝包想让对方快速失败DROP静默丢弃不回应不想暴露主机存在这个属性特别容易被忽略。很多人配白名单时只在 zone 里加了一条允许 192.168.10.20 访问22然后就以为完事了结果发现其他 IP 照样能连——因为 zone 的 target 是default没错但 zone 里可能还挂着sshservice那条规则对所有来源生效rich rule 的允许只是多开了一扇门原来的大门并没关上。所以铁律是白名单的本质是先关后开。要么把宽泛的 service/port 规则删掉要么把 zone 的 target 收紧只留 rich rule 这一条通路。只加不减等于没配。2.3 三种白名单方案的横向对比具体到只允许特定IP访问firewalld 下有三条主流路线各有适用面方案核心命令优点局限推荐场景source 绑定 zone--add-source--new-zone结构清晰一个源一个策略只能限制整个 zone 的服务集合整机收敛、内网服务器rich rule--add-rich-rule精确到端口、协议、源规则多了可读性下降单服务限制、精细控制ipset 集合--new-ipset 引用大批量IP管理高效语法略绕调试稍麻烦几十个以上来源IP我的选择习惯是这样来源 IP 少于 5 个、只限制一两个端口直接上 rich rule最直观如果要限制整台机器所有端口、来源是一整个网段用 source 绑定 zone如果来源 IP 是动态更新的、数量几十上百那就必须上 ipset否则每次加 IP 都要 reload维护成本太高。注意三种方案不要混用在同一批流量上。曾经见过有人既配了 source zone 又配了 rich rule两边规则冲突排查了半天才发现是优先级问题。同一需求选一条路线走到底。3. 动手实操三种白名单配置全流程理论说完直接上机操作。下面的命令我都以限制 SSH22端口为例实际操作时把端口和服务名换成你自己的即可。所有命令默认在 root 或有 sudo 权限的账户下执行。3.1 动手前的环境确认与备份先确认三件事firewalld 在跑、状态正常、当前配置心里有数。# 确认 firewalld 服务状态 systemctl status firewalld # 查看当前默认 zone 和活动 zone firewall-cmd --get-default-zone firewall-cmd --get-active-zones # 查看当前 zone 的完整配置重点看 services、ports、sources、rich rules firewall-cmd --list-all把firewall-cmd --list-all的输出完整复制到本地存一份这是你的后悔药。另外把整个配置目录也备份一下tar -czf /root/firewalld-backup-$(date %F).tar.gz /etc/firewalld提示远程操作防火墙前务必先开一个独立的第二个 SSH 会话挂着别关。万一规则写错把自己锁在门外这个老会话还能救你。这是我用血泪换来的习惯有一次手滑把 source 写错靠着一个没关的旧会话才恢复回来。3.2 方案一source 绑定独立 zone做整机收敛这个方案的思路是新建一个只对可信 IP 开放的 zone把 SSH 放进去然后从默认 zone 里删掉 SSH。# 1. 新建一个专用 zone firewall-cmd --permanent --new-zonetrusted-admin # 2. 把允许的源地址加进这个 zone支持单个IP和网段 firewall-cmd --permanent --zonetrusted-admin --add-source203.0.113.20/32 firewall-cmd --permanent --zonetrusted-admin --add-source198.51.100.0/24 # 3. 在这个 zone 里放行 SSH firewall-cmd --permanent --zonetrusted-admin --add-servicessh # 4. 把 SSH 从默认 zone 里删掉堵住原来那扇门 firewall-cmd --permanent --zonepublic --remove-servicessh # 5. 重新加载让 permanent 配置生效 firewall-cmd --reload # 6. 验证 firewall-cmd --zonetrusted-admin --list-all firewall-cmd --zonepublic --list-all这里最关键的是第4步。很多人配完前3步测试发现其他 IP 还是能连就是因为 public 里的 ssh service 没删。理解一下public zone 里的ssh是一条任何人都能连22端口的规则而 public 又是接口所在 zone来源不匹配 trusted-admin 的流量都会落到 public自然就被那条规则放行了。那203.0.113.20这种地址写单个 IP 要不要加/32加不加都能识别但加上更规范尤其是 IPv6 要用/128。如果来源是一整个网段直接写网段即可规则会按网段匹配。3.3 方案二rich rule 精确到端口做单服务限制只限制 SSH 不影响其他服务时rich rule 更轻量不用新建 zone。# 1. 先删掉 public 里宽泛的 ssh service firewall-cmd --permanent --zonepublic --remove-servicessh # 2. 添加精确到源IP 服务名的 rich rule firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address203.0.113.20 service namessh accept # 3. 如果 SSH 端口不是22用 port 写法 firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address203.0.113.20 port port2222 protocoltcp accept # 4. 生效并验证 firewall-cmd --reload firewall-cmd --zonepublic --list-rich-rulesrich rule 的语法结构是rule [family] [source] [destination] [service|port] action。写的时候有几个细节要注意family必须写ipv4和ipv6是分开的两条规则只写 ipv4 的话 IPv6 流量不受这条规则约束service name用的是 firewalld 预定义的服务名port port才是直接指定端口号两者别混accept只是允许它不会自动拒绝其他来源所以第1步删掉宽泛规则是必须的。如果你想更彻底一点把 public 的 target 也收紧成DROP让所有未匹配流量静默丢弃firewall-cmd --permanent --zonepublic --set-targetDROP firewall-cmd --reload注意把 target 改成 DROP 之前一定要确认白名单已经配好并验证通过否则 reload 的瞬间你就连不上了。%%REJECT%%比DROP温和一些会回一个拒绝包客户端能立刻收到连接被拒排查时更容易判断是防火墙拦了而不是网络不通。3.4 方案三ipset 管理批量来源IP当白名单有几十个 IP或者来源会动态变化时用 ipset 把地址打包管理。ipset 的规则条目在 firewalld 里可以整体替换加 IP 不用重写规则。# 1. 新建一个 ipset 集合类型用 hash:net 以支持网段 firewall-cmd --permanent --new-ipsetadmin_allow --typehash:net # 2. 往集合里添加条目 firewall-cmd --permanent --ipsetadmin_allow --add-entry203.0.113.20 firewall-cmd --permanent --ipsetadmin_allow --add-entry198.51.100.0/24 # 3. 在 zone 里引用这个 ipset放行 SSH firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source ipsetadmin_allow service namessh accept # 4. 同样别忘了删掉宽泛规则 firewall-cmd --permanent --zonepublic --remove-servicessh # 5. 生效并查看集合内容 firewall-cmd --reload firewall-cmd --ipsetadmin_allow --get-entries维护的时候只需要动集合不用碰规则本身# 加一个IP临时生效 firewall-cmd --ipsetadmin_allow --add-entry203.0.113.99 # 永久加需要加 --permanent 再 reload firewall-cmd --permanent --ipsetadmin_allow --add-entry203.0.113.99 firewall-cmd --reload # 删一个IP firewall-cmd --permanent --ipsetadmin_allow --remove-entry203.0.113.99 firewall-cmd --reloadipset 的类型选择也有讲究hash:ip只能存单个 IPhash:net能存网段hash:net,port还能把端口一起编进去。日常白名单用hash:net就够了兼容性最好。3.5 生效验证别只看配置要看真实链路配置写完一定要从外部真实发起连接验证而不是只看--list-all的输出。# 从白名单里的机器测应该成功 ssh useryour-server # 从不在白名单的机器测应该超时或被拒 ssh useryour-server # 在本机看监听和连接状态 ss -tlnp | grep :22 ss -tnp | grep :22如果临时需要看防火墙到底装了多少规则、真实的 netfilter 长什么样可以直接查底层# firewalld 默认走 nftables 后端时 nft list ruleset | head -50 # 老版本走 iptables 后端时 iptables -L -n -v --line-numbers看到底层规则你会发现firewalld 把每个 zone 都翻译成了对应的链rich rule 变成了带-s源地址条件的匹配项。对照着看能帮你更直观地理解上面的抽象概念。另外别忘了测试重启后是否持久reboot # 起来后重新确认规则还在 firewall-cmd --list-all4. 排查实录规则不生效的十种情况配置大方向对了实际跑起来还是可能出问题。下面这些是我踩过或帮别人解决过的真实案例整理成一份速查表遇到问题可以对着过一遍。4.1 先问自己三个定位问题遇到规则配了但不生效别急着改配置先回答三个问题第一规则到底在哪个 zone 生效用firewall-cmd --get-active-zones看输出的 zone 才是真正管着网卡的。曾经有人一直在 public 里折腾但网卡实际绑在ens33对应的自定义 zone 上白改半天。第二流量是本地还是远程发起的从本机自己连自己ssh localhost走的可能是 loopback 接口firewalld 对 lo 有特殊处理测试结果不代表外部行为。一定要从另一台机器测。第三是入站被拦还是出站被拦firewalld 默认允许出站但如果你改过 target出站策略可能也变了。用firewall-cmd --list-all看 zone 的 target 和全部规则别只看 rich rules。4.2 高频问题速查表现象可能原因排查动作其他IP仍能连SSH宽泛的 service/port 没删--list-services、--list-ports检查重启后规则消失只加了 runtime没加 --permanent重新加并--reloadreload 后规则没变服务被 --complete-reload 中断过systemctl restart firewalldIPv6 来源照样能连rich rule 只写了 familyipv4补一条 ipv6 规则容器服务无法访问Docker 直接写 iptables 绕过 firewalld见 4.3规则自相矛盾多个 zone 同时匹配source 优先触发--get-active-zones梳理改完自己连不上target 或 source 写错从本地控制台用回滚脚本恢复ipset 规则不生效集合类型不匹配hash:ip 存了网段重建为 hash:net日志里看不到拦截记录默认不记日志加 log 前缀规则观察4.3 Docker 和虚拟化环境的特殊坑这是最容易翻车的一类。Docker 启动时会把iptables的FORWARD链策略改成ACCEPT并且自己往里插规则实现容器端口映射和容器间通信。而 firewalld 的 zone 策略管的是 INPUT 和 FORWARD 两个方向的流量两边很容易打架。典型表现是你明明在 firewalld 里把某个端口限制到特定 IP结果通过 Docker 映射出去的端口还是对所有人开放因为访问实际上走的是 Docker 自己那套 DNAT FORWARD 规则firewalld 的 INPUT 链根本没参与。处理思路有几个一是在 Docker 的 daemon.json 里加iptables: false让 Docker 不碰 iptables端口管理全交给 firewalld但这样容器网络需要自己配运维成本上升二是把要限制的端口不通过-p映射改成用宿主机上的反向代理统一收口访问控制在代理层做三是接受 Docker 的现实容器暴露的服务单独用云安全组或上游设备限制。我一般倾向第二种把访问控制从容器层挪到宿主机的反向代理上规则更统一也更好审计。虚拟化方面如果宿主机上有 KVM默认的 NAT 网络会用到virbr0网桥和192.168.122.0/24网段。你在宿主机上收紧入站策略时注意别把虚拟机的出站或宿主机对虚拟机的访问一起拦了管理网段要记得放行。提示调整 Docker 相关策略前先docker ps看看哪些业务在线评估影响面。生产环境下改这类配置务必走变更窗口别在业务高峰期动手。4.4 IPv6 双栈的盲区现在很多云主机默认开启了 IPv6而 firewalld 的 rich rule 如果不指定 family行为会受默认值影响。稳妥做法是入站规则明确写全两条firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address203.0.113.20 service namessh accept firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv6 source address2001:db8::20/128 service namessh accept另外验证的时候也要注意telnet或nc测试时默认可能走 IPv4要显式用 IPv6 地址测一遍确认双栈都按预期工作。有些环境虽然开着 IPv6但实际业务走 IPv4那 IPv6 侧的 SSH 如果没管等于留了个后门。5. 生产环境落地的几个实操经验配置本身不难难的是在真实环境里安全地改、改完能维护、出问题能快速回退。这部分是我这些年攒下来的一些做法不一定适合所有场景但思路可以参考。5.1 防锁死用定时回滚兜底远程改防火墙最怕的就是改完连不上又没有控制台权限。我现在的固定动作是改之前设一个定时任务N 分钟后自动恢复备份配置如果操作成功就手动取消这个任务。# 1. 备份当前配置 cp -r /etc/firewalld /etc/firewalld.bak # 2. 设置10分钟后自动恢复并重启 echo cp -rf /etc/firewalld.bak/* /etc/firewalld/ systemctl restart firewalld | at now 10 minutes # 3. 执行你的变更操作 # ... # 4. 验证通过后取消定时任务 atq # 查看待执行任务 atrm 任务号 # 删除指定任务at命令如果没装可以用 crontab 模拟原理一样。这个兜底机制救过我不止一次尤其是在改 target 策略这种一改就可能全网断的操作上。关键点在于定时任务的执行不依赖你的 SSH 会话即使连接断了它照样跑。当然前提是你的变更和恢复脚本本身是正确的所以备份要真备份别复制了个半成品。另一个细节恢复脚本里最好带上日志输出写到一个固定文件里方便事后确认它到底跑没跑、跑了什么结果。cp -rf /etc/firewalld.bak/* /etc/firewalld/ systemctl restart firewalld echo $(date) rollback executed /var/log/fw-rollback.log5.2 灰度上线与日志审计白名单规则不要一次性全网铺开。我的做法是先在测试机验证再挑一台非核心的生产机灰度观察一两天确认没有业务受影响再推全量。判断是否受影响主要看业务日志和连接监控而不是只看防火墙配置对不对。审计方面firewalld 默认不会记录被拦的包这对排查不友好。有两种补法一是加带 log 前缀的 rich rule让命中的流量打日志firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address0.0.0.0/0 port port22 protocoltcp log prefixSSH-DENY levelinfo drop二是直接用内核的nftables/iptables日志能力配合journalctl或/var/log/messages查看。加了日志之后你能清楚看到哪些来源在被拦是否有异常高频访问。这比事后去翻认证日志要主动得多。注意日志规则本身也会产生磁盘写入量大时要注意 logrotate 配置别把磁盘写满。生产环境建议只对关键端口SSH、数据库端口加日志不要全局开。5.3 和上游网络设备的职责划分一台服务器前面的访问控制其实不止一层。机房可能有硬件防火墙深信服、锐捷这类设备在不少单位是标配云上有安全组主机上有 firewalld再往上可能还有负载均衡和反向代理。搞清楚每层管什么能避免重复劳动也能避免以为某层做了、其实没做的漏洞。我的分工习惯是这样上游设备做粗粒度的大范围封禁比如直接封掉整片高风险网段、只放行业务需要的协议主机上的 firewalld 做精细的服务级白名单比如 SSH 只允许运维网段、数据库只允许应用服务器。这样做的好处是即使主机配置被误改上游还有一层兜底反过来主机层面的规则能跟上业务变化不用每次都去申请改硬件策略。还有一个细节要提如果服务器前面有负载均衡或反向代理那么到达主机 firewalld 的源IP 往往是代理的地址而不是真实客户端。这时候白名单要么限制代理地址要么在代理层做真实IP 的访问控制主机层就只信代理。搞混这一点很容易把真实用户全拦在门外或者白名单形同虚设。最后再分享一个我自己的小习惯。每次改完防火墙我都会在变更记录里写清楚三件事改了什么规则、为什么改、怎么回滚。这行干久了就知道问题往往不是出现在配置那一刻而是三个月后你或者别人再回头看完全想不起来当时为什么这么配、动了哪一条。留个清晰的记录比自己记住要靠谱得多。