Linux 防火墙白名单配置实战:ufw、firewalld、iptables
2026/9/18 19:21:31 网站建设 项目流程

1. 先把"白名单"这件事想明白

1.1 白名单到底在解决什么问题

Linux 防火墙的白名单,说白了就一句话:默认拒绝所有流量,只把你明确认可的来源放进来。它的反面是黑名单——先放开、再封禁已知的坏 IP。很多人一开始图省事用黑名单,理由通常是"怕把自己的业务挡了"。但只要你在生产环境待过一段时间就会发现,黑名单根本维护不动:攻击源每天都在换,你封了 1000 个 IP,第 1001 个还是能进来。

白名单的逻辑正好反过来。你只需要回答一个问题:谁需要连我。运维跳板机的网段、监控服务器的地址、办公网出口、负载均衡的内网地址、数据库主从之间互相连接的那几台机器,把这些列清楚,剩下的全部丢掉。攻击面从"整个互联网"直接收缩到"你写的这几行规则",这是最划算的一次安全投入。

我见过太多机器是这样的状态:systemctl status firewalld显示 running,看着挺安心,实际firewall-cmd --list-all一看,services: ssh dhcpv6-client cockpit http https全开着,等于门锁挂在门上但没扣。这种情况在 Ubuntu 上同样常见,ufw status显示 active,可ufw allow 22谁都能连。防火墙开着不等于有白名单,这是最容易产生的错觉。

这篇内容面向三类人:刚接手 Linux 服务器的开发同学、需要给一批机器做安全收敛的运维、以及准备考试或者面试需要把这块讲清楚的人。Ubuntu、CentOS、RedHat 三个发行版我都会覆盖,因为它们虽然用的是同一套内核机制,但上层工具完全不同,网上的教程又经常混着抄,导致你在 Ubuntu 上照抄 CentOS 的命令,回车就报 command not found。

1.2 三套工具、三个发行版家族怎么对应

底层其实只有一套东西:Netfilter,Linux 内核里的包过滤框架。用户态工具是后来加上的,不同年代、不同发行版选了不同的封装。

发行版默认前端工具底层配置文件
Ubuntu 18.04+ / Debian 系ufwiptables/nftables/etc/ufw/ 目录
CentOS 7 / 8、RedHat 7+firewalldiptables/nftables/etc/firewalld/
CentOS 6 / RedHat 6 及更早iptables serviceiptables/etc/sysconfig/iptables
通用(所有发行版)直接写 nft / iptablesnftables / iptables视方案而定

这里要解释一个高频困惑:为什么 CentOS 7 上service iptables status会说没这个服务?因为 RHEL 7 开始,红帽把 firewalld 设成默认,iptables 的服务单元被拆到了iptables-services这个可选包里,不装就没有。反过来,Ubuntu 上你也能装 firewalld,但那是自找麻烦——默认装了 ufw 就用 ufw,别在同一台机器上同时开两套前端工具,规则互相覆盖,排查起来能让人崩溃。

选型上我的建议很直接:跟着发行版默认走。Ubuntu 用 ufw,CentOS 7+ 用 firewalld,老系统用 iptables。理由不是"默认的就是最好的",而是默认工具和系统的其他组件(比如 NetworkManager、cloud-init、各种安装脚本)有约定,你换一套工具,很可能某次 yum 更新之后规则就被别的东西改回去了。

提示:ufw 和 firewalld 都不要和 iptables 命令混用。你用iptables -L看到的那一堆链,很可能是前端工具生成的,直接手改会在下一次 reload 时全部丢失。

2. 动手之前:先把退路铺好

2.1 四元组与最小放行原则

热搜词里有一条"白名单需要四元组",这个说法很准确。一条完整的放行规则,本质是在匹配五个要素:源 IP、源端口、目的 IP、目的端口、协议。前面的四元组加上协议,构成了一个"流"的身份。

