Fail2ban防暴力破解配置教程

描述

 

Fail2ban 防暴力破解配置教程

一、问题背景

生产环境里 SSH、FTP、网站登录、邮件服务被人扫是常态。扫的形态一般是:

  • 凌晨 3 点 SSH 被人用 root 弱口令试 1 万次
  • WordPress xmlrpc.php 被扫 8 万次/小时
  • 某业务登录接口被 6 万 IP 撞
  • SMTP 被人拿去发垃圾邮件
  • FTP 弱口令被人上传 WebShell

最直接的应对:

  • 关弱协议(telnet、FTP 明文)
  • 用强密码 + 公私钥
  • 改端口
  • WAF 拦
  • 安全组封 IP

但所有这些加起来,仍需要一道“最后一道门”——Fail2ban。它干的事情很简单:扫描日志,发现异常就封禁 IP 一段时间。安装、配置好之后就能让 SSH 撞库、WordPress xmlrpc 撞库、FTP 弱口令、HTTP 401 风暴这些事直接消失。

这一篇讲:

  • Fail2ban 原理与安装
  • 核心配置 jail / filter / action
  • 实战配置 SSH、SSH 慢速 DDoS、nginx-http-auth、nginx-bad-request、nginx-botsearch、apache-badbot、vsftpd、postfix
  • 自定义 filter
  • 自定义 action(邮件、Slack、钉钉)
  • 与 firewalld / nftables / iptables / ufw 的协作
  • 数据库、unban、白名单
  • 排障与回滚
  • 监控告警

二、适用场景

  • SSH 22 端口防爆破
  • SSH 慢速 DDoS 防护
  • FTP / SFTP / vsftpd 弱口令防护
  • SMTP / Postfix / Dovecot 认证失败防护
  • nginx / apache / lighttpd 401、403、404 风暴防护
  • WordPress xmlrpc.php、wp-login.php 撞库防护
  • API 网关层 401 撞库防护
  • 自定义业务系统登录失败防护
  • 多服务器集中管理(与 fail2ban-client 远程)
  • 邮件 / Slack / 钉钉告警
  • 与云厂商安全组协同封禁
  • 与 WAF 协同
  • 与 Prometheus 监控集成

三、核心知识点

3.1 Fail2ban 原理

Fail2ban 本质是一个日志扫描 + 防火墙触发的守护进程:

  1. 启动时读取 /etc/fail2ban/jail.conf 与 /etc/fail2ban/jail.local
  2. 每个 jail 启动一个后台线程
  3. 后台线程使用 pyinotify(Linux inotify)监听日志文件变更
  4. 文件变更时读取新内容
  5. 用 filter 中定义的正则匹配异常
  6. 累计 maxretry 次后触发 action
  7. action 调用防火墙封禁 IP bantime 时长
  8. 封禁到期后调用 unban action 解封

关键依赖:

  • Python 3.7+(Fail2ban 1.0+)
  • pyinotify (Linux 上监控文件)
  • 防火墙后端(iptables、nftables、firewalld、ufw)
  • 日志后端(systemd、file、syslog)

