服务器 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 listbackend = 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 = 3banaction_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 是最有价值的一环,但完整的安全加固应该是:
- SSH 只允许密钥登录:
PasswordAuthentication no,根本上废掉密码爆破。 - 改默认端口 + 防火墙只放行必要端口:减少被扫描到的概率,但不依赖"隐蔽"作为安全。
- fail2ban 做封禁:处理漏网之鱼,兼顾 SSH 与 Web 场景。
- Nginx 限流:防止 CC 型攻击压垮服务。
- 定期审计:
lastb看失败登录、journalctl -u sshd看异常会话。
这几条加起来不到一小时就能配完,却能把 VPS 的失陷概率显著降低。个人站长没有运维团队,能做的就是把这些自动化防线搭好,然后定期检查它们是否还活着。fail2ban 装完不是终点,验证它真的在封、并且没把你封了,才算配置完成。