实际操作中,源端口几乎永远是随机的,你没法预测客户端用哪个端口发起连接,所以规则里一般只写源 IP + 目的端口 + 协议。目的 IP 在单机防火墙上通常省略(就是本机),只有在做转发、NAT 或者容器网络时才需要写明。

真正的功夫在于把源 IP 收窄到什么粒度。我的经验是按这个顺序收敛:

  • 能用具体主机地址(/32)就别用网段,能用 /28 就别用 /24。
  • 办公网出口如果是动态的,先找网管要固定出口 IP,实在没有就上跳板机。
  • 数据库、缓存、内部管理端口,永远只对应用服务器放行,不对办公网放行。
  • SSH 端口对办公网开放时,加上频率限制或者配合密钥登录,双保险。

有个细节经常被忽略:源 IP 是可以伪造的,但 TCP 三次握手伪造不了(除非能做到中间人)。所以对于 TCP 服务,源 IP 白名单是可靠的;对于 UDP 服务(比如 DNS、SNMP),伪造源 IP 就能打进来,白名单只能挡住"响应被发到哪",不能完全阻止探测。这也是为什么 DNS 服务器要额外做响应速率限制。

2.2 改防火墙前必须做的三件事

我踩过最惨的一次坑,是在一台只有 SSH 这一个入口的云主机上,手写 iptables 时先执行了iptables -P INPUT DROP,然后才想起来还没加 SSH 放行规则。那一瞬间,终端还连着,但因为你已经把当前会话也算进了 INPUT 链,回车之后直接卡死。最后靠 VNC 控制台进去救回来的,前后折腾了四十分钟。

从那以后,我固定做三件事:

第一,先加放行,后改默认策略。顺序永远是:写 ACCEPT 规则 → 确认规则在链里 → 最后才-P INPUT DROP。反过来做,等于先关门再找钥匙。

第二,所有 SSH 会话里做防火墙变更,都要开一个兜底定时任务。

# 5 分钟后把 INPUT 策略恢复为 ACCEPT,给自己留一扇窗 echo "iptables -P INPUT ACCEPT" | at now + 5 minutes

如果机器上没有at,用这个更土但一定生效的写法:

nohup bash -c 'sleep 300; iptables -P INPUT ACCEPT; iptables -F' >/dev/null 2>&1 &

规则调好了,kill掉这个后台进程,或者atrm掉定时任务就行。这招看着粗暴,但它救过我不下五次。

第三,变更前先备份当前规则。

# iptables 系 iptables-save > /root/iptables.bak.$(date +%F) # firewalld 系 tar czf /root/firewalld.bak.$(date +%F).tar.gz /etc/firewalld # ufw 系 tar czf /root/ufw.bak.$(date +%F).tar.gz /etc/ufw

备份这件事,顺风的时候觉得多余,逆风的时候就是命。

2.3 关闭防火墙不是解决方案

热搜里有个问题叫"防火墙关闭有影响吗"。我的回答很明确:在开发机上无所谓,在生产机上关掉等于把自己的端口清单挂在网上。有人会说"我后面有云安全组顶着",安全组确实是第一道关,但它管的是南北向流量(进出云环境的),同一 VPC 内部机器之间的东西向流量,安全组通常放得很宽,这时候主机防火墙就是唯一的阻挡层。

而且关掉防火墙还有两个隐性代价:一是很多安全扫描和基线检查会直接判你不合格,后面要写一堆说明;二是你会失去连接日志iptables的 LOG 目标、firewalld 的 log-denied、ufw 的 logging,能在被扫的时候给你留下证据。关掉之后,攻击者在你机器上敲门,你连声音都听不到。

正确的做法是:防火墙开着,然后花一下午把白名单配完。下面三节,我按发行版一个一个来。

3. Ubuntu / Debian 系:ufw 白名单实操

3.1 ufw 默认策略与开启顺序

