Fail2ban 实战完全指南——从安装配置到自定义规则,给自建服务装上自动防盗门

IT大叔的老读者都知道,我在这个站上写过不少…

IT大叔的老读者都知道,我在这个站上写过不少自建服务的教程——Nginx、Docker、Prometheus、Systemd……但有一个话题一直想写却总觉得”太基础”,直到上个月一个朋友的VPS被扫了 SSH 端口,暴力破解日志拉出来整整两万条记录,我才意识到:很多自部署玩家根本没给自己的服务加一道自动防盗门。

今天就把 Fail2ban 从零到实战讲透。这东西我用了快五年,从单机防 SSH 暴力破解到给 Web 服务加自定义规则,几乎是所有自部署玩家最值得先装上的安全工具——没有之一。

Fail2ban 是什么?一句话讲清楚

Fail2ban 就是一个日志驱动的自动防火墙。它盯着你服务的日志文件(比如 SSH 的 auth.log、Nginx 的 access.log),一旦发现某个 IP 在指定时间内失败次数超过阈值,就通过调用系统防火墙(iptables/nftables)把这个 IP 临时封掉一段时间。

说白了就是:谁反复试密码,自动拉黑。

基本原理不复杂,但用好了能挡住 99% 的自动化扫描和暴力破解。

第一步:安装 Fail2ban

绝大多数 Linux 发行版都包好了,直接装:

# Debian/Ubuntu
sudo apt update && sudo apt install fail2ban -y

# CentOS/Rocky/Alma
sudo dnf install epel-release -y && sudo dnf install fail2ban -y

# 装完检查服务状态
sudo systemctl status fail2ban

装完默认就已经开始保护 SSH 了,但我不建议直接用默认配置——默认 jail 的一些参数对生产环境来说太松。我们需要自己配。

第二步:核心配置——别动 jail.conf,用 jail.local

这是新手最容易踩的坑。Fail2ban 的主配置文件在 /etc/fail2ban/jail.conf,但升级包的时候这个文件会被覆盖。正确的做法是创建 jail.local,你的配置会覆盖同名参数:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo vi /etc/fail2ban/jail.local

先改全局默认值,找到 [DEFAULT] 段:

[DEFAULT]
# 封禁时间:3600 秒 = 1 小时(默认 600 秒太短了)
bantime = 3600

# 发现时间窗口:统计过去 10 分钟内的失败次数
findtime = 600

# 最大重试次数:10 分钟内允许失败 5 次,超过就封
maxretry = 5

# 封禁动作:默认用 iptables,现在很多系统已经切 nftables
# 自动检测,不用手动改
banaction = %(banaction_allports)s

# 发邮件通知(可选,不配也行)
# destemail = you@example.com
# action = %(action_mwl)s

这里解释一下我的考量:bantime = 3600 是因为扫端口的机器人通常会换 IP 继续搞,封太短没意义,封太长万一误伤自己人(比如你出差手机 IP 变了反复输错密码)也麻烦——1小时是个合理折中。你自己的服务器如果只有你一个人用,调到 86400(24小时)也没问题。

第三步:保护 SSH——这是底线

SSH 暴力破解是 VPS 上最最常见的攻击,没有之一。默认的 sshd jail 已经启用,但我们来确认一下并微调参数。在 jail.local 末尾加上或修改:

[sshd]
enabled = true
port    = ssh
logpath = %(sshd_log)s
# 如果你改了 SSH 端口,这里要同步改:
# port = 2222
maxretry = 3
findtime = 600
bantime  = 86400

重点是 maxretry = 3 配合 bantime = 86400——SSH 是核心入口,3 次输错直接关 24 小时,不过分。你自己的机器你记得密码,不会错三次的。

改完配置重启 Fail2ban:

sudo systemctl restart fail2ban

# 检查 SSH jail 是否生效
sudo fail2ban-client status sshd

