fail2ban — 入侵防御自动化封禁详解

fail2ban 是一个用 Python 编写的开源入侵防御工具(HIDS/IPS)。它扫描日志文件(如 /var/log/auth.log),一旦发现同一 IP 在短时间内出现多次认证失败,就通过修改系统防火墙规则(iptables / nftables / ipset 等)把该 IP 拒之门外,并在可配置的时间后自动解封。它开箱即可读懂 sshd、Apache、nginx 等几十种常见日志,也能轻易接入任何自研应用的日志。

本页先梳理 fail2ban 的"是什么"与行为/结构,再给出实战操作手册与参数指令速查。

先泼一盆冷水:fail2ban 只能降低暴力破解的速率,不能消除弱口令风险。官方明确建议:真正要保护服务,请使用双因子认证(2FA)或公钥/私钥认证机制。fail2ban 是"第二道防线",不是"万能药"。

1. 概述

1.1 是什么

fail2ban(f2b)由 Cyril Jaquier 于 2004 年编写,GPL v2 协议,当前由社区维护(v1.x,代码托管在 github.com/fail2ban/fail2ban)。它由三个可执行组件构成:

核心数据结构是 Jail(监狱):每一个被保护的服务(如 sshd、apache)就是一个 jail,里面定义了"看哪个日志、怎么判定失败、失败多少次封禁、用什么方式封禁、封多久"。fail2ban 会为每个 jail 起一个独立线程持续运转。

1.2 核心心智模型:Jail = Filter + Action

记住一句话:Jail(监狱)= Filter(过滤器,负责"判罪")+ Action(动作,负责"执行刑罚")+ 判决参数(bantime / findtime / maxretry)。

# 一条典型日志行(sshd 认证失败)
Feb  3 14:11:15 host sshd[25076]: Failed password for invalid user admin from 203.0.113.7 port 53526 ssh2

# 流程:datepattern 去掉时间 → failregex 匹配失败模式并抠出 IP
# failregex 得到  = 203.0.113.7 → 在 findtime(如10m) 内累计 ≥ maxretry(如5次) → 封禁 bantime(如1h)

四个"时间/次数"参数是所有 jail 的公共语言:

核心心智模型:把 fail2ban 想成一位"日志刑警"。它派一个警官(JailThread)盯着日志(logpath),用"判罪标准"(failregex/ignoreregex)认定谁是罪犯(IP),"案底"累计到够判(maxretry)就签发"逮捕令"(BanTicket),然后由"狱警"(Action)执行关押(actionban 写防火墙规则)。刑满(bantime)自动释放(actionunban)。

1.3 生态与位置:IDS/IPS/防火墙

要理解 fail2ban 在安全体系中的位置,先分清这些角色:

专有名词:

1.4 安装方式

发行版安装命令说明
Debian / Ubuntusudo apt install fail2ban仓库版本通常较新
RHEL / CentOS / Rockysudo yum install fail2ban可能需要 EPEL 源
Fedorasudo dnf install fail2ban—
Arch / Manjarosudo pacman -S fail2ban—
macOS (Homebrew)brew install fail2ban配合 launchd 使用

环境要求:Python ≥ 3.5(可选依赖 pyinotify、python-systemd、dnspython)。源码安装:

git clone https://github.com/fail2ban/fail2ban.git
cd fail2ban
sudo python setup.py install
# 自检与版本确认
fail2ban-client -h
fail2ban-client version

启动与开机自启(systemd 发行版):

sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban

2. 行为图(系统"做什么")

2.1 用例图 — 交互全景

从外部视角看,谁在与 fail2ban 打交道:管理员通过 client 控制它;攻击者持续失败登录"喂"它;它则指挥防火墙、发通知。

PlantUML diagram
图 2.1 用例图 — fail2ban 交互全景

2.2 活动图 — 从日志到封禁主流程

这是 fail2ban 的核心主流程:读日志 → 三层正则过滤 → 计数 → 到阈值 → 封禁 → 到期解封。

PlantUML diagram
图 2.2 活动图 — 一条日志行从读取到封禁/忽略的判定主流程

代码演示:三条 filter 规则如何层层把关

