fail2ban 实战:SSH 与 Nginx 登录爆破封禁、自定义 filter 与 recidive 长效黑名单

服务器 24 小时被爆破:fail2ban 从零配置到长效封禁

只要你的 VPS 暴露在公网 22 端口上过一天,SSH 日志里就一定有爆破记录。随手一条命令就能看到规模:

grep "Failed password" /var/log/auth.log | wc -l
# 一台新机器上线 24 小时,这个数字通常在几百到几万之间

看来源分布就更直观:

grep "Failed password" /var/log/auth.log \
  | grep -oE 'from [0-9.]+' | sort | uniq -c | sort -rn | head -20

改端口、禁用密码登录当然要做,但那是"减少暴露面",不是"封禁攻击者"。真正让爆破成本上升的,是 fail2ban:它读日志、识别失败模式、调用防火墙把攻击 IP 关进小黑屋。个人站长最值得装的一个安全组件,几乎没有之一。

安装与第一个 jail

Debian/Ubuntu 下:

apt update && apt install -y fail2ban
systemctl enable --now fail2ban
fail2ban-client status         # 应列出已启用的 jail

强烈建议不要直接改 /etc/fail2ban/jail.conf,因为包升级会覆盖。正确做法是建一个 jail.local,只写覆盖项:

# /etc/fail2ban/jail.local
[DEFAULT]
# 封禁时长;负数表示永久封禁(推荐配合 recidive 使用,见下文)
bantime  = 1h
# 统计窗口:在这个时间窗内累计失败次数
findtime = 10m
# 失败多少次触发封禁
maxretry = 5
# 白名单,你自己的固定 IP、内网、跳板机一定要写进来
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 你的办公IP

backend = systemd

[sshd]
enabled = true
port    = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime  = 3d

重启生效并查看:

fail2ban-client reload
fail2ban-client status sshd
# 输出会显示 Currently failed / Total failed / Banned IP list

backend = systemd 很重要。在新版系统上,sshd 的日志可能不再写入 /var/log/auth.log,而是进 journald。如果 fail2ban 报"Failed to access log file",基本就是这个原因。

最容易把自己关在门外的错误就是 ignoreip 没写自己的 IP。上线前务必确认你当前 IP 在白名单里,否则一次输错密码就自锁。如果不幸被锁,从 VPS 商家控制台的 VNC/救援模式进去,执行 fail2ban-client unban --all 即可。

第二类场景:保护 Nginx 下的登录、搜索接口

SSH 只是入口之一。你的网站后台、wp-login.php、xmlrpc 接口同样在被扫。先在 Nginx 里让这些请求留下可识别的日志,再配对应 jail。

用一个独立的 access_log,避免和正常访问混在一起:

# Nginx 配置
location = /wp-login.php {
    access_log /www/wwwlogs/auth_attempt.log;
    fastcgi_pass unix:/tmp/php-cgi.sock;
    include fastcgi.conf;
}

location = /xmlrpc.php {
    access_log /www/wwwlogs/auth_attempt.log;
    deny all;   # 不用 xmlrpc 就直接关掉,比任何防护都彻底
}

然后写一个自定义 filter。filter 的本质是一组正则,每条正则要用 <HOST> 标记出攻击者 IP:

# /etc/fail2ban/filter.d/nginx-auth.conf
[Definition]
failregex = ^<HOST> - .* "POST /wp-login\.php HTTP/.*" (200|302)
            ^<HOST> - .* "POST /wp-login\.php HTTP/.*" 200
ignoreregex =

注意正则里 < > 是 fail2ban 的占位符,不是 HTML 转义,写错会导致 filter 解析失败。配合的 jail:

[nginx-auth]
enabled  = true
port     = http,https
filter   = nginx-auth
logpath  = /www/wwwlogs/auth_attempt.log
maxretry = 5
findtime = 10m
bantime  = 1d

验证 filter 是否写得对,可以用 fail2ban-regex 离线测试,这是最被低估的命令:

fail2ban-regex /www/wwwlogs/auth_attempt.log /etc/fail2ban/filter.d/nginx-auth.conf
# 输出会显示:匹配了多少行、哪些行匹配、用的哪条 failregex

如果显示 "No match",先别急着上生产,把 failregex 调对再启用。

recidive:让惯犯永久出局

单个 jail 的封禁时间通常设几天。但真正的攻击者会在解封后立刻回来。recidive jail 的思路是:监视 fail2ban 自己的日志,谁被反复封禁,就重罚。

[recidive]
enabled  = true
logpath  = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime  = 4w
findtime = 1d
maxretry = 3

banaction_allports 表示封禁该 IP 的所有端口,而不只是触发的那一个。效果很直接:一个 IP 若一天内被 SSH jail 封 3 次,直接进 4 周黑名单,后续任何连接尝试都进不来。多数自动化爆破脚本撞上这层就会放弃。

封禁动作的底层:NFTables 还是 iptables

fail2ban 只是"决策者",真正落地的封禁靠 banaction。新版发行版默认走 nftables:

# 看当前用的是哪种
grep -r "banaction" /etc/fail2ban/jail.conf | head
ls /etc/fail2ban/action.d/ | grep -E 'nftables|iptables'

# 查看已下发的封禁规则
nft list ruleset | grep -A5 f2b

如果服务器上装的是纯 iptables(比如一些 CentOS 7 老机器),确认 banaction = iptables-multiport 能正常工作:

iptables -L -n | grep f2b

一个常见坑:服务器上装了 Docker,Docker 会操作 iptables 的 FORWARD 链,某些情况下与 fail2ban 的规则冲突,表现为"规则在,但流量没被拦"。排查方法是确认封禁规则位于 INPUT 链且优先级足够靠前。若用 nftables 版 fail2ban,基本不会遇到这个问题,这也是推荐新机用 nftables 的原因之一。

监控与告警:别让 fail2ban 静默失效

fail2ban 最危险的失效方式是"默默不再封禁"。原因通常有三:日志轮转后 logpath 不存在、正则因日志格式变化不再匹配、jail 被误关。

定期检查:

# 每天统计各 jail 状态
fail2ban-client status | tail -n +2 | tr ',' '\n' | tr -d ' ' | while read j; do
  echo "== $j"; fail2ban-client status "$j"
done

# 简单告警:若 1 小时内完全没有任何封禁记录,可能已失效

把下面这段放进 crontab,每小时检查一次活跃度:

#!/bin/bash
COUNT=$(grep -c "Ban " /var/log/fail2ban.log)
LAST=$(stat -c %Y /var/log/fail2ban.log)
NOW=$(date +%s)
# 若 6 小时没有新的封禁行为,发通知
if [ $((NOW - LAST)) -gt 21600 ]; then
  echo "[告警] fail2ban 6 小时无动作,请检查 $(hostname)" | mail -s "fail2ban 异常" you@example.com
fi

封禁之外的组合拳

fail2ban 是最有价值的一环,但完整的安全加固应该是:

  1. SSH 只允许密钥登录:PasswordAuthentication no,根本上废掉密码爆破。
  2. 改默认端口 + 防火墙只放行必要端口:减少被扫描到的概率,但不依赖"隐蔽"作为安全。
  3. fail2ban 做封禁:处理漏网之鱼,兼顾 SSH 与 Web 场景。
  4. Nginx 限流:防止 CC 型攻击压垮服务。
  5. 定期审计:lastb 看失败登录、journalctl -u sshd 看异常会话。

这几条加起来不到一小时就能配完,却能把 VPS 的失陷概率显著降低。个人站长没有运维团队,能做的就是把这些自动化防线搭好,然后定期检查它们是否还活着。fail2ban 装完不是终点,验证它真的在封、并且没把你封了,才算配置完成。

Last modification:September 29th, 2026 at 07:24 pm

Leave a Comment