ufw 是 "uncomplicated firewall" 的缩写,它把 iptables 的一堆链包装成了人类能读的规则。默认情况下 ufw 是关闭的,ufw status会显示Status: inactive。但不要直接ufw enable,因为它的默认策略是allow incoming,你一开,等于什么都没做。

正确的开启步骤是这样:

# 1) 先设默认策略:进来的拒绝,出去和转发放行 sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw default allow routed # 2) 先把自己要用的端口放行(此刻 ufw 还没生效,但规则已经写好了) sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp comment 'office-ssh' # 3) 最后才启用 sudo ufw enable

启用时会提示 "Command may disrupt existing ssh connections",输入y回车。我这里把deny incoming放在最前面,是因为 ufw 的规则是按文件里出现的先后顺序执行的,默认策略永远排在所有规则的最后。先写默认拒绝,再写具体放行,语义上跟 iptables 的-P INPUT DROP-A INPUT -j ACCEPT是一回事。

有一点必须解释清楚:ufw default allow routed这一行在普通服务器上可以省掉,但如果你这台机器要跑 Docker、要开 IP 转发、或者做内网网关,就一定要显式放行转发,否则容器里的服务会突然全部断网。

3.2 按源地址放行的三种写法

ufw 的规则语法其实很简洁,核心就三种形态,记住这三条基本够用一辈子。

第一种,只限端口不限来源(不推荐,但调试时用):

sudo ufw allow 443/tcp

第二种,限定来源 IP 或网段(白名单的主力写法):

# 单个 IP 访问 22 sudo ufw allow from 203.0.113.17 to any port 22 proto tcp # 一个 /24 网段访问 3306 sudo ufw allow from 10.20.30.0/24 to any port 3306 proto tcp # 指定来源网段访问任意端口 sudo ufw allow from 10.20.30.0/24

第三种,限定来源加出接口(多网卡机器用):

sudo ufw allow in on eth1 from 10.20.30.0/24 to any port 8080 proto tcp

这里有个很实用的细节:ufw allow from X to any port Yto any部分,在双栈机器上默认会同时生成 IPv4 和 IPv6 规则。如果你机器上 IPv6 根本没启用,可以不管;如果启用了但你不打算对 IPv6 做白名单,记得检查/etc/default/ufw里的IPV6=yes那一行,改成no更省心,否则会出现"我明明只放行了 IPv4 网段,怎么 IPv6 还能连"的诡异情况。

另外,ufw 的规则顺序问题需要特别注意。它不像 iptables 那样可以随意-I插到指定位置,虽然提供了ufw insert 1 allow ...把规则插到编号 1,但编号会随着增删变化,维护成本很高。所以一旦你的规则超过 15 条,或者出现了 allow 和 deny 混合的复杂逻辑,我建议直接放弃 ufw,改用 /etc/ufw/before.rules 写原生的 iptables 规则——ufw 在 enable 时会把这些规则一起加载进去,两边不冲突。

3.3 规则顺序、日志与限速

ufw 有个非常好用的功能叫限速,专门对付 SSH 爆破:

sudo ufw limit from 203.0.113.0/24 to any port 22 proto tcp

它的原理是:同一个来源 IP 在 30 秒内如果发起超过 6 次连接,就会被自动拉黑一段时间。对正常的交互式 SSH 完全没影响,但爆破脚本一跑就会被掐断。只要你的 SSH 是暴露在公网或者大内网的,我强烈建议用limit代替allow,这是性价比最高的一条规则。

日志方面,默认 ufw 的日志级别是low,只记录被策略拒绝的包。想看得更细:

sudo ufw logging medium sudo tail -f /var/log/ufw.log

日志长这样:

[UFW BLOCK] IN=eth0 OUT= MAC=... SRC=198.51.100.77 DST=10.0.0.5 LEN=60 TOS=0x00 PREC=0x00 TTL=52 ID=12345 DF PROTO=TCP SPT=41234 DPT=6379 WINDOW=29200 RES=0x00 SYN URGP=0