# filter.d/sshd.conf 中的判定链
[Definition]
prefailregex = ^<F-MLFID>%(__prefix_line)s<F-MLFID/>(?: %(__sshd_exclude)s)?(?: \\[pid <F-ID>\\d+<F-ID/>\\])? \
               <F-CONTENT>.+</F-CONTENT>$
failregex = ^%(__prefix_line)s(?:error: )?Received disconnect from <HOST>.*: 3: .*: Auth fail$
            ^%(__prefix_line)sInvalid user <USER> from <HOST> port $
            ^%(__prefix_line)s(?:error: )?pam_unix\(sshd:auth\): authentication failure; logname=.* uid=.* euid=.* tty=.* ruser=.* rhost=<HOST>
ignoreregex =

2.3 时序图 — 攻击→封禁→解封

把一次完整的攻击生命周期画成时序:攻击者失败登录 → sshd 写日志 → fail2ban 读到并计数 → 触发 actionban → 防火墙拒绝 → bantime 到期 → actionunban 恢复。

PlantUML diagram
图 2.3 时序图 — 一次暴力攻击从发生到被封禁再到自动解封

2.4 状态机图 — 失败票据与封禁生命周期

一个 IP 在 fail2ban 内部的"案底"演化:从累积失败(Ticket)到被判刑(BanTicket),再到刑满释放(unban),或通过 recidive / increment 越封越重。

PlantUML diagram
图 2.4 状态机图 — 失败票据与封禁的生命周期

3. 结构图(系统"由什么组成")

3.1 部署图 — 生产部署拓扑

先看宏观部署:fail2ban 的 client/server 如何用 socket 通信,server 又连接了哪些外部资源(日志、防火墙、sqlite、SMTP、DNS)。

PlantUML diagram
图 3.1 部署图 — 一台服务器上的 fail2ban 生产部署拓扑

3.2 组件图 — 内部模块

再看进程内部:fail2ban 是 Python 源码,client 与 server 分离,server 内部按职责拆分为 filters、regex、failmanager、banmanager、actions、database 等模块。

PlantUML diagram
图 3.2 组件图 — fail2ban 内部模块协作

3.3 包图 — /etc/fail2ban 配置布局