输出应该类似这样:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     47
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 3
   |- Total banned:     12
   `- Banned IP list:   185.220.101.xx 45.33.32.xx 91.121.88.xx

看到 Currently banned: 3 和那个 IP 列表了吗?这说明在你毫不知情的情况下,已经有三个 IP 被自动封了。这就是 Fail2ban 的价值——它在你睡觉的时候也在站岗

第四步:保护 Nginx/Caddy Web 服务

SSH 安全了,那你的 Web 服务呢?如果网站有登录页面(比如 WordPress 后台、你的 Open WebUI、Vaultwarden 等),一样会被机器人扫。我们需要配置 Nginx jail 来防暴力破解。

Fail2ban 自带几个 Nginx 相关的 jail,但默认都禁用了。我们来启用它们。在 jail.local 里追加:

[nginx-http-auth]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 5
findtime = 600
bantime  = 3600

# 针对 404 扫描(某些人在扫你的后台路径)
[nginx-noscript]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/access.log
maxretry = 6
findtime = 300
bantime  = 3600

如果你用 Caddy 而不是 Nginx,日志路径不一样。Caddy 的日志默认在 /var/log/caddy/ 或者 journald,需要自己写一个 filter。咱们可以单独建一个文件来定义 custom jail:

# /etc/fail2ban/jail.d/caddy-auth.conf
[caddy-auth]
enabled  = true
port     = http,https
# Caddy 日志走 journald 的话用这个 filter
logpath  = /var/log/caddy/*.log
maxretry = 5
findtime = 600
bantime  = 3600

重启 Fail2ban 让新 jail 生效:

sudo systemctl restart fail2ban
sudo fail2ban-client status nginx-http-auth
sudo fail2ban-client status caddy-auth   # 如果配了的话

第五步:自定义规则——自己写 filter 才是高阶玩法

很多时候,Fail2ban 自带规则不够用。比如你自建了一个服务(比如 Open WebUI、Paperless-ngx),它的登录失败日志格式跟标准的不一样。这时候就需要自己写 filter。

一个 filter 本质上就是一个正则表达式文件。举个例子,假设你有一个自建服务,它的日志里登录失败的记录是这种格式:

[2026-07-27 02:15:33] LOGIN_FAILED from 192.168.1.100: Invalid password for user admin

我们为它写一个 filter:

# /etc/fail2ban/filter.d/myapp-login.conf
[Definition]
failregex = ^\[.*\] LOGIN_FAILED from <HOST>: .*$
ignoreregx =

然后在 jail.localjail.d/myapp.conf 里引用:

[myapp-auth]
enabled  = true
port     = http,https
filter   = myapp-login
logpath  = /opt/myapp/logs/auth.log
maxretry = 5
findtime = 600
bantime  = 7200

调试 filter 的小技巧:用 fail2ban-regex 命令测试正则是否匹配,不用反复重启:

# 测试日志行是否能被 filter 捕捉
sudo fail2ban-regex /opt/myapp/logs/auth.log /etc/fail2ban/filter.d/myapp-login.conf

# 输出里会告诉你:
# - Successfully matched: 多少行被匹配
# - 没匹配的行会列出来方便排查

这个命令我几乎每次写新 filter 都要用——写正则最怕的就是自以为写对了实际上一条都没抓到。

第六步:日常运维——你会用到的几个命令

Fail2ban 的日常管理就这几个命令,背下来就好:

# 查看所有已启用的 jail 及封禁状态
sudo fail2ban-client status

# 查看某个 jail 的详情(被封 IP 列表)
sudo fail2ban-client status sshd

# 手动解封一个 IP(如果你误封了自己)
sudo fail2ban-client set sshd unbanip 你的IP地址

# 查看封禁日志(封了谁、什么时候、为什么)
sudo tail -f /var/log/fail2ban.log

# 临时手动封禁一个 IP(不需要等日志触发)
sudo fail2ban-client set sshd banip 恶意IP地址

# 重载配置(改完 jail.local 后执行)
sudo fail2ban-client reload

特别提醒:千万别把自己封了。我在刚用 Fail2ban 的时候至少把自己封过三次——出差换网络、VPN 切节点、手机热点 IP 变了,SSH 连不上只能去 VPS 控制面板的 VNC 里解封。建议你在配好 Fail2ban 之后,专门留一个终端窗口别关,先测试 SSH 连接正常再退出。

第七步:进阶——白名单与多机协同

白名单:如果你有固定的办公 IP(比如公司专线),可以在 jail.local[DEFAULT] 段加上:

ignoreip = 127.0.0.1/8 你的固定IP1 你的固定IP2

白名单里的 IP 永远不会被 Fail2ban 封禁。如果你的家庭宽带没有固定 IP,可以考虑用 Tailscale/WireGuard 连入内网再 SSH,然后把 Tailscale 的子网 IP 加进白名单。

多机协同封禁:如果你管理多个 VPS 或物理机,每台各自封 IP 其实效率不高——同一个攻击者扫了 A 机器没得手,转手就去扫 B 机器了。Fail2ban 支持 action = %(action_md)s 把封禁信息写到共享数据库(MySQL/SQLite),配合 fail2ban-broker 可以实现多机共享黑名单。不过这个配置稍复杂,对大部分自部署玩家来说不是刚需,这里先不展开了。

总结一张速查表

场景Jail 名称推荐 bantime说明
SSH 防护sshd86400核心入口,3次失败封24h
Nginx 登录认证nginx-http-auth3600防 Web 登录暴力破解
Nginx 路径扫描nginx-noscript3600防 404 扫描和路径探测
邮件服务器postfix/sendmail86400防 SMTP 认证爆破
自建服务(通用)自定义 filter7200自己写正则抓失败日志

配置 Fail2ban 这件事,花 20 分钟折腾一次,以后就不用再管它了——它会安安静静地在后台帮你拦掉至少 90% 的自动扫描和暴力尝试。我自己的服务器跑了大半年,单是 SSH 端口就被试了上万次,但一个都没进来过。

安全这件事,不一定是花大钱买硬件防火墙。有时候一个简单的开源工具配好了,就已经赢过了 99% 的脚本小子。

今天就到这里。有什么问题欢迎留言,IT大叔会挑有代表性的写后续。

—— IT大叔,一个写了十几年代码还在折腾的自部署玩家

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注