看懂这行的关键就三个字段:SRC是谁在敲、DPT敲的是哪个端口、PROTO用什么协议。如果DPT=6379,说明有人在扫你的 Redis。这类日志积累一段时间,你就能摸清自己机器被扫的规律。

注意:ufw logging medium在高流量机器上会产生大量日志,日志盘写满会导致服务异常。生产环境建议配合 logrotate,或者用high级别只在排查问题的短时间窗口内打开。

3.4 ufw 常用命令清单

日常运维真正会用到的命令其实就这些,建议直接存成笔记:

操作命令
查看规则(带编号)sudo ufw status numbered
查看详细规则sudo ufw status verbose
删除第 3 条规则sudo ufw delete 3
按内容删除sudo ufw delete allow from 203.0.113.17 to any port 22 proto tcp
临时关闭sudo ufw disable
重载配置sudo ufw reload
重置所有规则sudo ufw reset
查看原始规则文件cat /etc/ufw/user.rules

ufw reset要慎用,它会把所有规则清空并禁用 ufw,通常只在彻底重做时才用。而cat /etc/ufw/user.rules这招,是排查"我明明删了规则为什么还生效"的终极武器——有时候规则文件里残留了旧条目,status看不到,但实际在跑。

4. CentOS 7+ / RedHat 系:firewalld 白名单实操

4.1 zone 与 target:firewalld 的挡板逻辑

firewalld 最让人迷糊的就是zone(区域)这个概念。你可以把它理解成"几套不同的挡板,每套对应一批网卡或者一批来源地址"。一台机器可以同时启用多个 zone,流量进来之后,firewalld 会先根据来源 IP 匹配 zone,匹配不到就用网卡绑定的 zone,最后用默认 zone。

查当前状态:

firewall-cmd --get-active-zones firewall-cmd --get-default-zone firewall-cmd --list-all

默认 zone 一般是public,它的 target 是default,含义是"没被明确允许的都拒绝"。这就意味着,firewalld 天生就是白名单模式,你只要把内置放行的服务删干净,就自动变成白名单了。这也是它比 ufw 更省心的地方。

但很多人踩的坑是:publiczone 默认带了sshdhcpv6-clientcockpit这几个服务,安装完就是放行的。你得先把它们摘掉:

# 看看现在放了什么 firewall-cmd --list-services # 把不需要的摘掉,注意 ssh 别急着删,先加好你自己的白名单规则 sudo firewall-cmd --permanent --remove-service=cockpit sudo firewall-cmd --permanent --remove-service=dhcpv6-client

ssh之前一定要先把白名单规则加上,否则 reload 的一瞬间你就掉线了。

4.2 用 rich rule 做源地址白名单

firewalld 有两条路可以做源地址白名单,我实际用下来更推荐第一种:富规则(rich rule)

# 只允许 203.0.113.0/24 访问 22 端口 sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" port protocol="tcp" port="22" accept' # 只允许 10.20.30.0/24 访问 MySQL sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.20.30.0/24" port protocol="tcp" port="3306" accept' # 允许某台监控机访问所有端口(慎用) sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.20.30.99" accept'

rich rule 的语法结构其实很规整:rule + family + source + 动作条件 + accept/reject/dropdropreject的区别值得说一下——drop是直接丢包,对方会一直等到超时;reject会回一个 ICMP 不可达,对方立刻知道被拒了。对外网扫描源用drop更省事,对内网误配用reject更容易排查。

第二种路是source + zone,适合来源地址很多、需要分组的场景:

# 建一个专用 zone sudo firewall-cmd --permanent --new-zone=admin-ssh # 把办公网段塞进去 sudo firewall-cmd --permanent --zone=admin-ssh --add-source=203.0.113.0/24 sudo firewall-cmd --permanent --zone=admin-ssh --add-source=198.51.100.0/24 # 这个 zone 里放行 22 sudo firewall-cmd --permanent --zone=admin-ssh --add-port=22/tcp sudo firewall-cmd --reload