fail2ban 的"四个配置文件类型":fail2ban.conf(全局)、jail.conf(监狱)、filter.d/*.conf(过滤器)、action.d/*.conf(动作)。配置有严格的覆盖优先级。

PlantUML diagram
图 3.3 包图 — 配置目录布局与加载优先级
配置覆盖铁律:发行版自带的 *.conf 一律不修改(方便升级);所有自定义改动写进 *.local 或 .d/ 目录。同名的 .local 覆盖 .conf,晚解析的覆盖早解析的:jail.conf → jail.d/*.conf(字母序) → jail.local → jail.d/*.local(字母序)。

代码演示:只改一个文件启用默认 sshd 监狱

# 创建 /etc/fail2ban/jail.local,仅写想改的项即可
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1h

3.4 类图 — 核心类结构

最后细化到核心类:JailThread 是每个监狱的运转线程,持有 Filter、FailManager、BanManager、Actions;通过 Transmitter 接收 client 命令。

PlantUML diagram
图 3.4 类图 — fail2ban 核心类结构与关系

4. 实战操作手册

4.1 安装与启动

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

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

# 确认进程与版本
systemctl status fail2ban
fail2ban-client version          # v1.x
fail2ban-client ping             # 返回 pong 说明 server 存活

4.2 基础配置:启用 sshd 监狱

永远不要直接编辑 /etc/fail2ban/jail.conf(升级会被覆盖)。新建 jail.local:

# /etc/fail2ban/jail.local
[DEFAULT]
# 全局默认:10分钟内失败5次,封禁1小时
bantime  = 1h
findtime = 10m
maxretry = 5
# 永不封禁自己的/内网/办公出口 IP(用逗号分隔,支持 CIDR)
ignoreip = 127.0.0.1/8 ::1 192.168.0.0/16 10.0.0.0/8
# 默认封禁动作:按多端口写 iptables 规则(IPv4+IPv6 自动)
banaction = iptables-multiport
# 有 SMTP 的话可加邮件通知
# action = %(action_mwl)s   # 会同时调用 sendmail 发通知

[sshd]
enabled = true

应用配置并验证:

sudo systemctl reload fail2ban          # 热重载(不重启 server)
fail2ban-client status                  # 列出所有监狱
fail2ban-client status sshd             # 查看 sshd 监狱详情
注意:reload 会按新配置重建受影响的监狱,但不会清空已有封禁。想彻底重启监狱并清空封禁用 fail2ban-client reload --restart --unban sshd。

4.3 自定义 Filter(新应用接入)

以监控自研应用(如后端 API 的登录接口)为例:日志形如 2026-08-20 10:00:01 ERROR login failed ip=203.0.113.9 user=admin。

# 1) 新建过滤器 /etc/fail2ban/filter.d/myapp.conf
[Definition]
# 匹配失败日志并抠出 IP; 是内置的"IP或主机名"正则
failregex = ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ERROR login failed ip=<HOST> 
ignoreregex = ^.*user=(?:test|root)$     # 测试账号失败不封禁

# 2) 新建监狱 /etc/fail2ban/jail.local 追加
[myapp]
enabled  = true
filter   = myapp
logpath  = /var/log/myapp/api.log
backend  = polling
maxretry = 3
findtime = 5m
bantime  = 10m

先用 fail2ban-regex 验证正则(见 4.7),再 reload:

sudo systemctl reload fail2ban
fail2ban-client status myapp

4.4 自定义 Action(封禁+通知)

默认的 iptables-multiport 只做"DROP",可以再加动作:邮件通知、调 webhook、或自定义命令。

# /etc/fail2ban/action.d/mywebhook.local
[Definition]
actionban   = curl -s -X POST http://alert.example.com/ban \
              -d 'ip=<ip>&count=<bancount>&jail=<name>'
actionunban = curl -s -X POST http://alert.example.com/unban \
              -d 'ip=<ip>&jail=<name>'
actionstart = echo "jail <name> started at <time>" 
actionstop  = echo "jail <name> stopped at <time>"

# jail.local 里给 sshd 同时挂默认防火墙动作 + 通知动作(可挂多个)
[sshd]
enabled   = true
action    = iptables-multiport[name=sshd, port="ssh", protocol=tcp]
            mywebhook

动作命令通过 shell 执行,<ip>、<name>、<time>、<failures>、<matches>、<bancount> 等标签会被自动替换(完整标签见 5.4)。

4.5 日常运维命令

fail2ban-client status                      # 全部监狱状态
fail2ban-client status sshd                 # 单个监狱:封禁列表/失败统计
fail2ban-client status --all                # 详细模式
fail2ban-client stats                       # 全部监狱统计表格
fail2ban-client set sshd bantime 2h         # 热修改封禁时长
fail2ban-client set sshd banip 203.0.113.9  # 手动立即封禁一个 IP
fail2ban-client set sshd unbanip 203.0.113.9   # 手动解封
fail2ban-client unban --all                 # 清空全部封禁
fail2ban-client get sshd banned             # 列出该监狱封禁的 IP
fail2ban-client reload                      # 热重载配置
fail2ban-client restart                     # 重启整个 server
fail2ban-client set loglevel DEBUG          # 临时调日志级别调试

4.6 进阶:渐进封禁与 recidive

对付顽固攻击者:bantime.increment 让封禁时长逐次翻倍;recidive 监狱专门"封禁那些反复被其他监狱封禁的 IP"。

# jail.local 开启渐进封禁(全部监狱生效)
[DEFAULT]
bantime.increment = true
bantime.factor    = 2        # 每次翻倍
bantime.maxtime   = 1w       # 封禁时长上限
bantime.random    = true     # 加入随机扰动防规律
bantime.overalljails = true  # 跨监狱累计次数

# recidive 监狱:24h 内被其它监狱封禁 ≥3 次的 IP,直接封 1 周
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = iptables-allports
bantime  = 1w
findtime = 1d
maxretry = 3

4.7 正则测试:fail2ban-regex

写正则最容易出错,务必先用工具验证再上线:

# 用法: fail2ban-regex <LOG> <REGEX> [IGNOREREGEX]
# LOG 可以是:日志文件路径 / systemd-journal / 单行字符串
fail2ban-regex /var/log/auth.log '^.*Failed password for invalid user admin from <HOST> port '
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
fail2ban-regex '2026-08-20 10:00:01 ERROR login failed ip=203.0.113.9' \
  '^.*login failed ip=<HOST> '

# 常用选项
# -r/--raw      不解析 DNS,只看 IP
# --usedns=no   只匹配 IP 不匹配主机名
# -d PATTERN    自定义 datepattern
# -L N          多行正则的 maxlines
# -o 'ip,msg'   只输出 ip 和 msg 字段
fail2ban-regex --usedns=no -r /var/log/auth.log sshd

输出会告诉你:Successfully matched(成功匹配数)与 Date / IP / Host 命中统计。只要 Successfully matched > 0 且 IP 列正确,这个 filter 才能用。

5. 参数指令速查手册

5.1 fail2ban-client 命令一览

分类命令说明
基础start启动 server 和所有监狱
restart重启 server
reload [--restart] [--unban] [--if-exists] [<jail>]热重载配置;--restart 重启受影响监狱;--unban 同时清空封禁
stop停止所有监狱并终止 server
查询status [<jail>] [FLAVOR]server 或某监狱状态(basic/cymru/short/stats)
statistics全部监狱统计表
banned [<ip>...]有封禁的监狱字典 / 指定 IP 在哪些监狱被封
ping / version探活 / 版本
封禁unban --all清空所有封禁(含数据库)
unban <ip>... 解封指定 IP(所有监狱+数据库)
日志set loglevel <LEVEL>设置级别 CRITICAL/ERROR/WARNING/NOTICE/INFO/DEBUG/TRACEDEBUG/HEAVYDEBUG
set logtarget <TARGET>STDOUT/STDERR/SYSLOG/SYSTEMD-JOURNAL/文件
flushlogs关闭并重开日志文件(配合 logrotate)
数据库set dbfile <FILE>持久化库路径(None 关闭)
set dbmaxmatches <INT>每票据数据库保存的最大匹配行数(默认10)
set dbpurgeage <SECONDS>封禁历史保留时长(默认86400)
监狱控制add <jail> <backend>创建监狱(指定 backend)
start <jail> / stop <jail>启动/停止并移除某监狱
status <jail>某监狱详情
监狱配置set <jail> banip <ip>...手动封禁
set <jail> unbanip <ip>...手动解封
set <jail> bantime <TIME>改封禁时长
set <jail> maxretry <RETRY>改失败阈值
set <jail> findtime <TIME>改回看窗口
set <jail> addignoreip <ip> / delignoreip增删白名单

5.2 jail.conf 参数表

参数默认说明
enabledfalse是否启用该监狱
filter—使用的过滤器名(filter.d 下的文件名去扩展名)
logpath—监控的日志文件,支持通配符 glob,可加 tail 选项(从尾部读)
backendautoauto / pyinotify / polling / systemd
bantime10m封禁时长
findtime10m失败统计回看窗口
maxretry5窗口内失败次数阈值
ignoreip127.0.0.1/8 ::1白名单,支持 CIDR、DNS、file: 前缀加载外部名单
ignoreselftrue不封禁本机自身 IP
ignorecommand—封禁前执行的判断命令,退出码0则不封禁;可用 <ip> 等标签
ignorecache—ignore 判断的缓存(key/max-count/max-time)
banactioniptables-multiport默认封禁动作名
banaction_allportsiptables-allportsallports 类监狱(pam-generic/recidive)用
action%(action_mw)s动作列表,可带参数 iptables-ipset[type=multiport, blocktype=DROP],可配多个
maxmatches同 maxretry每票据内存保留的匹配行数
usednswarn是否解析日志中的主机名:yes/no/warn
logencodingauto日志文件编码
logtimezone—无时区日志的强制时区(Z/UTC±hhmm/GMT±hhmm)
skip_if_nologsfalse匹配不到日志文件时跳过该监狱而非报错
systemd_if_nologstrueauto 后端找不到文件时可切到 systemd journal
bantime.incrementfalse渐进封禁(每次翻倍)
bantime.factor / maxtime / random / overalljails—渐进封禁的系数/上限/随机/跨监狱累计

5.3 filter.d 参数表

参数说明
prefregex公共部分预处理正则;可用 <F-CONTENT>... 截取内容、<F-MLFID> 关联多行
failregex判定失败的核心正则,必须能提取 <HOST> 或 <F-ID>
ignoreregex匹配即忽略(即使命中 failregex)
maxlines多行正则的缓冲行数
datepattern自定义时间匹配,支持 Epoch/TAI64N/ISO8601,{DEFAULT}/{^LN-BEG} 等
journalmatchsystemd journal 的匹配表达式(journalctl 语法)

正则内建标签:

标签含义
<HOST>IP 或主机名(受 usedns 影响)
<ADDR>IPv4/IPv6 地址
<IP4> / <IP6>IPv4 / IPv6
<DNS>主机名
<CIDR> / <SUBNET>CIDR 掩码 / IP/CIDR 网段
<F-ID>...<F-ID>不用 IP 时的自定义封禁标识(如 user id)
<F-USER>...</F-USER>自由命名捕获组,可在 action 中用 <F-USER> 引用
<SKIPLINES>多行正则中允许跳过的行

5.4 action.d 参数表与标签

参数触发时机
actionstart监狱启动时(如创建防火墙链)
actionstop监狱停止时(如删除链)
actioncheck每次封禁/解封前检查环境;退出码非0会触发重初始化(先 stop 再 start)
actionban达到 maxretry 触发封禁时执行
actionunbanbantime 到期或手动解封时执行
timeout命令最大执行时长(秒),超时被杀

命令中可替换的标签(<ip>、<time> 等):

标签含义
<ip>被封 IP
<family>inet4 / inet6
<failures>本次票据的失败次数
<time>封禁的 Unix 时间戳
<bantime>本次实际封禁时长(秒)
<bancount>该 IP 累计被封次数
<matches>触发封禁的日志行拼接字符串(已转义防注入)
<ip-rev> / <ip-host>IP 的 PTR 记录 / 反解主机名
<fid>封禁事件标识(Ticket 标识符)
<restored>1=从数据库恢复的封禁,0=新封禁
<F-*>filter 中 <F-TAG> 捕获的自由字段
<ipfailures> / <ipjailfailures>基于数据库的跨监狱 / 本监狱累计失败数
<jail.banned> / <jail.found>该监狱当前封禁数 / 当前失败票据数

5.5 时间缩写格式

bantime、findtime 等时间参数既可用整数秒,也可用缩写:

单位缩写示例
年years / yea / yy1y = 31536000s
月months / mon1mo
周weeks / wee / ww1w
天days / da / dd1d
小时hours / hou / hh12h
分钟minutes / min / mm10m
秒seconds / sec / ss30s

可组合:1d12h30m;注意 m/mm 是分钟,月份要用 mo/mon。换算验证:

fail2ban-client --str2sec 1d12h     # 输出 129600

6. 故障排查与常见问题

现象原因排查 / 解决
启 jail 报 Have not found any log filelogpath 通配符没匹配到文件确认路径与权限;可加 skip_if_nologs = true;或允许 auto 后端切 systemd
该封的 IP 没被封failregex 没匹配 / 命中 ignoreip / 反解 DNS 对不上用 fail2ban-regex 实测;查看 /var/log/fail2ban.log 的 DEBUG 输出
日志里 RepeatedMsgReduction 导致漏检rsyslog 把重复消息压缩成 "last message repeated N times"在 rsyslog 配置中关闭 RepeatedMsgReduction
封禁不生效(防火墙没规则)action 名称/参数错误,或 iptables 权限不足fail2ban-client get sshd actions 查看已挂动作;手跑 actionban 看报错
误封了正常 IP回看窗口太短 / maxretry 太低 / 反解 DNS 不对称加大 findtime、调高 maxretry;把 usedns 设 no(只认 IP);加 ignoreip
IPv6 不生效allowipv6 设置或后端不支持确认 allowipv6;确认 iptables6/nft 规则链
重启后封禁丢失dbfile 未启用或损坏确认 /var/lib/fail2ban/fail2ban.sqlite3 存在且可写
日志文件轮转后读不到logrotate 与 fail2ban 未联动用 flushlogs 重开日志;或按发行版配置 logrotate 的 postrotate 脚本
fail2ban-client ping 失败server 未运行 / socket 路径不对systemctl status fail2ban;确认 /var/run/fail2ban/fail2ban.sock 存在
安全提醒:fail2ban 依赖防火墙生效,且只能防"网络层"的连不上,对已登录后的横向移动、应用层漏洞无能为力。务必配合:SSH 密钥/2FA、最小权限账号、及时打补丁、fail2ban 自身日志监控(recidive)。

7. 参考资料