在《服务器 SSH 加固实践》里我提过一句:就算关了密码登录,fail2ban 也值得装——它是"事后防御"的最后一道闸。那篇只给了最小配置,这篇把它讲透:fail2ban 到底怎么工作、jail 是什么、参数怎么调,以及如何给 Nginx 等其他服务也加上同样的保护。
一、fail2ban 在做什么
fail2ban 的原理一句话就能说清:盯着服务的日志,发现某个 IP 反复认证失败,就调用防火墙把它拉黑一段时间。它不防第一次失败,防的是"脚本式的反复试探"。
它的配置单元叫 jail(监狱)。一个 jail 由三部分组成:
- filter(过滤器):用正则从日志里挑出"失败行为";
- action(动作):命中后做什么,通常是调用防火墙封禁 IP;
- 参数:多快、多少次算"犯规",封多久。
比如 sshd jail = "从 auth 日志中匹配密码错误" + "5 分钟内失败 5 次" + "用 iptables 封 1 小时"。理解了这个结构,后面所有配置都是往这三个地方填东西。
二、安装与开机自启
Debian/Ubuntu 与 CentOS/Rocky 的安装命令略有不同:
# Debian / Ubuntu
sudo apt update && sudo apt install -y fail2ban
# CentOS / Rocky
sudo dnf install -y fail2ban
安装后立即启用并查看状态:
sudo systemctl enable --now fail2ban
systemctl status fail2ban --no-pager -l
看到 Active: active (running) 就说明它在跑了。默认配置其实已经带了 sshd 的 jail 模板,只是默认关闭,需要手动开启。
三、第一个 jail:给 sshd 上锁
fail2ban 的配置都在 /etc/fail2ban/ 下:jail.conf 是官方默认模板,不要直接改它(软件升级会覆盖)。自定义内容写进 jail.local,它优先级最高且不会被升级覆盖。用 root 权限创建:
sudo nano /etc/fail2ban/jail.local
写入最基础的 sshd jail:
[sshd]
enabled = true
port = ssh
maxretry = 5 # 失败 5 次
findtime = 10m # 10 分钟内
bantime = 1h # 封禁 1 小时
保存后重载配置并确认生效:
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
能看到 Currently banned 和封禁列表就说明 jail 在干活了。如果你已经按 SSH 加固篇关闭了密码登录,这个 jail 平时可能很安静——但它仍会在"有人拿到密钥还反复试"或你临时放开密码认证时兜底,属于保险,不嫌多。
参数含义:findtime是观察窗口,maxretry是窗口内允许的最大失败次数,bantime是命中后的封禁时长。三个一起理解:10 分钟内失败超过 5 次,封 1 小时。
四、日常命令
fail2ban 的日常操作都通过 fail2ban-client 完成,最常用的几个:
sudo fail2ban-client status # 所有 jail 概览
sudo fail2ban-client status sshd # 某个 jail 的封禁详情
sudo fail2ban-client set sshd banip 1.2.3.4 # 手动封一个 IP
sudo fail2ban-client set sshd unbanip 1.2.3.4 # 手动解封(误封时救命)
sudo fail2ban-client reload # 改了配置后重载
fail2ban 自己的运行日志在 /var/log/fail2ban.log,里面能看到每次封禁的完整记录(谁、何时、封到何时):
sudo tail -f /var/log/fail2ban.log
五、调参:递增封禁、白名单与 recidive
固定封 1 小时对脚本来说不痛不痒——它换个 IP 再来就是了。真正好用的调参是递增封禁:同一个 IP 反复犯案,每次封禁时间翻倍:
[DEFAULT]
bantime.increment = true
bantime.factor = 2 # 每次翻倍:10m → 20m → 40m …
bantime.maxtime = 32w # 上限 32 周,别真的无限涨
bantime = 10m
写在 [DEFAULT] 段会对所有 jail 生效。配合它,还可以开启内置的 recidive(惯犯)jail:某个 IP 在多个 jail 里反复触发封禁时,把它升级为超长封禁:
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
maxretry = 5
findtime = 1d # 一天内
bantime = 1w # 直接封一周
别忘了给"自己人"开白名单。把家里的固定 IP、公司出口 IP 加进 ignoreip,永远不封(用空格分隔,支持网段):
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 你的家庭固定IP/32
六、让日志来源正确:journald 与改端口
fail2ban 通过 filter 读日志,日志来源不对,一切都白搭。两个常见情况:
- 系统日志进了 journald:Ubuntu 22.04 及以后,若发现
/var/log/auth.log是空的或不存在,而journalctl -u ssh能看到登录记录,说明该走 systemd 后端——在对应 jail 里加一行backend = systemd即可; - 改了 SSH 端口忘了同步:封禁动作是按端口封的,SSH 改到 2222 后,jail 里要写
port = 2222,否则它封的是 22 端口,等于没封。
七、再加一个 jail:Nginx 登录爆破
fail2ban 的价值在于一个框架护所有服务。举一个和本站(Nginx)相关的例子:网站后台如果做了 HTTP 基础认证,暴力试密码的人同样会留下大量 401 记录。fail2ban 内置了现成的 nginx-http-auth filter,开启一个 jail 即可:
[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 10m
bantime = 2h
重载后查看:
sudo systemctl restart fail2ban
sudo fail2ban-client status nginx-http-auth
内置 filter 已经覆盖 sshd、nginx 登录、vsftpd、dovecot、postfix 等一大批常见服务——先查 /etc/fail2ban/filter.d/ 里有没有现成名字,90% 的情况不用自己写正则。
八、没有现成 filter?自己写一个
当内置 filter 覆盖不到你的服务时,自定义 filter 其实很简单。比如某个应用把登录失败写成一行日志:
2026-09-08 12:00:01 203.0.113.9 login failed for user admin
先建一个 filter 文件(名字自取,如 myapp.conf):
# /etc/fail2ban/filter.d/myapp.conf
[Definition]
failregex = ^\S+ \S+ <HOST> login failed
ignoreregex =
其中 <HOST> 是 fail2ban 内置的"匹配 IP 或主机名"占位符,会作为封禁对象。然后在 jail.local 里启用同名 jail:
[myapp]
enabled = true
logpath = /var/log/myapp/app.log
maxretry = 5
findtime = 10m
bantime = 1h
写正则最容易出错,好在 fail2ban 提供了测试工具,拿真实日志行验证你的 filter:
sudo fail2ban-regex /var/log/myapp/app.log /etc/fail2ban/filter.d/myapp.conf
它会告诉你匹配了多少行、哪些行没匹配上——调正则时比"重启看效果"高效得多。
九、常见坑速查
- 封了没效果:先确认
fail2ban.log里真的出现了Ban 某IP;再确认 action 用的防火墙和你系统的防火墙一致(装了 ufw 却让 iptables 去封,可能各自为政); - 把自己封了:立刻
fail2ban-client set 该jail unbanip 你的IP解封,然后把你的 IP 加进ignoreip; - 改了端口没同步:见第六节,jail 的
port要跟着 SSH 端口走; - 正则不生效:先用
fail2ban-regex离线测试,别反复重启试错; - 重启后封禁清空:fail2ban 的封禁状态默认不持久化,重启服务会清零重来——配合
bantime.increment它很快又会"攒"回来,不必纠结。
结语
把这几篇串起来看,一套个人服务器的防线已经成型了:《服务器 SSH 加固实践》焊死入口(密钥登录、关密码),本文的 fail2ban 在门口装了监控(谁反复试探就拉黑),配合 Nginx 篇的安全响应头把站点本身也包了一层。攻不进来的脚本,最终都会安静下来——你再去翻登录日志,剩下的就只有你自己的记录了。
fail2ban 不是银弹:它防脚本,不防真正有耐心的攻击者,更不能替代密钥认证、防火墙收窄端口这些"从根上减少暴露"的做法。防线要一层一层叠,而不是只指望某一个工具。下一篇准备聊聊让网站亮起小锁的 HTTPS 配置,先把地址栏那把锁挂上。