这种写法的好处是管理侧很清晰:以后想加一个新的办公网出口,只要往 zone 里加 source,不用重复写端口规则。缺点是多一层抽象,新人看--list-all-zones的输出会晕。

4.3 runtime 与 permanent 的坑

这是 firewalld 新手翻车率最高的一处。firewalld 有两套规则集:runtime(内存里,当前生效)和permanent(写在/etc/firewalld/里,重启后生效)。你用firewall-cmd加规则时如果不带--permanent,只改 runtime;带了--permanent,只改文件,不会立刻生效

所以标准动作永远是两步:

sudo firewall-cmd --permanent --add-rich-rule='...' sudo firewall-cmd --reload

--reload会丢掉所有 runtime 里没写进 permanent 的规则,所以别养成"先临时加一条试试"的习惯,试完忘了 reload,重启就没了。

还有个更隐蔽的问题:--reload会短暂切断连接吗?答案是已经建立的连接通常不受影响,但新连接会经历一个极短的窗口--reload本身是平滑的,真正会断的是systemctl restart firewalld。所以在线上机器上,我宁愿多敲一次--reload,也不去 restart 服务。

另外,--complete-reload--reload也不一样,前者会清空所有连接跟踪表,等于把现有会话全踢掉,只在规则乱到需要推倒重来时才用。

4.4 firewalld 常用命令清单

# 查当前 zone 的全部规则 firewall-cmd --list-all # 查所有 zone firewall-cmd --list-all-zones # 查看所有富规则 firewall-cmd --list-rich-rules # 删除某条富规则(把 add 换成 remove,内容一模一样) sudo firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="203.0.113.0/24" port protocol="tcp" port="22" accept' # 查看某个 zone 的 target firewall-cmd --permanent --zone=public --get-target

这里提醒一个细节:firewalld 的富规则顺序是按添加顺序(或者说按内部规则列表顺序)来的,但内部实现会把它拼成 iptables 规则链,做复杂逻辑时依然可能出现"先匹配到某条就返回"的情况。所以当你的富规则超过二三十条,排查会变得很难,这时候就该考虑下一节的 ipset 了。

5. CentOS 6 / RedHat 6 等老平台:iptables 白名单实操

5.1 规则链、匹配顺序与默认策略

虽然 RHEL 6 已经很老了,但不少内网老系统、工业设备、专用一体机还在跑,而且很多"高级玩法"(比如 ipset、conntrack 调优)在 iptables 层反而讲得更清楚。所以这一节我建议所有人都看一遍,不只是为了照顾老系统。

iptables 的核心机制是链(chain)和顺序匹配。INPUT 链处理发往本机的包,FORWARD 处理经过本机转发的包,OUTPUT 处理本机发出的包。规则从上往下匹配,匹配到第一条就执行动作并停止-j ACCEPT-j DROP都算终止动作)。

所以规则的顺序至关重要。正确的顺序是:

  1. 放行回环接口lo,不然本机的一堆服务会互相连不上。
  2. 放行已建立连接的后续包(状态匹配),否则每个包的响应都被当成新连接。
  3. 放行 ICMP 的必要类型,保证 ping 和路径 MTU 发现正常。
  4. 白名单来源的放行规则。
  5. 记录日志。
  6. 默认策略兜底。

第 2 条是新手最容易漏的。如果你写了-A INPUT -s 10.0.0.5 -p tcp --dport 22 -j ACCEPT,但没写状态匹配,那么客户端 SYN 能进来,服务器回 SYN-ACK 的时候,这个包属于 OUTPUT 链,会被放行;但客户端再发 ACK 过来,这个包在 INPUT 链里既不匹配源 IP 规则(其实匹配,因为源 IP 还是 10.0.0.5)……

嗯,SSH 这个例子里其实能通。真正会出问题的是主动去连别人的场景:本机发起一个到外部的 HTTP 请求,对方的响应包源 IP 是那个 HTTP 服务器,不在你的白名单里,就会被 DROP 掉,表现为"我 ping 得通它,但我请求不通"。这就是状态匹配存在的意义:

-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

这句话的意思是:只要是"已经建立连接"或者"与已建立连接相关"的包,一律放行。有了它,你的白名单就只需要管"谁可以发起新连接",不用管后续的数据流,规则量能减少一大半。

5.2 一份可以直接抄的白名单脚本

下面这份脚本我在 CentOS 6 和 CentOS 7(最小化安装、没装 firewalld)上都用过,改几个变量就能上:

#!/bin/bash # iptables-whitelist.sh # 用途:为服务器配置基于源地址的白名单防火墙 # 适用:CentOS 6 / RedHat 6 / 精简版 CentOS 7 set -e # 白名单网段,空格分隔 TRUSTED_NETS="127.0.0.1 10.20.30.0/24 203.0.113.0/28" # 对外开放的端口,格式 "端口/协议" OPEN_PORTS="22/tcp 443/tcp" # 清空旧规则 iptables -F iptables -X iptables -Z # 默认策略:进来拒绝,出去和转发放行 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT # 1) 回环接口 iptables -A INPUT -i lo -j ACCEPT # 2) 已建立连接与相关连接 iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # 3) 必要的 ICMP(ping、目标不可达、TTL 超时) iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT iptables -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT # 4) 白名单放行 for net in $TRUSTED_NETS; do for pp in $OPEN_PORTS; do port=${pp%/*} proto=${pp#*/} iptables -A INPUT -s "$net" -p "$proto" --dport "$port" -j ACCEPT done done # 5) 记录被拒绝的包(限速,避免日志被打爆) iptables -A INPUT -m limit --limit 10/min -j LOG --log-prefix "IPT-DROP: " --log-level 4 # 6) 兜底(其实默认策略已经 DROP,这条是为了语义清晰) iptables -A INPUT -j DROP echo "规则写入完成,当前规则数:$(iptables -L INPUT -n | wc -l)"

几个地方解释一下。set -e让脚本在任一命令失败时立刻退出,避免中途出错留下半套规则。-m limit --limit 10/min是日志限速,如果不加这个,被扫描的时候/var/log/messages一分钟能涨几百兆。IPT-DROP:这个前缀是给后续 grep 用的,方便你写脚本统计攻击源。

-P FORWARD DROP这一条,如果你的机器要做路由转发或者跑 Docker,需要改成 ACCEPT 并单独放行转发规则。Docker 会自动往 FORWARD 链里插自己的规则,所以在 Docker 宿主机上,iptables 手写规则和 Docker 的规则会打架,这是另一个大话题,这里先不展开。

5.3 持久化与开机加载

iptables 命令写进去的规则,重启就没了。持久化有两个办法。

老系统(CentOS 6 / RHEL 6)

service iptables save # 规则会存到 /etc/sysconfig/iptables service iptables restart

新系统(CentOS 7 装了 iptables-services)

yum install -y iptables-services systemctl stop firewalld systemctl disable firewalld iptables-save > /etc/sysconfig/iptables systemctl enable iptables systemctl start iptables

注意顺序:一定要先停 firewalld 再启用 iptables 服务,两套东西同时跑,规则会互相干扰,而且很可能重启之后你发现生效的是 firewalld 那套。

还有一种更原始但更可控的方式,把上面的脚本放到/etc/rc.local里(CentOS 6)或者写成一个 systemd unit(CentOS 7)。我个人的偏好是用 iptables-save / iptables-restore 配合 systemd unit,因为规则文件是纯粹的数据,改起来直观,也不会因为脚本里的某个变量写错导致整个链断掉。

6. 规则多了怎么办:ipset 与管理侧收敛

6.1 ipset 基本用法与 firewalld 集成

当白名单地址超过三五十个,或者你有一个经常变动的地址集合(比如一堆云函数出口 IP),逐条写规则就变成噩梦了。这时候用ipset:把所有地址装进一个集合,防火墙规则只匹配这个集合。

