速读#
UFW 决定哪些网络入口允许到达主机,fail2ban 根据日志中的重复失败临时封禁来源,两者职责不同,也都不是第一道安全措施。更优先的顺序是:不需要的服务不要监听公网,管理入口尽量放进 WireGuard 或来源白名单,SSH 禁用密码与 root 直登,容器不要随意发布宿主机端口;完成这些以后,防火墙负责强制边界,fail2ban 主要降低重复爆破、日志噪声和资源消耗。
原稿里“10 分钟失败 4 次就能区分人和脚本”没有依据。自动化攻击可以轮换 IP,正常用户也可能因多个客户端、错误密钥或网络重试积累失败。阈值必须结合认证方式、日志格式、管理来源和解封能力设置,并先确认 jail 真的读到了日志;一个 running 的 fail2ban 进程,不等于任何封禁正在生效。
防护顺序从监听开始#
安装防火墙前,先用实际状态回答机器开放了什么:
sudo ss -lntupsudo ufw status numberedsudo nft list rulesetdocker ps --format 'table {{.Names}}\t{{.Ports}}'ss 显示进程真实监听,UFW 与 nftables 显示主机规则,Docker 列表补上容器发布端口。一个只需要被 Nginx 访问的应用,优先绑定 127.0.0.1;同一 Compose 网络内的服务不发布 ports,直接用服务名通信;数据库和管理面只允许私网。删除公网监听比增加一条封禁规则更可靠,因为根本没有连接路径可以被反复试探。
云厂商安全组、主机防火墙和应用认证是不同边界。安全组可以在流量到达主机前过滤,UFW 在主机上提供可审计的默认策略,应用再判断已经到达端口的用户是否有权限。它们可以纵深配合,却不能互相证明:控制台显示安全组已关端口,仍应从外部测试;UFW 拒绝全网,也不能修复服务自身的越权漏洞。
UFW 适合小规模,但底层仍要看得见#
UFW 用更短的命令管理 netfilter 规则,适合规则不多的单机环境。不同发行版可能通过 iptables-nft 兼容层或其他后端落到 nftables,不能简单说“UFW 已经原生换成 nftables”;遇到 Docker、VPN、转发和复杂路由时,应检查最终 nft list ruleset,而不是只看 UFW 的高层列表。
一个仍公开 SSH、80 和 443 的传统 Web 主机,可以从下面的基线开始:
sudo ufw default deny incomingsudo ufw default allow outgoingsudo ufw allow 22222/tcp comment 'SSH'sudo ufw allow 80/tcp comment 'HTTP'sudo ufw allow 443/tcp comment 'HTTPS'sudo ufw enablesudo ufw status verbose默认允许出站便于普通服务器更新和访问依赖,但不是所有威胁模型的终点;需要控制数据外传或只允许固定依赖时,还要设计出站规则。入站默认拒绝的价值是新安装服务即使误监听公网,也不会自动获得访问路径,不过 Docker 等组件可能写入自己的规则,后文需要单独验证。
双栈主机还要确认 /etc/default/ufw 中 IPv6 策略与实际网络一致,并分别从外部测试 A 与 AAAA 地址。只验证 IPv4、却让服务在 [::]:端口 上接受全网连接,是最常见的“防火墙已经开了但另一条栈没检查”。如果完全不用 IPv6,应在系统、云网络和 DNS 三处一致关闭或限制,不能只删 AAAA 记录。
启用前要准备第二条管理路径#
“先放行 SSH 再 ufw enable”是最低线,不是完整回退方案。修改前应保留当前 SSH 会话,再从第二个终端确认新连接成功;有条件时打开云厂商串口或控制台,记录怎样在网络规则错误时恢复。如果 SSH 端口、来源 IP 和防火墙同时改变,一次只改一项,避免锁死后连原因都无法确认。
已有 SSH 连接可能因状态跟踪暂时不断,新连接却已经全部失败,因此不能拿当前窗口仍能输入命令证明规则正确。远程操作生产主机时,也不要使用 ufw --force enable 跳过所有确认。规则生效后,从真实外部网络测试允许端口和应拒绝端口,再关闭备用会话。
如果管理面已经迁入 WireGuard,可以只开放 WireGuard UDP,并让 SSH 只从接口进入:
sudo ufw allow 51820/udp comment 'WireGuard'sudo ufw allow in on wg0 to any port 22 proto tcp comment 'SSH via WireGuard'具体端口和接口名按实际配置替换。即使 SSH 不再公开,仍保留密钥认证和应用最小权限;VPN 设备密钥被盗后,攻击者会成为“合法私网设备”,不能让 WireGuard 代替全部身份验证。
Docker 发布端口不能只看 UFW#
Docker 会创建自己的 netfilter 规则,公开的 0.0.0.0:8080->8080 可能在 UFW 预期的拒绝路径之前被转发。不同 Docker 与防火墙后端版本行为会变化,所以“UFW status 没有 8080”不能证明容器端口没暴露。最小可靠方案仍然是不用 ports,或只绑定回环:
ports: - "127.0.0.1:8080:8080"确实需要公网容器端口时,应按当前 Docker 防火墙后端管理 DOCKER-USER/nftables 等相应链路,并在每次升级后从另一台主机复测,而不是复制一段旧 iptables 规则永久放着。Docker socket 的管理者接近主机 root,容器网络边界也不能只交给 UFW 一层。
fail2ban 依赖日志,不理解攻击本身#
fail2ban 用 filter 从日志中匹配失败事件,按 findtime 与 maxretry 计数,再调用 action 插入临时封禁规则。它适合处理来自同一地址的重复 SSH 失败,却挡不住轮换大量 IP 的低频攻击、已知漏洞利用、被盗合法密钥或真正的 DDoS。密码登录已经关闭后,fail2ban 仍可减少扫描噪声,但安全核心仍是密钥、MFA/私网、补丁和最小暴露面。
自定义应写在 /etc/fail2ban/jail.local 或 jail.d/*.local,不要直接修改发行版的 jail.conf。Debian 最小安装可能没有 /var/log/auth.log,这时 systemd backend 直接读取 journal:
[sshd]enabled = truebackend = systemdport = 22222findtime = 10mmaxretry = 6bantime = 1h这些数字只是保守示例,不是推荐常量。先看正常登录失败怎样记录、是否有共享出口或动态地址,再决定阈值;管理地址稳定时可以谨慎加入 ignoreip,但过宽白名单会让被攻陷的办公网或 VPN 节点永不被封。需要长期递增封禁时可以评估 bantime.increment,仍要保留明确解封路径。
配置完成后先测试解析,再 reload:
sudo fail2ban-client -tsudo systemctl reload fail2bansudo fail2ban-client statussudo fail2ban-client status sshdsudo journalctl -u fail2ban -n 100 --no-pagerstatus sshd 应显示 filter 与 action 的计数,但没有真实失败时计数为零完全正常。更可靠的验证是在保留现有管理会话和控制台的前提下,从另一条可控网络制造少量失败,观察日志、封禁和到期解封;测试地址被封后可以使用 fail2ban-client set sshd unbanip <IP> 恢复。不要在唯一管理 IP 上做第一次实验。
Tunnel 之后,目标不是“零规则”#
当前公共 Web 通过 Cloudflare Tunnel 到回环 Nginx,主机不再需要公开 80/443;管理入口若进入 WireGuard,公网只保留必要的 WireGuard UDP,甚至 SSH 也无需直接暴露。这确实比“开放 Web 端口再靠 fail2ban 观察”更小,但防火墙仍要约束其他服务、IPv6、容器和 VPN 转发,不能因为 Tunnel 健康就关闭主机边界检查。
我现在看 UFW 与 fail2ban 的顺序是:监听地址决定服务是否出现,云与主机防火墙决定谁能连接,认证决定连接者能做什么,fail2ban 最后对一部分重复失败增加成本。把后面的工具装得再复杂,也补不了前面一个无意公开的数据库端口;先删入口,再收来源,最后才谈自动封禁,才是这两层真正合适的位置。
