fail2ban 实战:自动封禁 SSH 暴破与 Web 攻击

fail2ban 是 Linux 服务器上最常用的入侵防御工具:它实时监控 SSH、Nginx、WordPress 等服务的日志,一旦发现某个 IP 在短时间内频繁尝试登录失败、暴力破解或扫描漏洞,就自动调用 iptables / nftables 把该 IP 临时封禁。对于直接暴露在公网的机器来说,fail2ban 几乎是零成本的第一道自动防线。本文从原理讲到实战,带你把 fail2ban 配成真正能挡住自动化暴破的护城河。

一、fail2ban 到底在做什么

fail2ban 的核心由三个概念组成:filter(过滤器)负责用正则表达式从日志里匹配「恶意行为」;action(动作)负责在匹配成功后执行封禁(调用防火墙规则);而 jail(监狱)把「某个服务 + 触发条件 + 惩罚动作」绑定在一起。一个 jail 典型的逻辑是:扫描 /var/log/auth.log,如果某个 IP 在 10 分钟内失败 5 次,就用 nftables 封它 1 小时。

配置文件分为两层:/etc/fail2ban/jail.conf 是发行版自带的默认模板,永远不要直接改它;所有自定义都写在 /etc/fail2ban/jail.local 或 jail.d/*.conf 里,后者优先级更高。filter 的正则规则则放在 /etc/fail2ban/filter.d/ 目录下。

二、安装与最小可用配置

在 Debian / Ubuntu 上安装只需一条命令,CentOS / Rocky 则使用 dnf。安装后服务默认不会自动开启任何 jail,需要先建一个 jail.local:

# Debian / Ubuntu
sudo apt update && sudo apt install -y fail2ban
# 复制默认配置,避免直接改动 jail.conf
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo systemctl enable --now fail2ban

在 jail.local 的 [DEFAULT] 段设置全局惩罚参数,这些会被所有 jail 继承:

[DEFAULT]
# 封禁时长:首次违规封 1 小时
bantime  = 1h
# 统计窗口:在 findtime 内达到 maxretry 即封禁
findtime = 10m
maxretry = 5
# 新版系统用 nftables,老系统用 iptables-multiport
banaction = nftables-multiport
# 日志后端:systemd-journald 的现代发行版用 systemd
backend  = systemd

三、SSH 防护:挡住 90% 的自动化暴破

公网服务器的 SSH 端口每天会被脚本扫描数千次,fail2ban 的 sshd jail 是最该第一时间开启的。把 maxretry 收紧到 3、bantime 拉到 24 小时,能让绝大多数撞库脚本在第一次试探时就吃闭门羹:

[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
# 容错更低:连错 3 次就封
maxretry = 3
bantime  = 24h
# 只允许密钥登录的服务器,可进一步配合禁用密码
# 详见《SSH 连接故障排查实战》中的服务端配置

修改后重载:sudo fail2ban-client reload sshd。如果你也维护着容器化的服务,记得参考 Docker 容器网络与端口映射实战 把宿主机真实客户端 IP 透传出来,否则下面会说到的「误封网关」坑就会找上门。

四、Web 层防护:Nginx 登录暴破与扫描

很多站点把后台 /wp-admin、/admin 或 API 登录口直接暴露,被脚本枚举密码。基于 Nginx 的 401/403 返回频率,可以新建一个 jail 来封禁:

[nginx-login]
enabled  = true
filter   = nginx-login
logpath  = /var/log/nginx/access.log
maxretry = 10
findtime = 5m
bantime  = 12h
port     = http,https

然后在 /etc/fail2ban/filter.d/nginx-login.conf 里写匹配规则。Nginx 的日志格式通常是 $remote_addr - $user [$time] "$request" $status,我们用 failregex 抓 401 / 403:

[Definition]
failregex = ^<HOST> -.*"(GET|POST).*HTTP/.*" 401
            ^<HOST> -.*"(GET|POST).*HTTP/.*" 403
ignoreregex =

Nginx 反代与真实 IP 透传的细节,建议搭配 Nginx 反向代理实战 一起看,否则容器前加了反代后日志里的 HOST 可能是网关地址。

五、自定义 filter:识别业务日志里的恶意行为

fail2ban 真正的威力在于 failregex 可以适配任何文本日志。比如拦截频繁扫描不存在路径的脚本(大量 404):

# /etc/fail2ban/filter.d/nginx-404.conf
[Definition]
failregex = ^<HOST> - - .* "(GET|POST) .* HTTP/.*" 404 
ignoreregex =

# 对应 jail
[nginx-404]
enabled  = true
filter   = nginx-404
logpath  = /var/log/nginx/access.log
maxretry = 30
findtime = 2m
bantime  = 6h

<HOST> 是 fail2ban 的占位符,运行时会替换成从日志提取出的客户端 IP;ignoreregex 用来放行白名单路径,避免误伤正常爬虫。规则写完后用 fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-404.conf 本地验证命中数,再上线。

六、日常运维:查看封禁与解封

封禁生效后,这些命令是日常排查必备:

# 查看所有 jail 与当前被封 IP 数量
sudo fail2ban-client status
# 查看具体 jail(如 sshd)的封禁列表
sudo fail2ban-client status sshd
# 误封时手动解封某个 IP
sudo fail2ban-client set sshd unbanip 1.2.3.4
# 把办公网段加入永久白名单,避免自己被锁
sudo fail2ban-client set sshd addignoreip 10.0.0.0/8

如果你常用远程 IDE 维护服务器,白名单里也别忘了加上跳板机与本地出口 IP,相关环境配置可参考 VS Code 远程开发实战。

七、与防火墙、容器的协同

banaction 的取值必须和服务器实际的防火墙一致:跑 nftables 的机器用 nftables-multiport,还在用 legacy iptables 的用 iptables-multiport,选错会导致封禁规则写不进去。另一个是容器陷阱——当 Nginx 跑在 Docker 里、又用桥接网络时,access.log 里记录的 <HOST> 其实是宿主机网关 IP,fail2ban 会误封网关、连累整台机器。解决办法是用 host 网络模式,或在反代层把真实客户端 IP 透传进日志。

更进一步,可以写自定义 action 把恶意 IP 同步到 Cloudflare 等 CDN 层,在流量到达源站前就拦掉;也能配合 recidive jail 统计「累犯」,对反复被封的 IP 直接拉黑更长时间。

八、避坑与最佳实践

常见坑现象解法
误封自己输错几次密码后被锁在外用 addignoreip 把办公 / 跳板机网段加白
容器日志 HOST 是网关封错 IP、整站异常host 网络或透传真实客户端 IP
bantime 太短攻击者换 IP 反复试探启用 recidive jail,累犯长封
fail2ban 自身挂掉毫无感知地失去防护systemd 自启 + 监控 status 告警
filter 正则写错该封的不封 / 误封正常用户上线前用 fail2ban-regex 验证命中数

九、落地清单

照着下面六步,十分钟内就能给服务器装上自动防御:

  • 安装 fail2ban,并把 jail.conf 复制为 jail.local;
  • 在 DEFAULT 段设置 bantime / findtime / banaction;
  • 开启 [sshd],把 maxretry 收紧到 3;
  • 为 Nginx 后台登录、404 扫描各加一个 jail;
  • 用 addignoreip 把办公网段加入白名单;
  • systemctl enable --now fail2ban,并接入监控定期检查 status。

十、累犯 jail:对反复违规的 IP 长期拉黑

短时间的 bantime 对「打一枪换一个 IP」的脚本工具有限,但对同一个长期骚扰的 IDC 段效果很好。更彻底的做法是启用 recidive jail:它统计「已经被封过 N 次」的 IP,把这些累犯直接拉黑更长的时间(甚至永久)。recidive 依赖 fail2ban 自身的日志,所以必须配合 logpath 指向 fail2ban 的日志文件:

[recidive]
enabled  = true
filter   = recidive
logpath  = /var/log/fail2ban.log
maxretry = 5
findtime = 1w
bantime  = 4w
# 累犯使用更激进的动作,单独封更久

启用后,第一次被封是 24 小时,第二次、第三次会逐步累积,到第五次直接封四周。这套「渐进式惩罚」既不会误伤偶发输错密码的正常用户,又能让真正的恶意扫描器付出越来越高的时间成本。配合前面第八节的白名单与监控,整套方案就能在不打扰人的前提下,把公网服务器的攻击噪音压到最低。

fail2ban 不是银弹,它挡的是「自动化、低水平」的批量攻击,不能替代强密码、密钥登录和及时打补丁。但作为成本几乎为零的第一道防线,它值得出现在每一台公网 Linux 服务器的标准镜像里。

上一篇 Xinference 实战:多模型统一推理服务
下一篇 nushell 实战:用结构化管道重塑命令行工作流
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]