Fail2ban 实战:用 Fail2ban 阻断 SSH 暴力破解

只要把一台 Linux 服务器暴露到公网,/var/log/auth.log 里很快就会出现成片的 SSH 暴力破解尝试:陌生 IP 拿着字典不停地猜用户名和密码。Fail2ban 就是为解决这个问题而生的入侵防御工具——它实时扫描日志,发现短时间内失败次数超过阈值的 IP,便自动调用 iptables / nftables 临时封禁。本文从安装到自定义规则,手把手教你用 Fail2ban 守住 SSH 与 Web 服务的入口,把自动化攻击挡在门外。

一、Fail2ban 工作原理

Fail2ban 不是防火墙本身,而是一个”日志驱动的封禁调度器”。它由三个核心概念组成:filter(过滤器)负责从日志里用正则提取出”这是一次失败登录”;action(动作)负责真正执行封禁(默认调用 iptables 加一条 DROP 规则);jail(监狱)把某个 filter 和某个 action 绑定到具体的日志路径与封禁策略上。三者组合,就构成了一条”发现异常 → 触发动作”的闭环。

1.1 监控与过滤

Fail2ban 默认每隔一秒轮询被监控的日志文件(也支持 systemd 的 journal 后端)。filter 用一组正则匹配”失败”行,例如 SSH 的 Failed password for。匹配上的源 IP 会被记入一个内部计数表,当单位时间内的失败次数达到 maxretry 阈值,就触发 ban。

1.2 封禁与解封

封禁动作默认通过 iptables 在 filter 表加一条针对该 IP 的 REJECT / DROP 规则,bantime 到期后自动移除。这种设计的好处是”临时惩罚”——既挡住暴力破解,又不会把误操作的自己永久关在门外。

二、安装与基础配置

绝大多数发行版仓库都自带 Fail2ban,一条命令即可安装。以 Debian / Ubuntu 为例:

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

# CentOS / Rocky(需先启用 EPEL)
sudo dnf install -y epel-release && sudo dnf install -y fail2ban

# 启动并设置开机自启
sudo systemctl enable --now fail2ban

Fail2ban 的主配置在 /etc/fail2ban/jail.conf,但千万不要直接改它——升级时会被覆盖。正确做法是在 /etc/fail2ban/jail.d/ 下新建 *.local 文件,它会以”覆盖”的方式合并进主配置。最基础的一层保护,先确保 SSH 监狱被打开:

# /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 5
bantime  = 1h
findtime = 10m

上面这段的含义是:在 10 分钟(findtime)内 SSH 登录失败 5 次(maxretry),就封禁该 IP 1 小时(bantime)。改完配置后重载:sudo fail2ban-client reload。

三、加固 SSH 防护

在开启 Fail2ban 之前,建议先把 SSH 的基础安全做扎实:禁用 root 直登、改用密钥登录、修改默认 22 端口。Fail2ban 是第二道防线,而不是替代措施。若你用的是 Nginx 反向代理 统一入口,也别忘了后端 SSH 的防护。

当服务器把 SSH 端口改成了 2222,需要告诉 jail 正确的端口,否则 Fail2ban 会去封一个没人连的 22 端口:

# /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled  = true
port     = 2222
filter   = sshd
logpath  = /var/log/auth.log
backend  = systemd
maxretry = 4
bantime  = 24h
findtime = 15m

这里把 backend 设为 systemd,让 Fail2ban 直接从 journald 读日志,避免某些系统日志路径不一致导致漏判。把 bantime 拉长到 24 小时,能显著抬高攻击者”换 IP 再来”的成本。

四、保护 Web 服务(Nginx)

SSH 之外,Web 服务同样会被扫:有人专门用脚本遍历 /wp-admin、/.env、/phpmyadmin 等敏感路径。我们可以为 Nginx 单独配一个 jail,把高频 404 / 403 的 IP 封掉。先写一个自定义 filter:

# /etc/fail2ban/filter.d/nginx-botsearch.conf
[Definition]
failregex = ^<HOST> -.*"(GET|POST).*(\.env|/wp-admin|/wp-login|/phpmyadmin|/admin).*" (404|403)
ignoreregex =

再为它开一个 jail,监控 Nginx 的访问日志:

# /etc/fail2ban/jail.d/nginx.local
[nginx-botsearch]
enabled  = true
port     = http,https
filter   = nginx-botsearch
logpath  = /var/log/nginx/access.log
maxretry = 10
bantime  = 12h
findtime = 10m

如果你是配合 Nginx 负载均衡 在 multiple upstream 后面跑业务,记得每个节点都部署同样的 Fail2ban 规则,或者把访问日志集中后再统一判断,避免攻击者在节点之间轮换绕过。

五、日常运维与排查

fail2ban-client 是与运行中的 Fail2ban 交互的命令行工具,掌握下面几条就能应付 90% 的运维场景:

# 查看所有已启用的 jail
sudo fail2ban-client status

# 查看某个 jail 的封禁详情(被封 IP、总数)
sudo fail2ban-client status sshd

# 手动解封一个 IP(误封自救)
sudo fail2ban-client set sshd unbanip 203.0.113.45