底层操作是这样:

# 安装(CentOS: yum install ipset,Ubuntu: apt install ipset) # 建一个集合,类型选择很关键 ipset create trusted_hosts hash:ip ipset create trusted_nets hash:net # 加入地址 ipset add trusted_hosts 203.0.113.17 ipset add trusted_nets 10.20.30.0/24 # 看一下 ipset list # 引用到 iptables iptables -A INPUT -m set --match-set trusted_nets src -p tcp --dport 22 -j ACCEPT

hash:iphash:net的区别一定要搞清:hash:ip里只能放单个 IP,你塞10.0.0.0/24进去会报错;hash:net里既能放单个 IP 也能放网段,但内存占用稍高。绝大多数白名单场景直接用hash:net就对了。

firewalld 从 0.4 版本开始直接支持 ipset:

# 建 ipset sudo firewall-cmd --permanent --new-ipset=trusted_nets --type=hash:net # 加地址 sudo firewall-cmd --permanent --ipset=trusted_nets --add-entry=10.20.30.0/24 sudo firewall-cmd --permanent --ipset=trusted_nets --add-entry=203.0.113.0/28 # 引用到 rich rule sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule source ipset="trusted_nets" port port="22" protocol="tcp" accept' sudo firewall-cmd --reload

好处非常明显:以后加一个新网段,只要--ipset --add-entry然后 reload,不用动任何规则。查firewall-cmd --ipset=trusted_nets --get-entries就能看到全部内容。

6.2 动态 IP 与云环境的处理思路

现实里最头疼的场景是:来源地址会变。比如办公网是动态出口、远程同事在家办公、云上的 API 网关出口 IP 不固定。

我的处理思路分三档:

第一档,找固定点。大多数企业都有固定的出口 IP 或者跳板机。哪怕是通过跳板机再多一跳,也比把整个 0.0.0.0/0 放开强得多。跳板机本身就是白名单资产的入口,把防护集中在一台机器上,管理成本反而更低。

第二档,用脚本定期同步。如果来源地址是某个服务商提供的、能通过接口查到(比如对象存储、CDN 回源段),就写一个定时任务,每 10 分钟拉一次列表,和 ipset 做差集更新。核心逻辑是"先加后删",避免中间出现空窗期:

#!/bin/bash # sync-whitelist.sh NEW_LIST=$(curl -s https://example.internal/api/egress-ips) ipset create trusted_nets hash:net -exist # 先全部加进去 for ip in $NEW_LIST; do ipset add trusted_nets "$ip" -exist done # 再删掉已经不在列表里的(需要自己维护一份旧列表做对比) # 简化做法:直接 swap,重建一个临时集合 ipset create trusted_tmp hash:net -exist ipset flush trusted_tmp for ip in $NEW_LIST; do ipset add trusted_tmp "$ip" -exist done ipset swap trusted_tmp trusted_nets ipset destroy trusted_tmp

ipset swap这个操作是原子的,切换过程中不会有"规则失效"的瞬间,比 flush 再 add 安全得多。这个思路值得记住,所有需要热更新集合的场景都能用。

第三档,对无法固定的来源,改用应用层认证。与其在防火墙上开一个大洞,不如把这个端口本身改成"必须带证书/Token 才能访问"。防火墙白名单守的是网络层,应用层认证守的是身份层,两者是互补的,不是二选一。

7. 常见问题排查与速查表

7.1 把自己锁在外面了怎么救

这是最高频的事故。分几种情况说。

能连 SSH 但新连接被拒:说明当前会话还在,规则里漏了 ESTABLISHED 状态匹配或者默认策略改错了。赶紧在一个还没断的会话里执行:

# iptables iptables -P INPUT ACCEPT iptables -F # firewalld firewall-cmd --set-default-zone=trusted # 或者临时全放 firewall-cmd --zone=trusted --add-source=0.0.0.0/0

