找回密码
立即注册
搜索
发回帖 发新帖

6312

积分

0

好友

771

主题
发表于 15 小时前 | 查看: 3| 回复: 0

一、问题背景

生产环境里 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 配置结构

[DEFAULT]
backend = polling
bantime = 10m
findtime = 10m
maxretry = 5
ignoreip = 127.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 :

[INCLUDES]
before = common.conf

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

关键点:

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

3.5 action 配置

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

[Definition]
actionstart = iptables -N f2b-<name>
              iptables -A f2b-<name> -j RETURN
              iptables -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>

actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
             iptables -F f2b-<name>
             iptables -X f2b-<name>

actioncheck = iptables -n -L <chain> | grep -q 'f2b-<name>[ \t]'

actionban = iptables -I f2b-<name> 1 -s <ip> -j DROP

actionunban = iptables -D f2b-<name> -s <ip> -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:

[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 配合

[DEFAULT]
banaction = nftables-multiport
chain = input

/etc/nftables.conf :

table 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 :日志(可选)

查看数据库:

apt 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=5 、 bantime=1h :10 分钟内 5 次失败,封 1 小时
  • findtime=24h 、 maxretry=20 、 bantime=24h :24 小时内 20 次失败,封 24 小时
  • findtime=1h 、 maxretry=10 、 bantime=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 长时间封禁。

[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

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

5.1.2 CentOS / RHEL

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

5.1.3 源码

git 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 容器化

docker 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 验证安装

# 版本
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 :

[DEFAULT]
# 白名单
ignoreip = 127.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 中追加:

[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

# 启动
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

[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 (系统自带):

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

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

5.7 启用 nginx-bad-request

[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 :

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

5.8 启用 nginx-botsearch

[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

[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

[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

[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 :

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

说明:ignoreregex 中的正则用于排除 GET/HEAD 请求,原文表达式未严格闭合引号,实际使用时建议用 fail2ban-regex 验证后再上线。

/etc/fail2ban/jail.local :

[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 :

[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl -s -X POST "<webhook_url>" \
              -H "Content-Type: application/json" \
              -d '{"msgtype":"text","text":{"content":"[Fail2ban] 封禁 IP <ip> 在 jail <name>,剩余时间 <bantime> 秒"}}'
actionunban = curl -s -X POST "<webhook_url>" \
              -H "Content-Type: application/json" \
              -d '{"msgtype":"text","text":{"content":"[Fail2ban] 解封 IP <ip> 在 jail <name>"}}'

[Init]
webhook_url = https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN

jail 中引用:

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

5.14 自定义 action:Slack Webhook

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

[Definition]
actionban = curl -s -X POST "<webhook_url>" \
              -H "Content-Type: application/json" \
              -d '{"text":"[Fail2ban] Banned IP <ip> in jail <name>"}'
actionunban = curl -s -X POST "<webhook_url>" \
              -H "Content-Type: application/json" \
              -d '{"text":"[Fail2ban] Unbanned IP <ip> in jail <name>"}'

[Init]
webhook_url = https://hooks.slack.com/services/YOUR/WEBHOOK

5.15 与云厂商安全组协同

5.15.1 AWS

pip install boto3

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

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

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

5.15.2 Cloudflare

pip install cloudflare

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

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

/etc/fail2ban/cloudflare-ban.py :

#!/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 防止误封

[DEFAULT]
# 加 IP 白名单
ignoreip = 127.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
  • 配置确认后再启用
[DEFAULT]
enabled = false

5.17 排障:Fail2ban 不生效

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

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

命令检查:

# 看 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,无法连回。

解决:

# 在另一台机器上 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 错误。

判断:

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

修复:

  • 只保留一个防火墙
  • 改 fail2ban 配置中的 banaction
[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 日志级别。

修复:

[DEFAULT]
loglevel = INFO

清理旧日志:

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

5.22 监控:Prometheus 集成

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

5.22.1 文本文件方式

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

#!/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 $JAILS; do
    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:

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

5.23 与 Prometheus 配合告警

groups:
- 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 数据库清理

# 备份
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 升级与回滚

# 升级
yum update fail2ban
systemctl restart fail2ban
fail2ban-client status

# 回滚
yum downgrade fail2ban-1.0.2-1.el7
systemctl restart fail2ban

5.26 卸载

# 停服
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 服务控制

# 启动
systemctl start fail2ban
systemctl enable fail2ban
systemctl status fail2ban

# 停止
systemctl stop fail2ban

# 重启
systemctl restart fail2ban

# 重新加载配置
systemctl reload fail2ban

6.2 jail 管理

# 查看所有 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 管理

# 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 测试

# 测试 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: <DATE>.*"
# 自带一个简单正则

6.5 日志

# 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 防火墙

# 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 数据库

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

七、配置示例

7.1 完整 jail.local 范例

[DEFAULT]
ignoreip = 127.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 :

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

jail:

[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 公私钥失败

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

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

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

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

7.5 与 nftables 配合

/etc/fail2ban/jail.local :

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

/etc/nftables.conf :

table 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 关键内容

2026-06-29 10:23:45,123 fail2ban.filter [1234]: WARNING [sshd] Found 1.2.3.4
2026-06-29 10:23:45,456 fail2ban.actions [1234]: WARNING [sshd] Ban 1.2.3.4
2026-06-29 10:33:45,789 fail2ban.actions [1234]: NOTICE  [sshd] Unban 1.2.3.4

关键字:

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

8.2 iptables 中看 ban 状态

iptables -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 启动验证

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

11.2 SSH jail 验证

# 模拟失败
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 验证

for 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 监控验证

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

十二、回滚方案

12.1 配置回滚

# 修改前
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 解封

# 停止 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 二进制回滚

yum downgrade fail2ban
systemctl restart fail2ban

12.4 临时关 jail

[DEFAULT]
enabled = false
systemctl 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 类撞库即可。

推荐配置:

[DEFAULT]
ignoreip = 127.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 场景

[DEFAULT]
ignoreip = 127.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 模式:

apiVersion: 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。

docker 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 基础用法

# 用默认 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 12:00:00 host sshd[1234]: Failed password for invalid user admin from 1.2.3.4" \
"Failed password for .* from <HOST>"

B.2 自定义 filter 调试步骤

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

调试技巧:

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

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

日志样例:

[2026-06-29 10:23:45] user_login FAILED user=alice ip=1.2.3.4 reason=password
[2026-06-29 10:23:46] user_login FAILED user=bob ip=1.2.3.4 reason=password
[2026-06-29 10:23:47] user_login OK user=carol ip=1.2.3.4

filter:

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

测试:

cat > /tmp/test.log <<'EOF'
[2026-06-29 10:23:45] user_login FAILED user=alice ip=1.2.3.4 reason=password
[2026-06-29 10:23:46] user_login FAILED user=bob ip=1.2.3.4 reason=password
[2026-06-29 10:23:47] user_login OK user=carol ip=1.2.3.4
EOF
fail2ban-regex /tmp/test.log /etc/fail2ban/filter.d/myapp-login.conf

输出:

Running 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=<HOST> reason=.*$
|      1.2.3.4  Mon Jun 29 10:23:45 2026
|      1.2.3.4  Mon Jun 29 10:23:46 2026
`-

Ignoreregex: 0 total
Date template hits:
|- [# of hits] date format
|  [3] Day-MONTH-Date Hour:Minute:Second.Year
`-

Lines: 3 lines, 0 ignored, 2 matched, 1 failed

附录 C:unban runbook

C.1 单 IP unban

# 找 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

# 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 解封带白名单

# 先 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 不可用时)

# 直接操作 iptables
iptables -L f2b-sshd -n --line-numbers
iptables -D f2b-sshd <num>

# 直接操作 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 容忍度
  • 自定义 <HOST> 解析

附录 E:fail2ban-client 远程管理

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

/etc/fail2ban/fail2ban.local :

[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 必须配强密码 + 防火墙限制。

客户端连接:

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

但生产中更推荐:

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

附录 F:日志切割

cat > /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:

[Definition]
failregex = suricata: .* SRC=<HOST> .*$

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 自身存活

以上配置方案在云栈社区有更多实战讨论,欢迎交流分享你的 Fail2ban 落地经验。




上一篇:Linux 运维排障命令速查:CPU 内存 磁盘 IO 网络进程排查路径
下一篇:Win11 26H2内存占用实测:空闲仅2.5GB,8GB老电脑优化指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-9 19:53 , Processed in 0.279255 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表