# 手动封禁一个 IP(应急)
sudo fail2ban-client set sshd banip 198.51.100.23

# 重新加载配置
sudo fail2ban-client reload

如果某天你把自己关在门外(比如公司动态 IP 被 ban),最稳妥的”自救”方式是:通过云控制台的串行控制台(VNC / Serial Console)登录,再用上面的 unbanip 解封,而不是反复重试 SSH 把失败次数越刷越高。

六、生产环境注意事项

在容器化和云原生环境里,Fail2ban 的部署有几个要点需要留意:

场景建议做法原因
Docker 容器内的服务Fail2ban 跑在宿主机,监控宿主机的 docker 日志或挂载出来的日志容器网络是 NAT,iptables 在容器内封禁对外部无效
云服务器(阿里云 / 腾讯云)Fail2ban 与云安全组 / 云防火墙配合,关键端口同时做白名单云防火墙优先级更高,能挡掉大流量扫描
Kubernetes 节点用 DaemonSet 在每个节点部署,或直接用 Cilium / eBPF 方案Pod IP 漂移,传统 iptables 封禁难追踪
高并发 Webfilter 正则要精准,避免把正常爬虫 / CDN 回源误判误封会直接影响线上可用性

在 Docker 部署的场景下,最常见的问题是”Fail2ban 装在容器里却封不住攻击”——因为容器用的是 bridge 网络,它加的 iptables 规则只作用在容器命名空间内。正确姿势是把 Fail2ban 放在宿主机,监控宿主机上 /var/lib/docker/containers/*/ 的 JSON 日志,或干脆让业务容器把日志 log-driver 打到宿主机的 syslog。

七、常见坑与排障

现象根因解决办法
改了 SSH 端口但没改 jail 的 portFail2ban 仍监控 22,攻击打在 2222 上不触发jail 里把 port = 2222 写对
status 显示 jail 未启用.local 文件名或路径错,未合并进配置确认放在 /etc/fail2ban/jail.d/ 且以 .local 结尾,再 reload
systemd 后端报 unable to read journalfail2ban 用户无 journal 读取权限把 fail2ban 加入 systemd-journal 组
封禁了 CDN / 正常爬虫filter 正则过宽收紧正则或在 jail 里加 ignoreip 白名单
重启后规则丢失用了非持久化 action 或 iptables 被重置确认 action 用默认的 iptables-multiport,并启用开机自启

最后提醒一句:Fail2ban 是”缓解”而非”根治”。它和 服务器安全加固、密钥登录、最小权限是同一套防御体系的不同层次。把这几层叠加起来,一台暴露在公网的服务器才能既方便自己登录,又让自动化攻击无功而返。若你关注更内核层的可观测性,也可以结合 eBPF 做更细粒度的异常连接追踪。

八、recidive 监狱:专门收拾屡教不改的 IP

普通 jail 在 bantime 到期后会自动解封,但有一类攻击者”解封即再来”,循环刷屏,短期封禁几乎没威慑力。Fail2ban 自带一个名为 recidive 的监狱专门对付这种顽固行为:它监控 Fail2ban 自己的日志,把”被 ban 过多次又反复出现”的 IP 拉黑更长时间,甚至按周计。启用方式很简单,新建一个 jail 即可:

# /etc/fail2ban/jail.d/recidive.local
[recidive]
enabled  = true
logpath  = /var/log/fail2ban.log
bantime  = 1w
findtime = 1d
maxretry = 5
backend  = auto

与此同时,务必在全局 [DEFAULT] 段加 ignoreip,把你自己的办公出口 IP、跳板机、内网段全部加白,避免误封后把自己锁在门外——这是生产环境踩过最多的坑之一:

# /etc/fail2ban/jail.d/default.local
[DEFAULT]
ignoreip = 127.0.0.1/8 10.0.0.0/8 203.0.113.0/24
bantime  = 1h
maxretry = 5

九、封禁后发通知:把攻击事件接进监控

自动封禁只是第一步,你还需要知道”此刻谁在打我”。Fail2ban 的 action 机制允许在 ban / unban 时触发自定义命令,最常见的做法是在默认动作之后追加一段 curl,把事件打到飞书、钉钉或 Slack 的 Webhook。示例如下:

# 在 jail 的 action 末尾追加通知
action = %(action_)s
         %(action_mwl)s
         curl -s -X POST "https://oapi.dingtalk.com/robot/send?access_token=XXX" \
           -H 'Content-Type: application/json' \
           -d '{"msgtype":"text","text":{"content":"Fail2ban banned  on "}}'

把封禁事件接进你的告警链路后,再配合 Prometheus 之类的指标采集,就能把”被动封禁”升级为”主动感知”——当某个时间段 ban 数量陡增时,你能第一时间收到通知,在攻击规模化之前介入。至此,从 SSH 到 Web、从单机到集群、从封禁到告警,一套可落地的 Fail2ban 入侵防御体系就搭建完成了。

上一篇 Java Flight Recorder 实战:用 JFR 飞行记录仪定位性能问题
下一篇 direnv 实战:进入目录自动加载项目环境变量
最新评论
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 […]