完全连不上:走云控制台的 VNC / 串口登录,或者联系机房上 KVM。登进去之后按上面的命令恢复。所以前面说的at兜底方案真的很重要,它能让你在 300 秒之内自动恢复,而不用等控制台。

Ubuntu 上ufw reset之后还是连不上:ufw 重置了,但机器上可能同时跑着 Docker 或者别的工具生成的 iptables 规则。执行iptables -L -n --line-numbers看看到底是谁的规则在挡路。

7.2 端口放行了却连不上

这个问题的排查要有顺序,不能瞎试。我固定按这个链条走:

排查点命令常见结论
服务本身在监听吗ss -lntp | grep 端口只监听 127.0.0.1 而不是 0.0.0.0
防火墙规则匹配到了吗iptables -L INPUT -n -v计数器不增长说明规则没匹配上
包到底走到哪了tcpdump -i any port 端口 -nn只见 SYN 不见 SYN-ACK,是被丢了
是不是安全组挡的云控制台看安全组主机放行了,云上没放行
是不是中间设备traceroute -T -p 端口 目标链路上有设备拦截

第一条最常见。很多服务默认只绑 127.0.0.1,比如 Redis 的默认配置、MySQL 在某些发行版上的默认配置、Node.js 应用如果写的是app.listen(3000, 'localhost')。防火墙放行了,包能到网卡,但服务根本不在那个地址上监听,你看到的现象就是"连接被拒绝"而不是"超时"。这两个现象要分清:超时通常是防火墙 DROP,拒绝通常是服务没监听或者被 REJECT

7.3 重启后规则失效

九成是持久化没做,剩下的一成是被别的组件覆盖了。

firewalld 的规则如果没带--permanent,重启必丢,这个前面讲过。iptables 的规则没执行iptables-saveservice iptables save,重启也必丢。Ubuntu 上 ufw 的规则默认是持久的,写在/etc/ufw/user.rules,但如果ufw服务被 disable 了,规则文件再全也不会加载,用systemctl is-enabled ufw确认一下。

还有一类隐蔽情况:机器上装了 Docker、kube-proxy、LibreNMS 这类会自动改写 iptables 的组件。它们启动时会往链里插规则,如果你的规则和它们的顺序冲突,表现可能是"防火墙看着是开的,但某些端口莫名其妙通了"。处理办法是让这些组件的规则优先,或者干脆不让它们管防火墙。

7.4 排查速查表

把上面几节的要点汇总成一张表,遇到问题时从上往下过一遍,基本能覆盖八成场景:

现象最可能的原因处理动作
SSH 卡住不动默认策略 DROP 且没放行 22控制台登录,iptables -P INPUT ACCEPT
端口 telnet 超时规则未放行或被 DROP检查规则的源地址是否匹配,看-v计数器
端口 connection refused服务未监听该地址ss -lntp确认绑定地址
重启后规则消失未持久化service iptables save/ 加--permanent
规则加了不生效firewalld 未 reloadfirewall-cmd --reload
日志暴涨未做日志限速-m limit --limit 10/min
本机服务互相连不上漏了 lo 接口放行iptables -A INPUT -i lo -j ACCEPT
主动出站请求无响应漏了 conntrack 状态匹配加 ESTABLISHED,RELATED 放行

最后分享一个我自己的习惯:每次改完防火墙,一定从另一台机器实测一次。别信status的输出,也别信自己的记忆。用nc -zv 目标IP 端口或者telnet,从内网、从外网、从白名单外的机器,各测一遍,四个方向都通了才算收工。防火墙这种东西,配置的时候多花二十分钟验证,比事后半夜被叫起来排查要划算得多。

还有一个后续可以做的事:把白名单规则和资产清单对齐。每台机器开放了哪些端口、对哪些来源开放、由谁负责,整理成一张表定期 review。我见过太多"当年临时开放、后来没人记得"的规则,它们就是白名单最终失效的原因。规则和人一样,不维护就会腐烂。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询