运维人员经常遇到一种尴尬情况:用 ufw allow 22/tcp 放行 SSH 端口后,服务器在公网上立刻被来自全球的扫描器盯上,暴力破解日志一天能刷几万条。这不是防火墙规则不够,而是规则不够细。精细化运维的核心思路,就是把“谁可以访问什么”这个问题回答到极致,从源地址、目的端口、协议状态、连接频率到时间窗口,每一层都加上约束条件。
从默认策略开始收紧很多人安装完 Ubuntu 后第一件事就是 ufw enable,但默认的 outgoing 策略是 allow,incoming 是 deny。对于面向公网的生产服务器,outgoing 也应该仔细控制。假设这台服务器只作为 Web 服务端,不需要主动向外发起连接,可以把出站默认策略改为 deny,再按需放行。操作顺序很关键,一定要先放行必要的出站流量,再修改默认策略,否则可能把自己锁在外面。
# 先放行 DNS 和 HTTP/HTTPS 出站,用于系统更新和 API 调用 sudo ufw allow out 53/udp sudo ufw allow out 53/tcp sudo ufw allow out 80/tcp sudo ufw allow out 443/tcp # 再修改默认出站为拒绝 sudo ufw default deny outgoing
入站方向同样需要明确化。不要直接 allow 80/tcp 了事,而是结合源地址限制。如果 Web 服务前面有反向代理或 CDN,后端服务器只应该接受来自代理节点的请求。Cloudflare 等 CDN 厂商会公开 IP 列表,可以写一个脚本定期更新规则,或者至少手动添加几条核心网段。
用源地址白名单替代全量放行SSH 端口是最典型的例子。运维人员的家庭宽带 IP 可能不固定,但通常在一个已知的 IP 段内,或者通过跳板机连接。如果必须从固定 IP 访问,直接在 ufw 里限定来源地址:
sudo ufw allow from 203.0.113.50 to any port 22 proto tcp
如果来源 IP 会变动,更安全的做法是使用端口敲门技术,或者通过 WireGuard 等 VPN 组网后,只允许内网 IP 访问 SSH。ufw 本身支持接口级别的规则,如果 VPN 接口是 wg0,可以这样写:
sudo ufw allow in on wg0 to any port 22 proto tcp
这条规则意味着只有通过 wg0 接口进来的流量才能访问 22 端口,公网网卡 eth0 上的 SSH 流量直接被拒绝,扫描器根本探测不到端口开放。
利用 ufw 的应用配置文件做端口组管理ufw 的应用配置文件位于 /etc/ufw/applications.d/,可以自定义应用组,把多个端口打包成一个逻辑服务。比如一个 Web 应用同时需要 80 和 443 端口,还带一个 8080 的管理后台,但管理后台只允许内网访问。可以创建自定义配置文件 /etc/ufw/applications.d/web-app:
[WebApp] title=Custom Web Application description=Main web service ports ports=80,443/tcp [WebAppAdmin] title=Web App Admin Panel description=Admin backend restricted to internal network ports=8080/tcp
然后分别应用规则:
sudo ufw allow WebApp sudo ufw allow from 10.0.0.0/8 to any app WebAppAdmin
这样维护起来清晰很多,不需要记住每个端口号,规则含义一目了然。当应用端口变更时,只需修改配置文件并重载 ufw,不用逐条改规则。
基于连接状态的精细化控制ufw 底层是 iptables,默认已经启用了连接跟踪,允许已建立和相关联的流量回包。但有些场景需要显式利用状态模块做更细的控制。比如限制新连接的速率,防止某个 IP 短时间内发起大量请求。ufw 本身不直接支持速率限制,但可以通过修改 /etc/ufw/before.rules 文件,在 filter 表的 ufw-before-input 链中插入 iptables 规则。
在 before.rules 的合适位置添加以下内容,实现对 SSH 端口每分钟只允许 6 个新连接的限制:
# 在 *filter 段内,COMMIT 之前添加 -A ufw-before-input -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH_RATE -A ufw-before-input -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 6 --name SSH_RATE -j DROP
保存后执行 sudo ufw reload 生效。这样即使攻击者知道 SSH 端口,也无法进行高速暴力破解,连接速率被严格限制。同样的方法可以用于 HTTP 端口,对单个 IP 的请求频率做硬限制,作为应用层限流的补充。
时间窗口规则实现定时访问控制某些服务只在特定时段需要对外开放,比如每日凌晨的数据同步接口,或者工作时间才需要的内部管理系统。iptables 的 time 模块可以实现基于时间的匹配,ufw 同样可以通过 before.rules 来调用。假设一个数据库备份端口 5432 只允许在凌晨 2 点到 4 点之间从备份服务器访问:
-A ufw-before-input -p tcp --dport 5432 -s 10.0.1.100 -m time --timestart 02:00 --timestop 04:00 -j ACCEPT -A ufw-before-input -p tcp --dport 5432 -j DROP
注意时区问题,系统默认使用 UTC 时间,需要根据实际情况换算。如果服务器使用本地时区,可以通过 TZ 环境变量或直接换算成 UTC 时间写入规则。这种基于时间的精细化控制,可以显著缩小攻击面,在非必要时段直接把端口关闭在网络层。
日志与审计:知道谁被拒绝了精细化规则上线后,难免会出现误拦截的情况。ufw 默认日志级别是 low,只记录被拒绝的包。可以通过 sudo ufw logging medium 或 high 来调整,但高日志级别会产生大量记录,建议在调试期间临时开启,日常保持 low 或 medium。日志写入 /var/log/ufw.log,可以用 grep 快速定位被拦截的合法请求:
grep "UFW BLOCK" /var/log/ufw.log | awk '{print $10,$11,$12}' | sort | uniq -c | sort -rn | head -20
这条命令统计被拦截最多的源 IP 和目标端口组合,帮助发现规则是否过于严格。如果看到某个内部 IP 频繁被拦,说明需要补充放行规则。对于长期运行的生产环境,建议将 ufw 日志接入集中式日志系统,设置告警规则,当某个 IP 被拒绝次数短时间内激增时触发通知。
IPv6 规则同步管理很多运维人员配置完 IPv4 规则就认为完事了,完全忽略了 IPv6。Ubuntu 默认启用 IPv6,如果只配了 IPv4 的 ufw 规则,攻击者可以通过 IPv6 绕过所有限制。检查 /etc/default/ufw 中的 IPV6 选项是否设置为 yes,然后确保每一条 IPv4 规则都有对应的 IPv6 版本。ufw 的 from 语法支持 IPv6 地址,例如:
sudo ufw allow from 2001:db8::/32 to any port 22 proto tcp
如果不打算使用 IPv6,最彻底的做法是在内核层面禁用,或者至少在 ufw 中把 IPv6 的入站和出站默认策略都设为 deny,并只放行必要的 IPv6 流量。半吊子的 IPv6 配置是严重的安全隐患。
规则集的版本管理与自动化部署手动敲命令配置防火墙,在服务器数量超过 3 台时就会变成噩梦。ufw 的规则实际上存储在 /etc/ufw/ 目录下的几个规则文件中,user.rules 和 user6.rules 分别存放 IPv4 和 IPv6 的用户自定义规则。这些文件是纯文本,可以直接纳入版本控制系统。标准做法是:在测试服务器上调试好规则,然后把整个 /etc/ufw/ 目录下的配置文件提交到 Git 仓库,生产服务器通过 Ansible 或 shell 脚本拉取配置并执行 ufw reload。
自动化部署时要注意,不要直接在生产服务器上执行 ufw enable,如果配置有问题会导致连接中断。应该先用 ufw enable 在测试环境验证,生产环境用 ufw reload 加载新规则。另外,在修改规则前备份当前生效的规则集,出问题时可以快速回滚:
sudo cp /etc/ufw/user.rules /etc/ufw/user.rules.bak.$(date +%Y%m%d)结合 fail2ban 实现动态黑名单
静态规则无法应对所有攻击场景,需要 fail2ban 这类工具动态补充。fail2ban 监控应用日志,发现多次认证失败后自动调用 ufw 封禁 IP,过一段时间自动解封。安装 fail2ban 后,创建 /etc/fail2ban/jail.local 配置文件,针对 SSH 服务启用 ufw 封禁:
[sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 3600 findtime = 600 action = ufw-ssh
然后在 /etc/fail2ban/action.d/ufw-ssh.conf 中定义具体动作,调用 ufw insert 将封禁规则插入到规则列表最前面,确保优先级最高。fail2ban 和静态规则配合使用,静态规则负责常态化的访问控制,fail2ban 负责动态响应异常行为,两者互补形成纵深防御。
定期审计与规则瘦身服务器运行一段时间后,ufw 规则会越加越多,出现冗余和冲突。建议每季度做一次规则审计,用 sudo ufw status numbered 列出所有规则及其序号,逐条审视是否仍然需要。删除规则可以用 sudo ufw delete 后跟规则内容,或者用序号删除。审计时重点关注几类问题:已经下线的服务对应的端口规则、过于宽泛的源地址范围、重复的规则、以及逻辑上被前面规则覆盖而永远不会命中的无效规则。保持规则集精简,不仅能提升匹配效率,也降低维护人员的心智负担。
精细化运维的本质,是把安全策略落实到每一条规则、每一个字段、每一个时间窗口。ufw 作为 iptables 的前端,在易用性和灵活性之间取得了很好的平衡。通过源地址白名单、接口绑定、连接速率限制、时间窗口控制、IPv6 同步、版本管理、动态封禁和定期审计这八个维度的精细化配置,Ubuntu 服务器的防火墙才能真正做到滴水不漏。
