Contents

Fail2ban 生产级部署完全指南:Debian 13 实战

前置阅读
本文是 Debian 13 新服务器初始化完全指南 的后续篇。如果你还没完成 SSH 端口修改和密钥认证,建议先做初始化再回来配置 fail2ban——SSH 爆破日志里全是 22 端口密码试错,你改了端口后 ban 到的才是真正有价值的威胁 IP。

问题:你的服务器每秒钟在被谁敲门

SSH 日志里最常见的画面:

Jul 14 03:12:42 vps sshd[12983]: Failed password for root from 45.33.32.156 port 44122 ssh2
Jul 14 03:12:44 vps sshd[12983]: Failed password for root from 45.33.32.156 port 44122 ssh2
Jul 14 03:12:46 vps sshd[12983]: Failed password for root from 45.33.32.156 port 44122 ssh2
Jul 14 03:12:48 vps sshd[12983]: Failed password for admin from 45.33.32.156 port 44122 ssh2
...

一个僵尸网络节点,2 秒一次,24 小时不间断。粗略估算:每小时 1,800 次尝试,每天 43,200 次。

我在 2018 年管理过一台暴露 22 端口的测试机,日均被尝试登录 8 万次以上。从那以后,防爆破就成了我初始化服务器的标准步骤。

fail2ban 做的事很简单:盯日志 → 匹配失败模式 → 拉黑 IP。它不是什么重量级安全产品,但就是这「盯、匹配、拉黑」三步,能把 99% 的自动化攻击挡在门外。


1. 安装 fail2ban

apt update
apt install fail2ban -y

安装完成后,fail2ban 会自动启动。验证:

systemctl status fail2ban

预期输出包含 active (running)


2. 核心架构:3 层文件体系

理解 fail2ban 的文件结构,是生产级配置的前提。很多新手上来直接改 .conf,下次升级包更新后配置就丢了。

