Fail2ban 生产级部署完全指南:Debian 13 实战
问题:你的服务器每秒钟在被谁敲门
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.local、nginx.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
EOF3.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)s6.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 = 300maxretry = 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.local9. recidive jail:对付惯犯
有些 IP 被封了 24 小时,解封后继续来。recidive jail 专门对付这种「刚放出来又犯事」的 IP:
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = nftables-allports
bantime = 2592000
findtime = 86400
maxretry = 3logpath指向 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 保护,按这个流程走:
流程图解读:
- 确定路径:先用
tail -f看服务的日志格式,找到认证失败那一行长什么样 - 写 filter:把失败行复制出来,提取正则,存到
filter.d/ - 验证:必做步骤。
fail2ban-regex不过的 filter,fail2ban 不会报错只会静默不工作 - 写 jail:在
jail.local里新增一段,指向你的 filter + logpath - reload:
systemctl reload fail2ban,不会中断现有封禁 - 观察:至少 24 小时。
grep "Ban" /var/log/fail2ban.log看封禁列表里有没有可疑 IP(比如 CDN 节点、搜索引擎爬虫) - 调优:发现有误封 → 加
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 status13. 常见问题排查
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延伸阅读
- 🔧 Debian 13 新服务器初始化完全指南 — 从系统更新到 SSH 加固,fail2ban 的前置步骤
- 🔥 Firewalld 端口转发教程 — 管理 nftables 防火墙规则
- 🖥️ RustDesk Server 自建教程 — 自建远程桌面 + fail2ban 保护