3.2 核心组件

  • fail2ban-server :守护进程
  • fail2ban-client :命令行客户端
  • fail2ban-regex :测试正则
  • /etc/fail2ban/jail.conf :默认配置(升级时会被覆盖)
  • /etc/fail2ban/jail.local :用户配置(优先级最高)
  • /etc/fail2ban/filter.d/*.conf :filter 定义
  • /etc/fail2ban/action.d/*.conf :action 定义
  • /var/lib/fail2ban/fail2ban.sqlite3 :数据库(封禁历史)
  • /var/log/fail2ban.log :fail2ban 自身日志
  • /var/run/fail2ban/ :socket、PID

3.3 jail 配置结构


			   ini[DEFAULT] backend = polling bantime = 10m findtime = 10m maxretry = 5 ignoreip = 127.0.0.1/8 [sshd] enabled = true port = ssh filter = sshd logpath = /var/log/secure 

参数:

  • enabled :是否启用
  • port :监听端口
  • filter :filter 名(不带 .conf)
  • logpath :日志路径
  • bantime :封禁时长
  • findtime :时间窗口
  • maxretry :最大失败次数
  • ignoreip :白名单
  • backend :polling、pyinotify、systemd
  • action :要执行的 action
  • usedns :是否解析 hostname
  • findtimeregex datepattern:自定义时间戳正则

3.4 filter 配置

/etc/fail2ban/filter.d/sshd.conf


			   ini[INCLUDES] before = common.conf [Definition] _daemon = sshd failregex = ^%(__prefix_line)s(? PAM: )?[aA]uthentication (?:failure|error|failed) for .* from (?:s+port d*)?(?: sshd*)?$             ^%(__prefix_line)s(? )?Received disconnect from (?::|;)?s+(?:[a-z]+s+shutdown|reset|disconnect|connection closed)?s*$             ^%(__prefix_line)sUser .+ from  not allowed because not listed in AllowUsers$             ^%(__prefix_line)sUser .+ from  not allowed because none of user's groups are listed in AllowGroups$             ^%(__prefix_line)s(? )?refused connect from S+ ()$             ^%(__prefix_line)sInvalid user .* from $             ^%(__prefix_line)sFailed S+ for .* from (?:s+port d*)?s+$             ^%(__prefix_line)s(? )?maximum authentication attempts exceeded for .* from (?:s+port d*)?s+.*$ ignoreregex = 

关键点:

  •  是占位符,会被替换为 (?:::f{4,6}:)?(?P[w-.^_]*w)
  • __prefix_line  是时间戳与进程名前缀
  • 多条 failregex 用缩进写
  • 调试时用 fail2ban-regex 测试

3.5 action 配置

/etc/fail2ban/action.d/iptables-multiport.conf 关键内容:


			   ini[Definition] actionstart = iptables -N f2b-               iptables -A f2b- -j RETURN               iptables -I  -p  -m multiport --dports  -j f2b- actionstop = iptables -D  -p  -m multiport --dports  -j f2b-              iptables -F f2b-              iptables -X f2b- actioncheck = iptables -n -L  | grep -q 'f2b-[ ]' actionban = iptables -I f2b- 1 -s  -j DROP actionunban = iptables -D f2b- -s  -j DROP 

action 类型:

  • iptables-multiport :iptables 传统封禁
  • iptables-allports :所有端口
  • nftables-multiport :nftables
  • firewalld :firewalld
  • ufw :ufw
  • route :blackhole 路由
  • cloudflare :调用 Cloudflare API
  • slack-notify :发 Slack 消息
  • sendmail-* :发邮件
  • dshield :上报 DShield
  • shorewall :shorewall 防火墙

3.6 与 firewalld 配合

firewalld 后端有专属 action:


			   ini[DEFAULT] banaction = firewallcmd-rich-rules banaction_allports = firewallcmd-rich-rules chain = INPUT 

banaction 包括:

  • firewallcmd-ipset
  • firewallcmd-rich-rules
  • firewallcmd-new (已弃用)

注意:firewalld 后端不会立即封禁,要等 firewalld 重载;但 rich-rules 是热生效的。

3.7 与 nftables 配合


			   ini[DEFAULT] banaction = nftables-multiport chain = input 

/etc/nftables.conf


			   nfttable inet filter {     chain input {         type filter hook input priority 0; policy accept;         ct state established,related accept         iif lo accept         tcp dport { 22, 80, 443 } accept         jump f2b-input     }     chain f2b-input {         return     } } 

注意:Fail2ban 的 nftables action 假设 nftables 中已存在同名 chain。

3.8 数据库结构

/var/lib/fail2ban/fail2ban.sqlite3 关键表:

  • bans :当前封禁列表
  • jails :jail 状态
  • logs :日志(可选)

查看数据库:


			   bashapt install -y sqlite3 sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 sqlite> .schema sqlite> SELECT * FROM bans; sqlite> .headers on sqlite> SELECT jail, ip, timeofban FROM bans; 

3.9 findtime、maxretry、bantime 配合

举例:

  • findtime=10m maxretry=5bantime=1h:10 分钟内 5 次失败,封 1 小时
  • findtime=24h maxretry=20bantime=24h:24 小时内 20 次失败,封 24 小时
  • findtime=1h maxretry=10bantime=10m:1 小时内 10 次失败,封 10 分钟

实战经验:

  • SSH:maxretry=5,bantime=1h 起步
  • WordPress:maxretry=3,bantime=24h
  • FTP:maxretry=3,bantime=24h
  • 撞库:maxretry=10,bantime=24h
  • 慢速 DDoS:用 recidive jail(重复犯案)

3.10 recidive 监狱

recidive 是“惯犯”监狱:把其他 jail 的 ban 行为记录进 recidive 的 logpath,多次被 ban 的人会进 recidive jail 长时间封禁。


			   ini[recidive] enabled = true filter = recidive logpath = /var/log/fail2ban.log bantime = 1w findtime = 1d maxretry = 5 action = iptables-multiport[name=recidive] 

四、整体排查或实施思路

4.1 部署加固主线

  • 目标:SSH、Web、FTP、SMTP 都被防爆破
  • 环境假设:Linux 系统(CentOS / Ubuntu / Debian),有 root 权限
  • 配置前检查:日志路径、防火墙类型、Python 版本
  • 实施步骤:安装、配置 jail.local、配置 filter、配置 action
  • 配置说明:每段 jail 的参数意义
  • 生效方式:systemctl enable --now
  • 验证方法:ssh 错几次密码看是否被 ban
  • 风险点:误封、影响业务、占用 CPU
  • 回滚方案:禁用 jail、删除 banaction、unban IP
  • 维护建议:定期检查、logrotate、监控

4.2 故障排查主线

  • 现象:Fail2ban 没起作用 / 误封 / 启动失败 / 日志报错
  • 初步判断:版本、Python、日志路径、权限、防火墙
  • 命令检查:journalctl、fail2ban-client status、iptables -L
  • 关键指标:ban 数、unban 数、内存、CPU
  • 根因定位:日志路径不对、权限不够、firewallcmd 冲突
  • 修复方案:改配置、装依赖、关掉其他防火墙
  • 验证结果:模拟异常,看是否 ban
  • 回滚预案:unban、关 jail
  • 复盘总结:写文档

五、实战步骤

5.1 安装

5.1.1 Debian / Ubuntu


			   bashapt update apt install -y fail2ban systemctl enable fail2ban systemctl start fail2ban systemctl status fail2ban 

5.1.2 CentOS / RHEL


			   bashyum install -y epel-release yum install -y fail2ban fail2ban-systemd systemctl enable fail2ban systemctl start fail2ban systemctl status fail2ban 

5.1.3 源码


			   bashgit clone https://github.com/fail2ban/fail2ban.git cd fail2ban python3 -m pip install . cp files/redhat-initd /etc/init.d/fail2ban systemctl daemon-reload systemctl enable fail2ban systemctl start fail2ban 

5.1.4 容器化


			   bashdocker run -d --name fail2ban      --net=host      --cap-add=NET_ADMIN      --cap-add=NET_RAW      -v /var/log:/var/log:ro      -v /etc/fail2ban:/etc/fail2ban      -v /var/lib/fail2ban:/var/lib/fail2ban      crazymax/fail2ban:latest 

5.2 验证安装


			   bash# 版本 fail2ban-server --version fail2ban-client version # 状态 fail2ban-client status # 启动的 jail fail2ban-client status # Number of jail: 0  (刚安装时为空,需要启用) # 日志 tail -f /var/log/fail2ban.log 

预期输出:版本 1.0.x,启动正常。

异常表现:fail2ban-client 报 connection refused,说明 fail2ban-server 未启动。

判断逻辑:检查 systemd 状态、socket 路径(/var/run/fail2ban/fail2ban.sock)。

下一步动作:systemctl start fail2ban 或 systemctl restart fail2ban

5.3 jail.local 基础配置

/etc/fail2ban/jail.local


			   ini[DEFAULT] # 白名单 ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12 # 封禁时长 bantime = 1h # 时间窗口 findtime = 10m # 最大失败次数 maxretry = 5 # 后端 backend = systemd # action banaction = iptables-multiport banaction_allports = iptables-allports chain = INPUT # 是否解析 hostname usedns = warn # 日志级别 loglevel = INFO # 目的地(邮件) destemail = ops@example.com sender = fail2ban@example.com mta = sendmail # 收件人 # mta = smtp # smtphost = smtp.example.com # smtpport = 587 # smtpuser = alerts@example.com # smtppass = YOUR_PASSWORD # smtpfrom = fail2ban@example.com # smtpto = ops@example.com # smtptls = starttls # 邮件 action action = %(action_mwl)s 

参数解释:

  • ignoreip :白名单,必须包含公司 IP、VPN 段、家庭 IP
  • bantime = -1 :永久封禁(慎用)
  • usedns = no :关闭 DNS 解析,避免 IP 反向解析慢导致 fail2ban 卡住
  • action_mwl :发邮件 + log + whois

5.4 启用 SSH jail

/etc/fail2ban/jail.local 中追加:


			   ini[sshd] enabled = true port = ssh filter = sshd logpath = %(sshd_log)s backend = %(sshd_backend)s maxretry = 5 findtime = 10m bantime = 1h 

%(sshd_log)s 在 Debian 是 /var/log/auth.log,在 RHEL 是 /var/log/secure

注意:

  • CentOS 7 用 journalctl 的 systemd 后端,需要 backend = systemd
  • CentOS 6 没有 systemd,用 polling

5.5 验证 SSH jail


			   bash# 启动 systemctl restart fail2ban # 看状态 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: # 模拟失败 ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no -o PreferredAuthentications=password wronguser@127.0.0.1 # 故意输错 6 次密码 # 看是否被 ban fail2ban-client status sshd # Banned IP list: 127.0.0.1 # 看 iptables iptables -L -n | grep f2b # 解封 fail2ban-client set sshd unbanip 127.0.0.1 

5.6 启用 nginx-http-auth


			   ini[nginx-http-auth] enabled = true filter = nginx-http-auth port = http,https logpath = /var/log/nginx/error.log maxretry = 5 findtime = 10m bantime = 1h 

/etc/fail2ban/filter.d/nginx-http-auth.conf(系统自带):


			   ini[Definition] failregex = ^ [error] d+#d+: *d+ user "([^"]+):"? client: , server: S+, request: "S+ S+ HTTP/d+.d+", host: "S+"(?:, referrer: "S+")?s*$ ignoreregex = 

验证:访问一个有 basic auth 的 URL,输错 6 次,看是否被 ban。

5.7 启用 nginx-bad-request


			   ini[nginx-bad-request] enabled = true filter = nginx-bad-request port = http,https logpath = /var/log/nginx/access.log maxretry = 10 findtime = 10m bantime = 1h 

/etc/fail2ban/filter.d/nginx-bad-request.conf


			   ini[Definition] failregex = ^ -[^"]* "[A-Z]+ /S+ HTTP/d+.d+" (4(04|03|44|13|14|51|93|95)) d+ .*$ ignoreregex = ^ -[^"]* "[A-Z]+ /S+ HTTP/d+.d+" 200 d+ .*$ 

5.8 启用 nginx-botsearch


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

/etc/fail2ban/filter.d/nginx-botsearch.conf(系统自带)覆盖典型扫描路径:

  • /admin/
  • /wp-admin/
  • /xmlrpc.php
  • /phpmyadmin/
  • /.env
  • /.git/
  • /backup/
  • /.svn/

5.9 启用 sshd-ddos 慢速 SSH DDoS


			   ini[sshd-ddos] enabled = true filter = sshd-ddos port = ssh logpath = %(sshd_log)s backend = %(sshd_backend)s maxretry = 10 findtime = 1h bantime = 24h 

/etc/fail2ban/filter.d/sshd-ddos.conf(系统自带)匹配大量连接尝试。

5.10 启用 vsftpd


			   ini[vsftpd] enabled = true filter = vsftpd port = ftp,ftp-data,ftps,ftps-data logpath = /var/log/vsftpd.log maxretry = 3 findtime = 10m bantime = 24h 

5.11 启用 postfix / dovecot


			   ini[postfix] enabled = true filter = postfix port = smtp,465,submission logpath = /var/log/mail.log maxretry = 5 findtime = 10m bantime = 1h [dovecot] enabled = true filter = dovecot port = pop3,pop3s,imap,imaps,submission logpath = /var/log/mail.log maxretry = 5 findtime = 10m bantime = 1h 

5.12 自定义 filter:WordPress xmlrpc 撞库

/etc/fail2ban/filter.d/wordpress-xmlrpc.conf


			   ini[Definition] failregex = ^ .* "POST /xmlrpc.php HTTP/d+.d+" d+ .*$             ^ .* "POST /wp-login.php HTTP/d+.d+" d+ .*$ ignoreregex = ^ .* "(GET|HEAD) .*$ 

/etc/fail2ban/jail.local


			   ini[wordpress-xmlrpc] enabled = true filter = wordpress-xmlrpc port = http,https logpath = /var/log/nginx/access.log maxretry = 5 findtime = 5m bantime = 24h 

5.13 自定义 action:钉钉 Webhook

/etc/fail2ban/action.d/dingtalk-webhook.conf


			   ini[Definition] actionstart = actionstop = actioncheck = actionban = curl -s -X POST ""                -H "Content-Type: application/json"                -d '{"msgtype":"text","text":{"content":"[Fail2ban] 封禁 IP  在 jail ,剩余时间  秒"}}' actionunban = curl -s -X POST ""                -H "Content-Type: application/json"                -d '{"msgtype":"text","text":{"content":"[Fail2ban] 解封 IP  在 jail "}}' [Init] webhook_url = https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN 

jail 中引用:


			   ini[wordpress-xmlrpc] action = dingtalk-webhook[name=wordpress-xmlrpc] 

5.14 自定义 action:Slack Webhook

/etc/fail2ban/action.d/slack-webhook.conf


			   ini[Definition] actionban = curl -s -X POST ""                -H "Content-Type: application/json"                -d '{"text":"[Fail2ban] Banned IP  in jail "}' actionunban = curl -s -X POST ""                -H "Content-Type: application/json"                -d '{"text":"[Fail2ban] Unbanned IP  in jail "}' [Init] webhook_url = https://hooks.slack.com/services/YOUR/WEBHOOK 

5.15 与云厂商安全组协同

5.15.1 AWS


			   bashpip install boto3 

/etc/fail2ban/action.d/aws-waf.conf


			   ini[Definition] actionban = aws wafv2 update-ip-set --name  --scope REGIONAL --id  --addresses /32 --change-token  actionunban = ... 

更简单的方案:使用 iptables 封禁 1 小时后,云厂商安全组在大网段(AS)层做兜底。

5.15.2 Cloudflare


			   bashpip install cloudflare 

/etc/fail2ban/action.d/cloudflare-ban.conf


			   ini[Definition] actionban = python3 /etc/fail2ban/cloudflare-ban.py ban  actionunban = python3 /etc/fail2ban/cloudflare-ban.py unban  

/etc/fail2ban/cloudflare-ban.py


			   python#!/usr/bin/env python3 import sys import CloudFlare import os def main():     cf = CloudFlare.CloudFlare(token=os.environ['CF_API_TOKEN'])     zone_id = os.environ['CF_ZONE_ID']     ip = sys.argv[2]     if sys.argv[1] == 'ban':         cf.zones.firewall.access_rules.rules.post(             zone_id,             data={'mode''block''configuration': {'target''ip''value': ip}, 'notes''fail2ban'}         )     else:         # 查找并删除         rules = cf.zones.firewall.access_rules.rules.get(zone_id)         for rule in rules:             if rule['configuration']['value'] == ip:                 cf.zones.firewall.access_rules.rules.delete(zone_id, rule['id']) if __name__ == '__main__':     main() 

风险点:脚本中明文存储 token 必须改为环境变量或受保护配置文件。

5.16 操作失败时的保护

5.16.1 防止误封


			   ini[DEFAULT] # 加 IP 白名单 ignoreip = 127.0.0.1/8 ::1             10.0.0.0/8             192.168.0.0/16             172.16.0.0/12             # 办公网段            203.0.113.0/24             # 公司 VPN            198.51.100.0/24             # 跳板机            192.0.2.10 

5.16.2 防止 SSH 永久 ban

bantime 不要写 -1,避免被 ban 后只能进机房解封。

5.16.3 高风险时先关

  • 服务器刚上线、白名单未配置时,临时禁掉所有 jail
  • 配置确认后再启用

			   ini[DEFAULT] enabled = false 

5.17 排障:Fail2ban 不生效

现象:多次失败密码,但 IP 没被 ban。

初步判断:filter 不匹配、logpath 错、bantime 没生效、iptables 不存在。

命令检查:


			   bash# 看 fail2ban 日志 tail -f /var/log/fail2ban.log journalctl -u fail2ban -f # 看 jail 状态 fail2ban-client status sshd # 测试 filter fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf # 看 iptables iptables -L -n # 看 fail2ban 是否能写入 iptables iptables -N f2b-test 

关键指标:

  • Currently failed :实时失败计数
  • Total failed :累计失败
  • Banned IP list :被 ban 的 IP
  • File list :监控的日志文件
  • Backend :监控方式

根因定位:

  • File list  列出空:logpath 不对或权限不足
  • Currently failed  不增加:filter 不匹配
  • Banned IP list  空:action 没成功
  • iptables 中没有 f2b-sshd chain:banaction 错误

修复方案:

  • logpath 改对
  • 改 filter
  • 检查 iptables / nftables 兼容性
  • 检查 firewalld 是否启用

验证结果:模拟失败,确认能 ban。

5.18 排障:误封自己

现象:连了几次失败后自己被 ban,无法连回。

解决:


			   bash# 在另一台机器上 unban ssh other-server "fail2ban-client set sshd unbanip YOUR_IP" # 或者从 console 登录(云厂商 VNC) fail2ban-client set sshd unbanip YOUR_IP iptables -D f2b-sshd -s YOUR_IP -j DROP 

预防:

  • 永远先配 ignoreip
  • 把跳板机 IP 加到所有 jail 的 ignoreip
  • 不要在生产环境的 SSH jail 上做测试

5.19 排障:firewalld / nftables / iptables 冲突

现象:Fail2ban 启动失败,提示 banaction 错误。

判断:


			   bash# 看哪个防火墙在跑 systemctl status firewalld systemctl status nftables iptables -L -n nft list ruleset 

修复:

  • 只保留一个防火墙
  • 改 fail2ban 配置中的 banaction

			   ini[DEFAULT] banaction = iptables-multiport # 或 banaction = nftables-multiport # 或 banaction = firewallcmd-rich-rules 

5.20 排障:CPU 高

现象:Fail2ban 进程 CPU 高。

原因:

  • usedns = yes  + DNS 服务器慢
  • 大日志 + polling 后端
  • findtime  太大

修复:

  • usedns = warn  或 no
  • backend = pyinotify (如果用 polling 改 inotify)
  • 拆 jail,每个 jail 单独监控一段日志

5.21 排障:磁盘写满

现象:fail2ban.log 巨大。

原因:DEBUG 日志级别。

修复:


			   ini[DEFAULT] loglevel = INFO 

清理旧日志:


			   bashtruncate -s 0 /var/log/fail2ban.log systemctl reload fail2ban 

5.22 监控:Prometheus 集成

使用 fail2ban-exporter 或文本文件方式:

5.22.1 文本文件方式

/usr/local/bin/fail2ban-exporter.sh


			   bash#!/bin/bash TEXTFILE_DIR="/var/lib/node_exporter/textfile_collector" TMP_FILE="$TEXTFILE_DIR/fail2ban.prom.tmp" OUT_FILE="$TEXTFILE_DIR/fail2ban.prom" # 先清空 "$TMP_FILE" # 遍历所有 jail JAILS=$(fail2ban-client status | grep 'Jail list' | sed -E 's/^.*://;s/,//g') for jail in $JAILSdo     STATUS=$(fail2ban-client status "$jail")     BANNED=$(echo "$STATUS" | awk '/Banned IP list/{print $NF}' | grep -oE '[0-9]+')     FAILED=$(echo "$STATUS" | awk '/Total failed/{print $NF}')     BANNED=${BANNED:-0}     FAILED=${FAILED:-0}     cat >> "$TMP_FILE" <<EOF # HELP fail2ban_jail_banned_ip_count Number of banned IPs in jail # TYPE fail2ban_jail_banned_ip_count gauge fail2ban_jail_banned_ip_count{jail="$jail"} $BANNED # HELP fail2ban_jail_total_failed Total failed login attempts in jail # TYPE fail2ban_jail_total_failed counter fail2ban_jail_total_failed{jail="$jail"} $FAILED EOF done # 原子替换 mv "$TMP_FILE" "$OUT_FILE" 

加 crontab:


			   bashchmod +x /usr/local/bin/fail2ban-exporter.sh echo '* * * * * /usr/local/bin/fail2ban-exporter.sh' | crontab - 

5.23 与 Prometheus 配合告警


			   yamlgroups:   - name: fail2ban     rules:       - alert: Fail2banJailHighBanCount         expr: fail2ban_jail_banned_ip_count > 100         for: 5m         labels:           severity: warning         annotations:           summary: "Jail {{ $labels.jail }} has banned {{ $value }} IPs"       - alert: Fail2banServiceDown         expr: absent(fail2ban_jail_banned_ip_count)         for: 5m         labels:           severity: critical         annotations:           summary: "Fail2ban exporter is down" 

5.24 数据库清理


			   bash# 备份 cp /var/lib/fail2ban/fail2ban.sqlite3 /var/lib/fail2ban/fail2ban.sqlite3.bak # 进入 sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 # 看所有 ban .mode column .headers on SELECT * FROM bans; # 清空 DELETE FROM bans; # 看 jail 状态 SELECT * FROM jails; 

5.25 升级与回滚


			   bash# 升级 yum update fail2ban systemctl restart fail2ban fail2ban-client status # 回滚 yum downgrade fail2ban-1.0.2-1.el7 systemctl restart fail2ban 

5.26 卸载


			   bash# 停服 systemctl stop fail2ban systemctl disable fail2ban # 卸载 yum remove fail2ban # 或 apt remove --purge fail2ban # 清空规则 iptables -L -n # 手动删除 f2b-* chain for chain in $(iptables -L -n | grep '^Chain f2b' | awk '{print $2}'); do     iptables -F "$chain"     iptables -X "$chain" done 

六、常用命令

6.1 服务控制


			   bash# 启动 systemctl start fail2ban systemctl enable fail2ban systemctl status fail2ban # 停止 systemctl stop fail2ban # 重启 systemctl restart fail2ban # 重新加载配置 systemctl reload fail2ban 

6.2 jail 管理


			   bash# 查看所有 jail fail2ban-client status # 查看指定 jail fail2ban-client status sshd # 启用 jail fail2ban-client set sshd addaction iptables-multiport # 禁用 jail fail2ban-client stop sshd # 启动 jail fail2ban-client start sshd # reload jail fail2ban-client reload sshd 

6.3 IP 管理


			   bash# unban 单个 IP fail2ban-client set sshd unbanip 1.2.3.4 # 列出被 ban 的 IP fail2ban-client status sshd # 列出所有 ban iptables -L f2b-sshd -n # nftables nft list chain inet filter f2b-sshd 

6.4 filter 测试


			   bash# 测试 logpath + filter fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf # 显示匹配行 fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf --print-all-matched # 详细模式 fail2ban-regex -v /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf # 调试新 filter fail2ban-regex /var/log/access.log "POST /xmlrpc.php" "Date: .*"  # 自带一个简单正则 

6.5 日志


			   bash# fail2ban 自身日志 tail -f /var/log/fail2ban.log journalctl -u fail2ban -f # 慢日志 journalctl -u fail2ban --since "1 hour ago" # 错误 journalctl -u fail2ban -p err 

6.6 防火墙


			   bash# iptables iptables -L -n iptables -L f2b-sshd -n iptables -D f2b-sshd -s 1.2.3.4 -j DROP # nftables nft list ruleset nft list chain inet filter f2b-sshd # firewalld firewall-cmd --list-all firewall-cmd --permanent --list-rich-rules 

6.7 数据库


			   bashsqlite3 /var/lib/fail2ban/fail2ban.sqlite3 sqlite> .tables sqlite> .schema bans sqlite> SELECT * FROM bans; sqlite> DELETE FROM bans; 

七、配置示例

7.1 完整 jail.local 范例


			   ini[DEFAULT] ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12 203.0.113.0/24 bantime = 1h findtime = 10m maxretry = 5 backend = systemd banaction = iptables-multiport chain = INPUT usedns = warn loglevel = INFO destemail = ops@example.com sender = fail2ban@example.com mta = sendmail action = %(action_mwl)s [sshd] enabled = true port = ssh filter = sshd logpath = %(sshd_log)s maxretry = 5 bantime = 1h [sshd-ddos] enabled = true port = ssh filter = sshd-ddos logpath = %(sshd_log)s maxretry = 10 findtime = 1h bantime = 24h [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/error.log maxretry = 5 bantime = 1h [nginx-bad-request] enabled = true port = http,https filter = nginx-bad-request logpath = /var/log/nginx/access.log maxretry = 10 bantime = 1h [nginx-botsearch] enabled = true port = http,https filter = nginx-botsearch logpath = /var/log/nginx/access.log maxretry = 5 bantime = 24h [vsftpd] enabled = true port = ftp,ftp-data,ftps,ftps-data filter = vsftpd logpath = /var/log/vsftpd.log maxretry = 3 bantime = 24h [postfix] enabled = true port = smtp,465,submission filter = postfix logpath = /var/log/mail.log maxretry = 5 bantime = 1h [dovecot] enabled = true port = pop3,pop3s,imap,imaps,submission filter = dovecot logpath = /var/log/mail.log maxretry = 5 bantime = 1h [recidive] enabled = true filter = recidive logpath = /var/log/fail2ban.log bantime = 1w findtime = 1d maxretry = 5 action = iptables-multiport[name=recidive] 

7.2 自定义 filter:业务登录失败

/etc/fail2ban/filter.d/myapp-login.conf


			   ini[Definition] failregex = ^ -[^"]* "POST /api/login HTTP/d+.d+" 401 d+ .*$             ^ -[^"]* "POST /api/login HTTP/d+.d+" 403 d+ .*$ ignoreregex = ^ -[^"]* "POST /api/login HTTP/d+.d+" 200 d+ .*$ 

jail:


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

7.3 自定义 filter:SSH 公私钥失败


			   ini[Definition] failregex = ^%(__prefix_line)s(? )?Authentication refused: bad ownership or modes for directory .*$             ^%(__prefix_line)s(? )?Authentication refused: too many authentication failures for .* from (?:s+port d*)?s*$             ^%(__prefix_line)sUser .+ from  not allowed because not listed in AllowUsers$             ^%(__prefix_line)sUser .+ from  not allowed because none of user's groups are listed in AllowGroups$             ^%(__prefix_line)srefused connect from S+ ()$ 

7.4 自定义 action:解封后写入审计日志

/etc/fail2ban/action.d/audit-log.conf


			   ini[Definition] actionban = /usr/bin/logger -p auth.warning "Fail2ban banned  in " actionunban = /usr/bin/logger -p auth.info "Fail2ban unbanned  in " 

7.5 与 nftables 配合

/etc/fail2ban/jail.local


			   ini[DEFAULT] banaction = nftables-multiport banaction_allports = nftables-allports chain = input 

/etc/nftables.conf


			   nfttable inet filter {     chain input {         type filter hook input priority 0; policy accept;         ct state established,related accept         iif lo accept         ip protocol tcp tcp dport { 22, 80, 443 } accept         ip protocol udp udp dport 53 accept         jump f2b-input     }     chain f2b-input {         return     } } 

八、日志或指标观察方法

8.1 fail2ban.log 关键内容


			   text2026-06-29 1045,123 fail2ban.filter [1234]: WARNING [sshd] Found 1.2.3.4 2026-06-29 1045,456 fail2ban.actions [1234]: WARNING [sshd] Ban 1.2.3.4 2026-06-29 1045,789 fail2ban.actions [1234]: NOTICE  [sshd] Unban 1.2.3.4 

关键字:

  • Found :检测到失败
  • Ban :封禁
  • Unban :解封
  • Restore /Save:规则变化

8.2 iptables 中看 ban 状态


			   bashiptables -L f2b-sshd -n -v # pkts bytes target  prot opt in  out source  destination # 1234  876M  DROP   all  --  *   *   1.2.3.4   0.0.0.0/0 

8.3 关键指标

指标 来源 健康范围 异常表现
Total failed fail2ban-client 业务相关 突增为被扫
Currently banned fail2ban-client < 100 > 1000 为大规模扫
Banned IP list fail2ban-client 业务相关 单 IP 多次 ban 为顽固攻击
fail2ban_jail_banned_ip_count exporter 业务相关 突增
fail2ban_jail_total_failed exporter 业务相关 突增
process_cpu_seconds_total{name="fail2ban-server"} node_exporter < 5% > 30% 为 filter 不命中
process_open_fds{name="fail2ban-server"} node_exporter < 1024 持续高为 inotify 监控太多文件

8.4 与业务侧核对

业务侧应该把可疑 IP 加进 ignoreip,把生产出口 IP 加进 ignoreip。每月巡检一次。

九、排查路径

9.1 Fail2ban 不启动

  • journalctl -u fail2ban -xe
  • fail2ban-server -t :前台调试模式
  • 检查 Python 版本、依赖

9.2 Filter 不匹配

  • fail2ban-regex  测试
  • 看日志格式变化(升级后 sshd 日志格式可能变了)
  • 看新版本 OS 的日志位置变化

9.3 IP 没被 ban

  • fail2ban-client status  看 Total failed 增长
  • 看 iptables / nftables / firewalld 规则
  • 看 banaction 是否被注释

9.4 IP 没被 unban

  • bantime = -1 :永久 ban
  • 配置文件 reload 没生效
  • time  命令校准

9.5 fail2ban-server 内存持续增长

  • journalctl  看异常
  • 升级到最新版本(修复内存泄漏)
  • 减少 jail 数量

9.6 自定义 filter 不工作

  • 用 fail2ban-regex --print-all-matched 验证
  • 检查 __prefix_line 格式
  • 检查 datepattern

十、风险提醒

  • ignoreip  没配会把自己 ban 出去
  • bantime = -1  永久 ban 风险极高(重启或 banaction 改了就解除,但中间没人能进)
  • usedns = yes  + DNS 服务器慢会让 fail2ban 卡住
  • 在 iptables / nftables / firewalld 三个防火墙间来回切换,规则会混乱
  • 自定义 action 中明文存 token 风险
  • maxretry  设得太小会误封正常用户
  • banaction  与系统自带工具链(puppet、saltstack、ansible)的 iptables 模块可能冲突
  • 与 K8s NetworkPolicy 冲突(封禁在主机层,K8s 内部流量不经过)
  • 自定义 filter 写错正则会导致 Currently failed 飙高
  • findtime  太大 + maxretry 太小会让正常流量被 ban
  • fail2ban 自身 log 写满磁盘
  • 同时启用 iptables 和 firewalld 会让 fail2ban 启动失败
  • recidive jail 的 bantime 要比普通 jail 长才有意义
  • 切换到 IPv6 时 banaction 仍为 iptables-legacy,可能不生效

十一、验证方式

11.1 启动验证


			   bashsystemctl status fail2ban fail2ban-client status # 至少显示 Number of jail 

11.2 SSH jail 验证


			   bash# 模拟失败 for i in 1 2 3 4 5 6; do     sshpass -p "wrong" ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no -o PreferredAuthentications=password wronguser@127.0.0.1 2>/dev/null done # 看是否被 ban fail2ban-client status sshd iptables -L f2b-sshd -n 

11.3 HTTP 401 验证


			   bashfor i in 1 2 3 4 5 6; do     curl -s -o /dev/null -u wrong:wrong http://example.com/admin/ done fail2ban-client status nginx-http-auth 

11.4 通知验证

  • 触发 ban,检查邮件/Slack/钉钉
  • 触发 unban,检查通知

11.5 监控验证


			   bashcurl http://127.0.0.1:9100/metrics | grep fail2ban_ 

十二、回滚方案

12.1 配置回滚


			   bash# 修改前 cp /etc/fail2ban/jail.local /etc/fail2ban/jail.local.bak.$(date +%Y%m%d_%H%M%S) # 出问题 cp /etc/fail2ban/jail.local.bak.YYYYMMDD_HHMMSS /etc/fail2ban/jail.local systemctl restart fail2ban 

12.2 全部 ban 解封


			   bash# 停止 fail2ban systemctl stop fail2ban # 清理 iptables for chain in $(iptables -L -n | grep '^Chain f2b' | awk '{print $2}'); do     iptables -F "$chain"     iptables -X "$chain" done # 清理数据库 sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "DELETE FROM bans;" # 启动 systemctl start fail2ban 

12.3 二进制回滚


			   bashyum downgrade fail2ban systemctl restart fail2ban 

12.4 临时关 jail


			   ini[DEFAULT] enabled = false 

			   bashsystemctl reload fail2ban 

十三、生产环境注意事项

  • 必须先在 ignoreip 中加好公司所有出口 IP、跳板机 IP、监控 IP
  • 必须保留对 sshd 的 match 与 bantime 不冲突的测试
  • 必须给 fail2ban 自身配监控(CPU、内存、磁盘、日志)
  • 必须 logrotate fail2ban.log
  • 必须有 unban 流程文档
  • recidive jail 必须开
  • 邮件 / Slack / 钉钉 告警必须验证
  • 自定义 filter 必须经过 fail2ban-regex 严格测试
  • 自定义 action 中 token 必须用环境变量
  • 必须定期巡检 iptables / nftables 规则数量
  • 必须避免 bantime = -1
  • 必须在多台机器上跑 fail2ban,相互解封通过 fail2ban-client 远程
  • 必须在 K8s 场景下考虑 sidecar 方案或 DaemonSet
  • 必须在云厂商场景下配置 VPC 防火墙兜底
  • 必须配置 logrotate / 日志切割
  • 必须把配置纳管到 git / 配置中心

十四、总结

Fail2ban 配置起来不难,但要跑稳,有 12 个关键点:

  • ignoreip 必须全
  • bantime 不能 -1
  • usedns 不能 yes
  • backend 用 systemd + pyinotify
  • recidive 必开
  • 自定义 filter 用 fail2ban-regex 测
  • 自定义 action 不用明文 token
  • 防火墙只能保留一个
  • 数据库要备份要清理
  • 监控要齐全
  • unban 流程要文档化
  • 配置版本化、可回滚

跑稳之后的实际收益是:SSH 撞库、网站爆破、FTP 弱口令、SMTP 撞库、WordPress xmlrpc 撞库这些常态化的攻击,全部被自动挡在门外。

最后留一个执行清单:

  • 第 1 步:装包、启动、确认 fail2ban-client status 有 Number of jail
  • 第 2 步:配置 jail.local,把公司 IP 全加 ignoreip
  • 第 3 步:启用 sshd,模拟 6 次失败,看是否被 ban
  • 第 4 步:启用 nginx-bad-request / nginx-botsearch,看是否能 ban
  • 第 5 步:开 recidive
  • 第 6 步:接告警(邮件 / Slack / 钉钉)
  • 第 7 步:接监控(Prometheus 文本文件)
  • 第 8 步:写 unban runbook
  • 第 9 步:把配置纳管到 git
  • 第 10 步:每月巡检规则数量、文件描述符、内存

附录 A:常见 Fail2ban 实战模板

A.1 阿里云 ECS 场景

阿里云服务器默认安全组只放行业务端口,Fail2ban 主要作用是减少业务层被撞的次数。注意:不要把 22 端口放在公网(用跳板机 + EIP + 安全组 0.0.0.0/0 放行 + jump host);让 Fail2ban 集中处理 Web 类撞库即可。

推荐配置:


			   ini[DEFAULT] ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12 bantime = 1h findtime = 10m maxretry = 5 backend = systemd banaction = iptables-multiport chain = INPUT usedns = warn loglevel = INFO [sshd] enabled = true port = ssh filter = sshd logpath = %(sshd_log)s maxretry = 5 bantime = 1h [nginx-botsearch] enabled = true port = http,https filter = nginx-botsearch logpath = /var/log/nginx/access.log maxretry = 5 bantime = 24h [recidive] enabled = true filter = recidive logpath = /var/log/fail2ban.log bantime = 1w findtime = 1d maxretry = 5 action = iptables-multiport[name=recidive] 

A.2 自建 IDC 场景


			   ini[DEFAULT] ignoreip = 127.0.0.1/8 ::1             10.0.0.0/8             192.168.0.0/16             172.16.0.0/12             203.0.113.0/24             198.51.100.0/24 bantime = 24h findtime = 30m maxretry = 5 backend = systemd banaction = nftables-multiport chain = input usedns = warn loglevel = INFO destemail = ops@example.com sender = fail2ban@example.com mta = sendmail [sshd] enabled = true port = ssh filter = sshd logpath = %(sshd_log)s maxretry = 3 bantime = 24h [nginx-bad-request] enabled = true port = http,https filter = nginx-bad-request logpath = /var/log/nginx/access.log maxretry = 10 bantime = 1h [recidive] enabled = true filter = recidive logpath = /var/log/fail2ban.log bantime = 30d findtime = 7d maxretry = 5 action = nftables-multiport[name=recidive] 

A.3 K8s 场景

K8s 中用 sidecar 模式跑 Fail2ban 不太合适(因为容器内 iptables 行为特殊)。一般用 DaemonSet + hostPath 共享日志 + privileged 模式:


			   yamlapiVersion: apps/v1 kind: DaemonSet metadata:   name: fail2ban spec:   selector:     matchLabels:       app: fail2ban   template:     metadata:       labels:         app: fail2ban     spec:       containers:         - name: fail2ban           image: crazymax/fail2ban:latest           securityContext:             privileged: true             capabilities:               add:                 - NET_ADMIN                 - NET_RAW           volumeMounts:             - name: var-log               mountPath: /var/log               readOnly: true             - name: fail2ban-config               mountPath: /etc/fail2ban             - name: fail2ban-data               mountPath: /var/lib/fail2ban       volumes:         - name: var-log           hostPath:             path: /var/log         - name: fail2ban-config           configMap:             name: fail2ban-config         - name: fail2ban-data           hostPath:             path: /var/lib/fail2ban 

但实际生产中更多用:

  • 主机层跑 Fail2ban(每台服务器一个)
  • 监控数据用 node_exporter + 文本文件收集器
  • 在 Calico / Cilium 场景用 NetworkPolicy 协同

A.4 Docker 场景

Docker 容器内一般不需要 Fail2ban,但 Docker daemon 暴露的端口仍然可能被撞。常见做法:

  • 把 SSH / Web 跑在主机层
  • 容器内只跑应用
  • 主机层跑 Fail2ban 即可

容器化跑 Fail2ban 适用场景:需要统一管理多台机器 / 想用 fail2ban 触发外部 API。


			   bashdocker run -d --name fail2ban      --net=host      --cap-add=NET_ADMIN      --cap-add=NET_RAW      -v /var/log:/var/log:ro      -v /etc/fail2ban:/etc/fail2ban      -v /var/lib/fail2ban:/var/lib/fail2ban      crazymax/fail2ban:latest 

附录 B:fail2ban-regex 实战

B.1 基础用法


			   bash# 用默认 filter 测试日志 fail2ban-regex /var/log/auth.log # 指定 filter fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf # 显示所有匹配 fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf --print-all-matched # 详细 fail2ban-regex -v /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf # 详细 + 显示 unmatched fail2ban-regex -v /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf --print-all-unmatched # 用内置示例测试 fail2ban-regex -v "Jan  1 1200 host sshd[1234]: Failed password for invalid user admin from 1.2.3.4"                 "Failed password for .* from " 

B.2 自定义 filter 调试步骤

  1. 抓 100 行真实日志
  2. 写 failregex 草稿
  3. fail2ban-regex  跑一遍
  4. 调整正则
  5. 再跑一遍
  6. 重复直到匹配

调试技巧:

  •  必须是第一个出现的 IP
  • __prefix_line  兼容 syslog、rsyslog、systemd 三种格式
  • 时间戳格式要明确
  • 调试时用 print-all-matched 看匹配是否合理

B.3 案例:自定义业务登录失败 filter

日志样例:


			   text[2026-06-29 1045] user_login FAILED user=alice ip=1.2.3.4 reason=password [2026-06-29 1046] user_login FAILED user=bob ip=1.2.3.4 reason=password [2026-06-29 1047] user_login OK user=carol ip=1.2.3.4 

filter:


			   ini[Definition] failregex = ^[d{4}-d{2}-d{2} d{2}:d{2}:d{2}] user_login FAILED user=S+ ip= reason=.*$ ignoreregex = ^[d{4}-d{2}-d{2} d{2}:d{2}:d{2}] user_login OK .*$ 

测试:


			   bashcat > /tmp/test.log <<'EOF' [2026-06-29 1045] user_login FAILED user=alice ip=1.2.3.4 reason=password [2026-06-29 1046] user_login FAILED user=bob ip=1.2.3.4 reason=password [2026-06-29 1047] user_login OK user=carol ip=1.2.3.4 EOF fail2ban-regex /tmp/test.log /etc/fail2ban/filter.d/myapp-login.conf 

输出:


			   textRunning tests ============= Use   failregex filter file : myapp-login, basedir: /etc/fail2ban Use         log file : /tmp/test.log Use         encoding : UTF-8 Results ======= Failregex: 2 total |-  #) [# of matches] regular expression |   1) [2] ^[d{4}-d{2}-d{2} d{2}:d{2}:d{2}] user_login FAILED user=S+ ip= reason=.*$ |      1.2.3.4  Mon Jun 29 1045 2026 |      1.2.3.4  Mon Jun 29 1046 2026 `- Ignoreregex: 0 total Date template hits: |- [# of hits] date format |  [3] Day-MONTH-Date HourSecond.Year `- Lines: 3 lines, 0 ignored, 2 matched, 1 failed 

附录 C:unban runbook

C.1 单 IP unban


			   bash# 找 jail fail2ban-client status # 看具体 jail fail2ban-client status sshd # 找 IP fail2ban-client status sshd | grep 'Banned IP list' # unban fail2ban-client set sshd unbanip 1.2.3.4 # 验证 fail2ban-client status sshd | grep 'Banned IP list' iptables -L f2b-sshd -n | grep 1.2.3.4 # 应该没有输出 

C.2 批量 unban


			   bash# unban 所有 IP for jail in $(fail2ban-client status | grep 'Jail list' | sed 's/.*://;s/,//g'); do     for ip in $(fail2ban-client status "$jail" | awk '/Banned IP list/{print $NF}' | tr ',' ' '); do         echo "Unbanning $ip from $jail"         fail2ban-client set "$jail" unbanip "$ip"     done done 

C.3 解封带白名单


			   bash# 先 unban fail2ban-client set sshd unbanip 1.2.3.4 # 加进 ignoreip echo "ignoreip = 1.2.3.4" >> /etc/fail2ban/jail.local # 或加到全局 # /etc/fail2ban/jail.local # [DEFAULT] # ignoreip = ... 1.2.3.4 systemctl reload fail2ban 

C.4 紧急 unban(fail2ban-server 不可用时)


			   bash# 直接操作 iptables iptables -L f2b-sshd -n --line-numbers iptables -D f2b-sshd  # 直接操作 nftables nft delete element inet filter f2b-sshd { 1.2.3.4 } # 直接操作 firewalld firewall-cmd --permanent --remove-rich-rule="rule family='ipv4' source address='1.2.3.4' reject" firewall-cmd --reload # 改数据库 sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "DELETE FROM bans WHERE ip='1.2.3.4';" 

附录 D:常见误封案例与解决方案

D.1 案例 1:CI/CD 机器被 ban

现象:Jenkins / GitLab Runner 因为代码里 ssh user@host 错配密码,被 fail2ban 封。

解决:

  • CI / CD 机器出口 IP 加 ignoreip
  • 改用 SSH 私钥

D.2 案例 2:监控节点被 ban

现象:监控机器用 SSH 探测端口和服务,频繁触发 Connection closed by authentication 误匹配。

解决:

  • 监控节点出口 IP 加 ignoreip
  • 改用 SSH 私钥

D.3 案例 3:内部 NAT 网段被 ban

现象:办公网 NAT 后所有员工都从同一个出口 IP 出去,几个人错密码,全公司被 ban。

解决:

  • 办公网出口 IP 加 ignoreip
  • bantime  缩短
  • 启用基于用户的 ban 而非基于 IP

D.4 案例 4:scp 中断被误判

现象:scp 传输大文件时网络抖动导致中断,被 fail2ban 算作失败。

解决:

  • 自定义 filter 中过滤 “connection reset” 类
  • 加 maxretry 容忍度

D.5 案例 5:客户端心跳被误判

现象:客户端每 30 秒发心跳,断线重连时被算作新连接 + 失败。

解决:

  • 改 filter
  • 加 maxretry 容忍度
  • 自定义  解析

附录 E:fail2ban-client 远程管理

Fail2ban 1.0+ 支持远程管理。配置服务端 socket 监听 TCP:

/etc/fail2ban/fail2ban.local


			   ini[Definition] socket = /var/run/fail2ban/fail2ban.sock [socket] [socktype=file] [file=/var/run/fail2ban/fail2ban.sock] [Definition] # 或开 TCP socket(需谨慎) [socket] [socktype=tcp] [address=0.0.0.0] [port=8023] [password=YOUR_STRONG_PASSWORD] 

注意:开 TCP socket 必须配强密码 + 防火墙限制。

客户端连接:


			   bashfail2ban-client -s 10.0.0.1:8023 -p YOUR_PASSWORD status 

但生产中更推荐:

  • 用 SSH 远程登录到 fail2ban 主机执行 fail2ban-client
  • 或用 Ansible / SaltStack 批量管理
  • 或用 API(自建)

附录 F:日志切割


			   bashcat > /etc/logrotate.d/fail2ban <<'EOF' /var/log/fail2ban.log {     daily     missingok     rotate 30     compress     delaycompress     notifempty     create 0640 root root     sharedscripts     postrotate         fail2ban-client flushlogs 2>/dev/null || true     endscript } EOF 

fail2ban-client flushlogs 会重新打开 fail2ban.log 文件。

附录 G:与其它安全工具协同

G.1 与 WAF 协同

WAF(ModSecurity、OpenResty、Coraza)跑在 HTTP 层,Fail2ban 跑在主机层。两者可以独立工作:

  • WAF:拦 SQL 注入、XSS、应用层 CC
  • Fail2ban:拦 SSH 撞库、WordPress 撞库、FTP 弱口令

WAF 把 401/403 写到 nginx error log,Fail2ban 监听这个 log 自动 ban。

G.2 与 IDS 协同

Suricata、Snort 触发告警后,写到 syslog,Fail2ban 监听 syslog 自动 ban。

filter:


			   ini[Definition] failregex = suricata: .* SRC= .*$ 

G.3 与 SIEM 协同

ELK、Splunk 接收 fail2ban.log 写入,做威胁分析、报告、合规。

附录 H:故障复盘案例

H.1 案例 1:被大规模分布式扫 SSH

现象:1 小时内 5000 个不同 IP 尝试 SSH 登录。

根因:未启用 sshd jail。

修复:启用 sshd jail,maxretry=3,bantime=24h,recidive 必开。

复盘:

  • 必须开 sshd jail
  • 必须开 recidive
  • 必须配监控

H.2 案例 2:被 CC 攻击导致 Fail2ban 占用大量内存

现象:fail2ban 进程内存占用 2GB,CPU 50%。

根因:nginx-bad-request filter 监听了 50GB 的大日志,pyinotify 跟不上节奏。

修复:

  • 拆 jail,每个 jail 监控特定子日志
  • logrotate 加大频率
  • 改用 polling + 定时清理

复盘:

  • 单个 logpath 不应超过 1GB
  • logpath 必须 logrotate
  • 监控 fail2ban 内存

H.3 案例 3:Fail2ban 升级后所有 jail 失效

现象:升级到 1.1 后所有 jail 报错,filter 不匹配。

根因:1.1 改了 failregex 语法,旧 filter 不兼容。

修复:使用官方最新 filter 模板,重新测试。

复盘:

  • 升级前在测试环境跑一遍
  • 自定义 filter 必须有备份
  • 升级后必须 fail2ban-client status 验证
  • 监控 fail2ban 自身存活

 


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分