文件/目录 角色 操作规则
/etc/fail2ban/jail.conf 官方默认配置文件 永远不要直接修改。包更新时会覆盖
/etc/fail2ban/jail.d/defaults-debian.conf Debian 发行版特定默认值 也不要直接修改
/etc/fail2ban/jail.local 你的自定義配置(优先级最高) 在这里写你的 jail 配置
/etc/fail2ban/jail.d/*.local 分模块配置文件 按服务拆分(如 sshd.localnginx.local
/etc/fail2ban/filter.d/ 日志匹配规则(filter) 官方自带的放在这,自定义的也放这
/etc/fail2ban/action.d/ 封禁动作定义(action) 定义怎么 ban(iptables/nftables/Cloudflare)
/var/log/fail2ban.log fail2ban 主日志 所有 jail 的启停、封禁/解禁记录都在这里

配置优先级jail.local > jail.d/*.local > jail.d/*.conf > jail.conf


3. 生产级 jail.local 配置

下面是我在 200+ 台服务器上验证过的 jail.local 模板。每个参数的含义在表里详细说明。

cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
# —————— 白名单(永不封禁)——————
# 你自己的 IP、公司 VPN IP、监控服务器 IP
ignoreip = 127.0.0.1/8 ::1

# —————— 封禁策略(全局默认)——————
# 封禁时长(秒):86400 = 24 小时
bantime = 86400

# 时间窗口(秒):在此窗口内达到 maxretry 的失败次数即封禁
findtime = 600

# 最大重试次数
maxretry = 5

# —————— 防火墙后端 ——————
# Debian 13 默认使用 nftables,这里明确指定
banaction = nftables-multiport
banaction_allports = nftables-allports

# —————— 日志后端 ——————
# systemd-journal 比直接读文件更可靠,且不用处理日志轮转
backend = systemd

# —————— 通知(可选)——————
# 有条件的可以配置邮件通知
# destemail = your@email.com
# sendername = Fail2Ban
# mta = sendmail
# action = %(action_mw)s

[sshd]
enabled = true
port = 9527
filter = sshd
logpath = %(sshd_log)s
maxretry = 3
bantime = 86400
findtime = 600

[sshd-ddos]
enabled = true
port = 9527
filter = sshd-ddos
logpath = %(sshd_log)s
maxretry = 3
bantime = 604800
findtime = 300
EOF

3.1 关键参数解读

参数 含义 生产级建议值 原因
bantime 封禁时长(秒) 86400(24h) SSH 爆破 IP 通常是短期租用的僵尸节点,24h 后大概率已释放。如果是长期恶意 IP,用 recidive jail 处理
findtime 统计窗口(秒) 600(10 min) 窗口太短容易漏,太长误杀概率上升。10 分钟是均衡点
maxretry 失败次数阈值 3(SSH)/ 5(通用) 配合 ed25519 密钥认证,正常用户不会失败。设为 3 足够安全
ignoreip 白名单 你自己 + 跳板机 至少放入你自己的出口 IP,避免密码输错把自己关外面
banaction 封禁方式 nftables-multiport Debian 13 默认防火墙是 nftables。不要用 iptables-multiport

3.2 sshd vs sshd-ddos:两个 jail 的区别

Jail filter 文件 匹配模式 建议 bantime 适用场景
sshd filter.d/sshd.conf 单次连接中的失败尝试 24 小时 普通暴力破解
sshd-ddos filter.d/sshd-ddos.conf 短时间内大量连接尝试(DDoS 式扫描) 7 天 僵尸网络大规模扫描

sshd-ddos 的 filter 匹配的是 SSH 连接层面的异常——比如 1 秒内从同一个 IP 来了几十个连接,这是典型的僵尸网络行为,和普通密码爆破性质不同,应该给予更长的封禁。


4. 防火墙后端:nftables 集成

Debian 13 已经用 nftables 取代了 iptables(虽然 iptables 的兼容层还在)。fail2ban 默认检测到 nftables 会自动使用它。

验证 fail2ban 的 nftables 规则是否生效:

# 查看 fail2ban 创建的 nftables 表
nft list ruleset | grep -A 10 fail2ban

# 或者用 fail2ban-client 查看
fail2ban-client status sshd

预期输出示例:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

5. 日常运维命令速查表

以下是我日常最常用的 fail2ban 命令,每个命令有明确的「什么时候用」。

场景 命令 说明
查看所有 jail 状态 fail2ban-client status 列出所有活跃的 jail
查看某个 jail 详情 fail2ban-client status sshd 显示封禁数、当前被封 IP 列表
手动封禁一个 IP fail2ban-client set sshd banip 1.2.3.4 立即封禁,不受 maxretry 限制
手动解封一个 IP fail2ban-client set sshd unbanip 1.2.3.4 立即解封(比如误封了自己)
查看封禁日志 tail -f /var/log/fail2ban.log 实时看封禁/解禁事件
查看 SSH 被尝试次数 grep "Failed password" /var/log/auth.log | wc -l 看看攻击量
查看被封 IP 排名 awk '/Ban/ {print $NF}' /var/log/fail2ban.log | sort | uniq -c | sort -rn | head -20 哪些 IP 最"勤奋"
查看解禁事件 grep "Unban" /var/log/fail2ban.log 看哪些 IP 刚被释放
检查配置语法 fail2ban-client -d 输出完整配置(包括从 .conf 继承的),用于排查
重载配置(不中断服务) systemctl reload fail2ban 改了配置后重新加载,不中断当前封禁
查看某 IP 是否被封 fail2ban-client status sshd | grep "1.2.3.4" 快速检查

6. 告警与通知:别让封禁成为黑箱

生产环境应该知道「什么时候、谁被谁封了」。fail2ban 支持邮件通知。

6.1 配置邮件通知

# 安装邮件发送工具
apt install sendmail -y

jail.local[DEFAULT] 段添加:

destemail = your-email@example.com
sendername = Fail2Ban
mta = sendmail
# action_mw:封禁时发邮件 + whois 报告
action = %(action_mw)s

6.2 通知类型对比

action 类型 触发时机 内容
%(action_)s 封禁时 仅执行防火墙规则,不通知
%(action_mw)s 封禁时 发邮件 + 附 whois 查询结果
%(action_mwl)s 封禁时 发邮件 + whois + 触发日志行
%(action_cf_mwl)s 封禁时 同上,但封禁通过 Cloudflare API(适合 CDN 后场景)

7. 自定义 Filter:以 Nginx 401 攻击为例

7.1 问题场景

跑了个带 Basic Auth 的 Nginx 站点,攻击者用字典爆破用户名密码。默认 fail2ban 只有 nginx-http-auth filter,但你想同时封禁那些尝试访问不存在路径的扫描器。

7.2 写一个自定义 filter

cat > /etc/fail2ban/filter.d/nginx-404.local << 'EOF'
[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*HTTP/.*" 404 [0-9]+.*$
ignoreregex =
EOF

正则逐段解读

部分 含义
^<HOST> 匹配 IP 地址(fail2ban 预定义宏,自动匹配 IPv4/IPv6)
-.*" 匹配 Nginx 日志格式中的连字符和双引号部分
(GET|POST|HEAD) 匹配 HTTP 方法
.*HTTP/.*" 匹配协议部分
404 返回码 404
[0-9]+.*$ 响应长度和后续内容

7.3 在 jail.local 中启用

[nginx-404]
enabled = true
port = http,https
filter = nginx-404
logpath = /var/log/nginx/access.log
maxretry = 30
bantime = 3600
findtime = 300
maxretry 怎么定
Nginx 404 是一个很弱的信号——正常访问者也会点错链接。所以 maxretry = 30 设得比较高,只封禁那些明显是在扫描目录结构的自动化工具。生产环境调试 filter 时,先用高 maxretry,观察 24 小时误封率再逐步收紧。

8. 测试 filter 正则表达式

写完 filter 后,用 fail2ban-regex 验证正则是否匹配正确的行:

# 语法检查:用一段样例日志测试
fail2ban-regex \
  '192.168.1.1 - - [14/Jul/2026:12:00:00 +0000] "GET /wp-admin HTTP/1.1" 404 162 "-" "Mozilla/5.0"' \
  '^<HOST> -.*"(GET|POST|HEAD).*HTTP/.*" 404 [0-9]+.*$'

输出 Lines: 1 lines, 0 ignored, 1 matched, 0 missed 就是正确的。

用实际日志文件测试:

fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-404.local

9. recidive jail:对付惯犯

有些 IP 被封了 24 小时,解封后继续来。recidive jail 专门对付这种「刚放出来又犯事」的 IP:

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = nftables-allports
bantime = 2592000
findtime = 86400
maxretry = 3
  • logpath 指向 fail2ban 自己的日志
  • 一个 IP 在 24 小时内(findtime)被其他 jail 封禁 3 次(maxretry)→ 封禁 30 天(bantime = 2592000
  • nftables-allports:全部端口封禁,不只是 SSH

10. 生产环境日志分析

10.1 日志位置

日志文件 内容
/var/log/fail2ban.log fail2ban 主日志:启停、封禁、解禁
/var/log/auth.log SSH 认证日志(fail2ban sshd jail 监听这个)
/var/log/nginx/access.log Nginx 访问日志

10.2 常用分析命令

# 查看今天被封了多少 IP
grep "$(date +%Y-%m-%d)" /var/log/fail2ban.log | grep "Ban" | wc -l

# 查看过去 24 小时的封禁趋势(每小时统计)
grep "Ban" /var/log/fail2ban.log | awk '{print $1, $2}' | cut -d':' -f1 | sort | uniq -c

# 找出被 ban 次数最多的 IP(惯犯)
grep "Ban" /var/log/fail2ban.log | awk '{print $NF}' | sort | uniq -c | sort -rn | head -20

# 查看当前哪些 IP 被 SSH jail 封禁
fail2ban-client status sshd | grep "Banned IP list"

# 查看 fail2ban 整体状态(所有 jail 的封禁总数)
fail2ban-client status

# 检查 fail2ban 自身是否有错误
journalctl -u fail2ban --since "1 hour ago" | grep -i "error\|fail"

11. 新增 jail 的标准流程

每当我需要为一个新服务加上 fail2ban 保护,按这个流程走:

流程图解读

  1. 确定路径:先用 tail -f 看服务的日志格式,找到认证失败那一行长什么样
  2. 写 filter:把失败行复制出来,提取正则,存到 filter.d/
  3. 验证:必做步骤。fail2ban-regex 不过的 filter,fail2ban 不会报错只会静默不工作
  4. 写 jail:在 jail.local 里新增一段,指向你的 filter + logpath
  5. reloadsystemctl reload fail2ban,不会中断现有封禁
  6. 观察:至少 24 小时。grep "Ban" /var/log/fail2ban.log 看封禁列表里有没有可疑 IP(比如 CDN 节点、搜索引擎爬虫)
  7. 调优:发现有误封 → 加 ignoreregex 排除,或者调高 maxretry

12. 完整配置示例

以下是 jail.local 的完整生产级配置,包含 SSH 防护、recidive 惯犯处理、可选 Nginx 404 扫描防护:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
bantime = 86400
findtime = 600
maxretry = 5
banaction = nftables-multiport
banaction_allports = nftables-allports
backend = systemd

[sshd]
enabled = true
port = 9527
filter = sshd
logpath = %(sshd_log)s
maxretry = 3
bantime = 86400
findtime = 600

[sshd-ddos]
enabled = true
port = 9527
filter = sshd-ddos
logpath = %(sshd_log)s
maxretry = 3
bantime = 604800
findtime = 300

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = nftables-allports
bantime = 2592000
findtime = 86400
maxretry = 3

配置写好之后,重载生效:

systemctl reload fail2ban
fail2ban-client status

13. 常见问题排查

13.1 「我改了配置但没生效」

99% 的情况是没有 reload:

systemctl reload fail2ban

不要用 restart——restart 会清空当前所有封禁列表,之前被封的 IP 会被放出来。

13.2 「fail2ban 启动失败,日志里没有错误」

检查配置文件语法:

fail2ban-client -d 2>&1 | head -50

-d 输出完整的最终配置,如果看到某行明显不对(比如端口写了字符串),就是配置语法错误。

13.3 「IP 被封了但我还能登录」

确认你是用密钥登录的——fail2ban 的 sshd filter 默认匹配的是密码失败,密钥认证失败走的是另一条日志。如果攻击者用密钥试错(虽然几乎没有私钥还试什么……),需要补充一个 filter。

13.4 「我的 ip 被误封了,怎么赶紧解封」

fail2ban-client set sshd unbanip <你的IP>

或者如果连 SSH 都连不上,通过 VNC/控制台登录后执行。

13.5 「fail2ban 和 nftables 的关系是什么」

fail2ban 不替换 nftables,而是在 nftables 规则中动态插入封禁规则。你可以同时用 firewalld(它也基于 nftables),两者不冲突。

# 查看 fail2ban 在 nftables 中的痕迹
nft list table inet f2b-table

延伸阅读


关于作者
我是 VPSDeck,十年 DevOps 和系统运维工程师。从物理服务器到 Kubernetes,从单机建站到跨洲容灾——每个阶段的安全问题我都踩过坑,fail2ban 是我部署过的成本最低、收益最高的安全工具。希望这篇文档能帮你在生产环境少踩几个雷。