Ubuntu 22.04 Fail2Ban 入侵防御部署与命令详解
Ubuntu 22.04 系统 Fail2Ban 入侵防御工具的安装(在线 apt 与离线 deb)与 fail2ban-client 全部常用命令参数实测详解,含 jail/filter 配置与错误登录封禁实战
Ubuntu 22.04 Fail2Ban 入侵防御部署与命令详解
目标:掌握 Ubuntu 22.04 下 Fail2Ban(v0.11.2)的在线/离线安装、jail/filter/action 配置详解、fail2ban-client 全部常用命令与参数真实回显、错误登录封禁/解封实战演练。本文全部命令回显已在 Ubuntu 22.04 真实环境实测通过
一、环境信息
本文所有命令均在一套 Ubuntu 22.04.5 LTS 最小化安装系统(systemd 托管,apt 包管理器) 上真实执行,关键环境参数如下:
| 项目 | 版本 / 值 | 实测依据 |
|---|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS(jammy) | /etc/os-release 的 PRETTY_NAME="Ubuntu 22.04.5 LTS" |
| 内核 | 6.8.0-138-generic(x86_64) | uname -r |
| Fail2Ban | 0.11.2-6(jammy/universe 源) | fail2ban-client -V → 0.11.2,dpkg -l fail2ban |
| Python 运行时 | /usr/bin/python3(3.10.12) | python3 --version;fail2ban-server 由 python3 /usr/bin/fail2ban-server -xf start 解释执行 |
| OpenSSH | openssh-server 1:8.9p1-3ubuntu0.17(amd64) | dpkg -l openssh-server |
| 客户端工具 | openssh-client(ssh 命令)、sshpass 1.09 | sshpass -V → sshpass 1.09 |
| 防火墙 | iptables(legacy 链),banaction=iptables-multiport | iptables -S | grep -i f2b 显示 f2b-sshd 链与 REJECT 规则 |
| 日志体系 | systemd journald(backend=systemd),rsyslog 未运行 | fail2ban-client status sshd 显示 Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd;systemctl is-active rsyslog → inactive |
| 日志文件 | /var/log/auth.log(由 journalctl 导出生成,12 行);/var/log/fail2ban.log(fail2ban 自身日志,属 root:adm) | ls -l、wc -l 实测 |
| 测试时间 | 2026-10-06(UTC,各回显时间戳) | 命令回显时间戳 |
说明:本环境为纯 systemd 日志环境(journald),未运行传统 rsyslog,因此
/var/log/auth.log不实时追加(测试中用journalctl导出生成样本)。Fail2Ban 走 journal 匹配(Journal matches)。默认 Ubuntu 22.04 服务器安装会常驻 rsyslog 并把 sshd 认证日志写入/var/log/auth.log,两种形态 Fail2Ban 均支持,区别详见"五、配置详解"的 backend 章节与 FAQ。
二、工具概述
2.1 工作原理:jail → filter → action 三段式
Fail2Ban 是一个基于日志的暴力破解防护框架,核心思想是:持续扫描认证日志,当某个来源 IP 在时间窗口内失败的次数超过阈值,就调用防火墙规则把该 IP 临时封禁。
其运行模型由三个层次组成:
日志流(journald / 日志文件)
│
▼
┌────────── jail(监狱:逻辑单元)──────────┐
│ 绑定 filter(匹配规则)+ action(处置动作) │
│ 参数:maxretry / findtime / bantime / │
│ ignoreip / ignoreself / backend │
└──────────────┬──────────────┬───────────────┘
│ │
┌─────────▼──────┐ ┌───▼──────────────┐
│ filter 正则 │ │ action 封禁命令 │
│ (sshd.conf) │ │ (iptables-multiport│
│ failregex │ │ firewallcmd 等) │
│ ignoreregex │ │ │
└────────────────┘ └──────────────────┘
一次完整的"封禁事件"流程如下:
- 采集:jail 的 backend(本文为
systemd,即 journald)持续读取 sshd 产生的认证日志;若为文件 backend(pyinotify/polling/auto),则监听日志文件尾部。 - 解析时间:用 datepattern 从每行日志中解析出时间戳,判断是否落在
findtime窗口内。 - 提取内容:prefregex 先把"日志前缀(时间/主机/进程)"与"日志内容"分离。
- 匹配失败:failregex 对内容做正则匹配,命中则从
<HOST>占位符中提取来源 IP;ignoreregex 命中的行被排除。 - 计数:同一 IP 在
findtime秒内累计命中maxretry次,即触发封禁(本文配置 maxretry=5,实测第 5 次失败后封禁)。 - 封禁:执行 action 的
actionban命令。本文实测 action 为iptables-multiport:创建名为f2b-sshd的链,在 INPUT 链按 multiport 端口 22 跳入该链,并在链首插入REJECT --reject-with icmp-port-unreachable。 - 到期解封:达到
bantime秒后自动执行actionunban从链中删除该 IP;也可用fail2ban-client unban <ip>手工提前解封。 - 持久化:封禁历史写入 sqlite3 数据库
/var/lib/fail2ban/fail2ban.sqlite3,重启服务后仍可查询(Total banned等计数不丢)。
2.2 /etc/fail2ban/ 目录树与各文件作用
本机实测目录结构(ls /etc/fail2ban/ 回显):
/etc/fail2ban/
├── action.d/ # 封禁动作定义目录
│ ├── iptables-multiport.conf # iptables 多端口动作(Ubuntu 实测生效动作)
│ ├── iptables-common.conf # iptables 动作公共参数(chain/blocktype 等)
│ ├── iptables-ipset-proto4.conf
│ ├── firewallcmd-ipset.conf # firewalld+ipset 动作(Ubuntu 亦随包提供)
│ ├── systemd.conf # 用 systemd 自身的"封禁"(如 ssh 源)
│ └── ... 共 40+ 个动作模板
├── fail2ban.conf # 主配置:socket/pidfile/dbfile/loglevel/logtarget
├── fail2ban.d/ # 主配置片段目录(默认空)
├── filter.d/ # 日志匹配正则目录
│ ├── sshd.conf # OpenSSH 匹配规则(本文核心,见 5.3)
│ ├── sshd-ddos.conf
│ ├── common.conf # 公共前缀(__prefix_line)定义
│ └── ... 共 80+ 个服务模板
├── jail.conf # jail 默认定义(包提供,不要直接修改)
├── jail.d/
│ └── defaults-debian.conf # Debian/Ubuntu 自带:默认启用 [sshd] jail
├── jail.local # 用户自定义 jail(优先级最高,本文创建)
├── paths-arch.conf # 发行版日志路径(Arch)
├── paths-common.conf # 通用日志路径宏(sshd_log、syslog_authpriv 等)
├── paths-debian.conf # Debian 系路径
└── paths-opensuse.conf # openSUSE 路径
关键文件说明:
| 文件 | 作用 |
|---|---|
fail2ban.conf | 守护进程级配置。socket = /var/run/fail2ban/fail2ban.sock(client 与 server 通信用)、pidfile = /var/run/fail2ban/fail2ban.pid、dbfile = /var/lib/fail2ban/fail2ban.sqlite3、loglevel = INFO、logtarget = /var/log/fail2ban.log、dbpurgeage = 1d(封禁历史保留 1 天,即 86400 秒)、dbmaxmatches = 10(每条 ticket 最多保存 10 条匹配日志) |
jail.conf | 所有 jail 的默认模板。默认 bantime = 10m、findtime = 10m、maxretry = 5、backend = auto、banaction = iptables-multiport、banaction_allports = iptables-allports;ignoreip 默认被注释(Ubuntu 实测第 92 行 #ignoreip = 127.0.0.1/8 ::1);末尾定义 sshd、dropbear、apache 等大量 jail 骨架 |
jail.d/*.conf | jail 片段,按文件名顺序在 jail.conf 之后加载;Ubuntu 的 defaults-debian.conf 内容为 [sshd] enabled = true(见 5.1) |
jail.local | 用户覆盖文件,优先级最高;本测试将 sshd jail 与 DEFAULT 参数写在这里 |
filter.d/sshd.conf | sshd 失败匹配正则,含 prefregex/cmnfailre/failregex/journalmatch 等(完整见 5.3) |
action.d/iptables-multiport.conf | iptables 的 actionstart/actionban/actionunban 具体命令(完整见 5.4) |
paths-common.conf | 宏定义:syslog_authpriv = /var/log/auth.log、sshd_log = %(syslog_authpriv)s、sshd_backend = %(default_backend)s、default_backend = %(default/backend)s(实测第 10、21、33、34 行) |
2.3 版本差异简述(0.11.x vs 1.0.x)
本文 Ubuntu 22.04 为 Fail2Ban v0.11.2(CentOS 8 为 v1.0.2),两版实测差异如下:
| 差异点 | v0.11.2(本文,Ubuntu) | v1.0.2(CentOS 8) |
|---|---|---|
| filter 结构 | 已引入 prefregex + cmnfailre + mode 参数 | 相同 |
kex_exchange_identification 匹配 | mdre-ddos 含 [Cc]lient sent invalid protocol identifier、[Cc]onnection closed by remote host | 增加 read: 前缀与 reset by peer 分支 |
Connection closed/reset 匹配 | mdre-normal-other 为 (Connection closed|Disconnected) | 增加 reset 分支 |
set logtarget 取值 | STDOUT, STDERR, SYSLOG or a file | 增加 SYSTEMD-JOURNAL |
裸正则 <HOST> 要求 | 无 <HOST> 报 ERROR: No failure-id group(实测一致) | 同左 |
help 命令 | 实测报 ERROR NOK: ('Invalid command',)(v0.11.2 特有怪癖,见 6.10) | 正常返回帮助 |
| 默认后端解析 | auto → systemd(journal 匹配) | 同左 |
其余命令语法、status 输出结构、jail 配置项两版完全兼容,下文命令详解对两版均适用。
三、在线安装方案
3.1 前置:安装并启动 OpenSSH 服务
Fail2Ban 防护的对象是 sshd,必须先保证 sshd 存在并监听 22 端口。若系统未安装,执行:
apt-get install -y openssh-server
真实回显(完整):
Reading package lists...
Building dependency tree...
Reading state information...
The following NEW packages will be installed:
openssh-server openssh-sftp-server
0 upgraded, 2 newly installed, 0 to remove and 7 not upgraded.
Need to get 495 kB of archives.
After this operation, 1707 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 openssh-sftp-server amd64 1:8.9p1-3ubuntu0.17 [38.0 kB]
Get:2 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 openssh-server amd64 1:8.9p1-3ubuntu0.17 [457 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 495 kB in 1s (321 kB/s)
Selecting previously unselected package openssh-sftp-server.
(Reading database ... 7037 files and directories currently installed.)
Preparing to unpack .../openssh-sftp-server_1%3a8.9p1-3ubuntu0.17_amd64.deb ...
Unpacking openssh-sftp-server (1:8.9p1-3ubuntu0.17) ...
Selecting previously unselected package openssh-server.
Preparing to unpack .../openssh-server_1%3a8.9p1-3ubuntu0.17_amd64.deb ...
Unpacking openssh-server (1:8.9p1-3ubuntu0.17) ...
Setting up openssh-sftp-server (1:8.9p1-3ubuntu0.17) ...
Setting up openssh-server (1:8.9p1-3ubuntu0.17) ...
Created symlink /etc/systemd/system/multi-user.target.wants/ssh.service → /lib/systemd/system/ssh.service.
/usr/sbin/policy-rc.d returned 101, not running 'start rescue-ssh.target ssh.service'
/usr/sbin/policy-rc.d returned 101, not running 'start ssh.socket'
Processing triggers for ufw (0.36.1-4ubuntu0.1) ...
Processing triggers for libc-bin (2.35-0ubuntu3.14) ...
注意 Ubuntu 上的两个细节:① 服务单元名是 ssh.service(不是 sshd.service),安装时自动生成
ssh.service的 enable 软链;② 最小化环境因 policy-rc.d 不会自动启动服务,需手工systemctl enable --now ssh。
启动 sshd 并设置开机自启(每条命令独立执行):
systemctl enable --now ssh
真实回显:
Synchronizing state of ssh.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable ssh
systemctl status ssh --no-pager
● ssh.service - OpenBSD Secure Shell server
Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2026-10-06 05:28:32 UTC; 8h ago
Docs: man:sshd(8)
man:sshd_config(5)
Main PID: 191459 (sshd)
Tasks: 1 (limit: 5753)
Memory: 2.3M
CPU: 512ms
CGroup: /system.slice/.../ssh.service
└─191459 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
测试环境为模拟错误密码登录,需要放开密码认证。实测 sshd 生效配置(
sshd -T):port 22、permitrootlogin without-password、passwordauthentication yes。生产环境强烈建议关闭密码登录、仅用密钥认证(见注意事项 ⚠️4)。
客户端工具(ssh 命令)通常随 openssh-client 已装好;模拟攻击用的 sshpass 见第七章实战演练。
3.2 安装 fail2ban 主程序
Ubuntu 22.04 的 fail2ban 由 universe 源提供(版本 0.11.2-6),安装命令:
apt-get install -y fail2ban
真实回显(完整,含依赖解析):
Reading package lists...
Building dependency tree...
Reading state information...
The following additional packages will be installed:
python3-pyinotify python3-systemd whois
Suggested packages:
mailx system-log-daemon monit sqlite3 python-pyinotify-doc
The following NEW packages will be installed:
fail2ban python3-pyinotify python3-systemd whois
0 upgraded, 4 newly installed, 0 to remove and 10 not upgraded.
Need to get 512 kB of archives.
After this operation, 2684 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 fail2ban all 0.11.2-6 [394 kB]
Get:2 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-pyinotify all 0.9.6-1.3 [24.8 kB]
Get:3 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-systemd amd64 234-3ubuntu2 [39.6 kB]
Get:4 http://archive.ubuntu.com/ubuntu jammy/main amd64 whois amd64 5.5.13 [53.4 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 512 kB in 2s (283 kB/s)
Selecting previously unselected package fail2ban.
(Reading database ... 6971 files and directories currently installed.)
Preparing to unpack .../fail2ban_0.11.2-6_all.deb ...
Unpacking fail2ban (0.11.2-6) ...
Selecting previously unselected package python3-pyinotify.
Preparing to unpack .../python3-pyinotify_0.9.6-1.3_all.deb ...
Unpacking python3-pyinotify (0.9.6-1.3) ...
Selecting previously unselected package python3-systemd.
Preparing to unpack .../python3-systemd_234-3ubuntu2_amd64.deb ...
Unpacking python3-systemd (234-3ubuntu2) ...
Selecting previously unselected package whois.
Preparing to unpack .../whois_5.5.13_amd64.deb ...
Unpacking whois (5.5.13) ...
Setting up whois (5.5.13) ...
Setting up fail2ban (0.11.2-6) ...
invoke-rc.d: policy-rc.d denied execution of start.
Setting up python3-pyinotify (0.9.6-1.3) ...
Setting up python3-systemd (234-3ubuntu2) ...
关键点:Ubuntu 的 fail2ban 是 all(架构无关)Python 包,安装时自动带入 3 个依赖:
python3-pyinotify(文件变化监听,文件 backend 用)、python3-systemd(journal 读取,systemd backend 用)、whois(whois 查询动作用)。invoke-rc.d: policy-rc.d denied execution of start是最小化环境的 policy 行为,表示安装时未自动启动服务,属正常。
补充:
whois虽是自动安装的依赖,但它同时是fail2ban-client的 whois 查询 action 所需;python3-systemd是 Ubuntu 上 backend = systemd 的硬前提,卸载 fail2ban 时这三个包会成为"不再需要的自动安装包"(见第十一章)。
3.3 启动并验证
systemctl enable --now fail2ban
真实回显:
Synchronizing state of fail2ban.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable fail2ban
Created symlink /etc/systemd/system/multi-user.target.wants/fail2ban.service → /lib/systemd/system/fail2ban.service.
systemctl status fail2ban --no-pager
● fail2ban.service - Fail2Ban Service
Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2026-10-06 04:59:34 UTC; 122ms ago
Docs: man:fail2ban(1)
Main PID: 1611 (fail2ban-server)
Tasks: 4 (limit: 5753)
Memory: 10.7M
CPU: 106ms
CGroup: /system.slice/.../fail2ban.service
└─1611 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
回显中
Fail2Ban Service由 python3 解释执行/usr/bin/fail2ban-server -xf start(-x 强制清理旧 socket、-f 前台运行由 systemd 托管)。vendor preset: enabled说明 Ubuntu 包默认开机自启。
首次安装后立刻执行 fail2ban-client version 可能报 socket 错误(服务刚被拉起、socket 尚未就绪,属正常现象):
2026-10-06 04:59:04,790 fail2ban [1510]: ERROR Failed to access socket path: /var/run/fail2ban/fail2ban.sock. Is fail2ban running?
等几秒后即可正常:
fail2ban-client version
0.11.2
fail2ban-client status
Status
|- Number of jail: 1
`- Jail list: sshd
Ubuntu 的
jail.d/defaults-debian.conf默认启用了 [sshd] jail(内容仅[sshd] enabled = true),所以装完即有一个 sshd jail 在运行,这是 Ubuntu/Debian 与 CentOS(需手工写 jail.local 启用)的最大差异。
配置校验(不启动服务只检查语法):
fail2ban-client -t
OK: configuration test is successful
3.4 安装后 deb 包清单
dpkg -l fail2ban
+++-==============-============-============-===================================================
ii fail2ban 0.11.2-6 all ban hosts that cause multiple authentication errors
四、离线安装方案
适用于目标机器无法访问外网(无 universe 源/无互联网)的场景:在一台联网的 Ubuntu 机器上用 apt-get download 下载全部 deb 及依赖,拷贝到目标机后本地安装。
4.1 查看 fail2ban 的依赖
apt-cache depends fail2ban
真实回显(完整):
fail2ban
Depends: <python3:any>
python3
Depends: lsb-base
|Recommends: nftables
Recommends: iptables
Recommends: whois
Recommends: python3-pyinotify
Recommends: python3-systemd
Suggests: <mailx>
bsd-mailx
mailutils
Suggests: <system-log-daemon>
syslog-ng-core
busybox-syslogd
inetutils-syslogd
socklog-run
rsyslog
Suggests: monit
Suggests: sqlite3
解读:fail2ban 的硬依赖只有
python3(含python3:any)与lsb-base;python3-pyinotify、python3-systemd、whois、nftables、iptables都是 Recommends(推荐)级,在线安装时会一并装上(见 3.2 回显);rsyslog等只是 Suggests。离线安装至少应携带:fail2ban + python3-pyinotify + python3-systemd + whois + nftables/iptables(按目标机已有情况取舍)。
4.2 在有网机器上下载 deb 包
在联网机器上执行:
cd /tmp
apt-get download fail2ban
真实回显:
Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 fail2ban all 0.11.2-6 [394 kB]
Fetched 394 kB in 2s (253 kB/s)
同时拉取依赖(在线安装时的 3 个自动依赖 + 可选防火墙工具):
cd /tmp && apt-get download fail2ban python3-pyinotify python3-systemd whois
真实回显(摘录):
Get:1 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-pyinotify all 0.9.6-1.3 [24.8 kB]
Get:2 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-systemd amd64 234-3ubuntu2 [39.6 kB]
Get:3 http://archive.ubuntu.com/ubuntu jammy/main amd64 whois amd64 5.5.13 [53.4 kB]
Fetched 118 kB in 2s (74.1 kB/s)
下载完成后清单(真实 ls -lh):
-rw-r--r-- 1 root root 386K Mar 22 2022 /tmp/fail2ban_0.11.2-6_all.deb
-rw-r--r-- 1 root root 25K Nov 2 2020 /tmp/python3-pyinotify_0.9.6-1.3_all.deb
-rw-r--r-- 1 root root 39K Mar 17 2022 /tmp/python3-systemd_234-3ubuntu2_amd64.deb
-rw-r--r-- 1 root root 53K Apr 8 2022 /tmp/whois_5.5.13_amd64.deb
4.3 目标机离线安装
把下载好的 deb 拷贝到目标机后,推荐用 dpkg -i 按依赖顺序安装(先依赖、后主包,否则报依赖缺失):
dpkg -i /tmp/python3-pyinotify_0.9.6-1.3_all.deb
dpkg -i /tmp/python3-systemd_234-3ubuntu2_amd64.deb
dpkg -i /tmp/whois_5.5.13_amd64.deb
dpkg -i /tmp/fail2ban_0.11.2-6_all.deb
若目标机完全断网且缺 python3/lsb-base,需再从基础源下载
python3、lsb-base及 python3 自身的依赖链(python3-minimal、libpython3.10-stdlib 等),可先在有网机执行apt-cache depends fail2ban递归展开后逐一apt-get download。
⚠️ 未实测项:本测试环境 fail2ban 已在线安装,
dpkg -i在全新离线机器上的完整安装回显未采集。预期输出与 3.2 节在线安装结构一致(Selecting previously unselected → Unpacking → Setting up → 依赖顺序错误时输出dpkg: dependency problems prevent configuration of fail2ban并中止)。请以实际环境为准。
安装完成后同样执行(与在线安装一致):
systemctl enable --now fail2ban
fail2ban-client -t
fail2ban-client version
预期输出:OK: configuration test is successful 与 0.11.2(可对照 3.3 节)。
五、配置详解
5.1 jail.conf 与 jail.local 的关系与推荐写法
jail.conf:安装包自带的默认模板,升级包时可能被覆盖,永远不要直接修改。jail.local:用户覆盖文件,优先级最高,任何配置写在里面即可覆盖 jail.conf 的同名项。jail.d/*.conf:片段目录,按文件名排序在 jail.conf 之后加载(Ubuntu 的defaults-debian.conf默认启用 sshd jail)。
配置文件加载顺序(后加载者覆盖先加载者):
jail.conf < jail.d/*.conf < jail.local
Ubuntu 自带 jail.d/defaults-debian.conf 完整内容(实测):
[sshd]
enabled = true
推荐写法:所有自定义内容写入 /etc/fail2ban/jail.local。 本测试完整内容如下(真实文件):
[DEFAULT]
bantime = 10m
findtime = 10m
maxretry = 5
backend = systemd
ignoreself = false
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
参数含义与默认值总表(默认值取自 /etc/fail2ban/jail.conf 与 paths-common.conf 实测):
| 参数 | 本配置值 | 默认值(jail.conf) | 含义 |
|---|---|---|---|
enabled | true | false(Debian 包在 jail.d 默认开 sshd) | 是否启用该 jail |
maxretry | 5 | 5 | findtime 窗口内失败多少次触发封禁 |
findtime | 10m(600 秒) | 10m | 统计失败的时间回溯窗口 |
bantime | 10m(600 秒) | 10m | 封禁时长;-1 表示永久封禁 |
ignoreip | (未设) | 注释状态(#ignoreip = 127.0.0.1/8 ::1) | 永不封禁的 IP/网段白名单 |
ignoreself | false | true(jail.conf 注释为 true 语义) | 是否忽略本机自身 IP(本测试置 false 以便模拟 127.0.0.1 封禁) |
backend | systemd | auto | 日志来源:systemd=journald、auto=自动探测、pyinotify/polling=文件轮询 |
banaction | (未设,继承 iptables-multiport) | iptables-multiport | 封禁使用的 action;banaction_allports = iptables-allports |
port | ssh | ssh(各 jail 可覆盖) | 封禁针对的端口(action 生成规则时使用) |
logpath | %(sshd_log)s | %(sshd_log)s | 日志路径宏;sshd_log = %(syslog_authpriv)s = /var/log/auth.log(paths-common.conf 实测第 21、33 行) |
filter | (未设,默认 sshd) | %(name)s[mode=%(mode)s] | 使用的 filter.d 正则文件(默认按 jail 名解析) |
usedns | (未设) | warn | 日志中出现主机名时是否做 DNS 解析(warn=解析但只告警) |
jail.conf 中 [sshd] 段完整内容(实测):
[sshd]
# To use more aggressive sshd modes set filter parameter "mode" in jail.local:
# normal (default), ddos, extra or aggressive (combines all).
# See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details.
#mode = normal
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
注意:jail.conf 默认
maxretry = 5,本测试沿用默认 5,因此第 5 次失败即封禁(见实战篇)。jail.conf 中对应段落真实内容(实测第 101~108 行):
# "bantime" is the number of seconds that a host is banned.
bantime = 10m
# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime = 10m
# "maxretry" is the number of failures before a host get banned.
maxretry = 5
paths-common.conf 关键宏(实测):
default_backend = %(default/backend)s
syslog_authpriv = /var/log/auth.log
syslog_backend = %(default_backend)s
sshd_log = %(syslog_authpriv)s
sshd_backend = %(default_backend)s
5.2 backend 选择:systemd vs auto(Ubuntu 实测)
backend 决定 Fail2Ban 从哪里读取日志,jail.conf 第 132 行默认 backend = auto。Ubuntu 的 fail2ban.conf 注释对可选值的官方解释(实测原文摘录):
pyinotify:需要 python3-pyinotify(文件变化监视器);未安装则回落 auto。gamin:需要 Gamin;未安装则回落 auto。polling:轮询算法,不依赖外部库。systemd:用 python3-systemd 库直接访问 systemd journal。指定该 backend 时 logpath 无效,改用 filter 里的journalmatch。auto:按 pyinotify → gamin → polling 顺序尝试。
Ubuntu 上的实测结论:
auto在 Ubuntu 上默认解析为 systemd(paths-common.conf:default_backend = %(default/backend)s,而 auto 探测时 systemd 可用则优先)。因此即使不写backend = systemd,fail2ban-client status sshd同样显示Journal matches。- 显式
backend = systemd时,get sshd logpath返回No file is currently monitored(实测),日志改从 journal 的journalmatch(_SYSTEMD_UNIT=sshd.service + _COMM=sshd)过滤。 - 若改用文件 backend(
backend = pyinotify),则必须保证logpath指向的文件真实存在且由 rsyslog 实时写入;默认 Ubuntu 22.04 服务器 rsyslog 常驻,/var/log/auth.log正常追加。最小化环境无 rsyslog 时,文件 backend 会因找不到文件而无法匹配(见 FAQ-1/FAQ-2)。
判断当前 backend 类型的命令(实测):
fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
fail2ban-client get sshd logpath
No file is currently monitored
出现 Journal matches 且 logpath 报无文件 → systemd backend;出现 File list: /var/log/auth.log → 文件 backend。
5.3 filter.d/sshd.conf 完整内容与正则说明
本机(v0.11.2)完整内容如下:
# Fail2Ban filter for openssh
#
# If you want to protect OpenSSH from being bruteforced by password
# authentication then get public key authentication working before disabling
# PasswordAuthentication in sshd_config.
#
#
# "Connection from <HOST> port \d+" requires LogLevel VERBOSE in sshd_config
#
[INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf
[DEFAULT]
_daemon = sshd
# optional prefix (logged from several ssh versions) like "error: ", "error: PAM: " or "fatal: "
__pref = (?:(?:error|fatal): (?:PAM: )?)?
# optional suffix (logged from several ssh versions) like " [preauth]"
#__suff = (?: port \d+)?(?: \[preauth\])?\s*
__suff = (?: (?:port \d+|on \S+|\[preauth\])){0,3}\s*
__on_port_opt = (?: (?:port \d+|on \S+)){0,2}
# close by authenticating user:
__authng_user = (?: (?:invalid|authenticating) user <F-USER>\S+|.*?</F-USER>)?
# for all possible (also future) forms of "no matching (cipher|mac|MAC|compression method|key exchange method|host key type) found",
# see ssherr.c for all possible SSH_ERR_..._ALG_MATCH errors.
__alg_match = (?:(?:\w+ (?!found\b)){0,2}\w+)
# PAM authentication mechanism, can be overridden, e. g. `filter = sshd[__pam_auth='pam_ldap']`:
__pam_auth = pam_[a-z]+
[Definition]
prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID>%(__pref)s<F-CONTENT>.+</F-CONTENT>$
cmnfailre = ^[aA]uthentication (?:failure|error|failed) for <F-USER>.*</F-USER> from <HOST>( via \S+)?%(__suff)s$
^User not known to the underlying authentication module for <F-USER>.*</F-USER> from <HOST>%(__suff)s$
<cmnfailre-failed-pub-<publickey>>
^Failed <cmnfailed> for (?P<cond_inv>invalid user )?<F-USER>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
^<F-USER>ROOT</F-USER> LOGIN REFUSED FROM <HOST>
^[iI](?:llegal|nvalid) user <F-USER>.*?</F-USER> from <HOST>%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because not listed in AllowUsers%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because listed in DenyUsers%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because not in any group%(__suff)s$
^refused connect from \S+ \(<HOST>\)
^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*3: .*: Auth fail%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because a group is listed in DenyGroups%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because none of user's groups are listed in AllowGroups%(__suff)s$
^<F-NOFAIL>%(__pam_auth)s\(sshd:auth\):\s+authentication failure;</F-NOFAIL>(?:\s+(?:(?:logname|e?uid|tty)=\S*)){0,4}\s+ruser=<F-ALT_USER>\S*</F-ALT_USER>\s+rhost=<HOST>(?:\s+user=<F-USER>\S*</F-USER>)?%(__suff)s$
^maximum authentication attempts exceeded for <F-USER>.*</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> not allowed because account is locked%(__suff)s
^<F-MLFFORGET>Disconnecting</F-MLFFORGET>(?: from)?(?: (?:invalid|authenticating)) user <F-USER>\S+</F-USER> <HOST>%(__on_port_opt)s:\s*Change of username or service not allowed:\s*.*\[preauth\]\s*$
^Disconnecting: Too many authentication failures(?: for <F-USER>\S+|.*?</F-USER>)?%(__suff)s$
^<F-NOFAIL>Received <F-MLFFORGET>disconnect</F-MLFFORGET></F-NOFAIL> from <HOST>%(__on_port_opt)s:\s*11:
<mdre-<mode>-other>
^<F-MLFFORGET><F-MLFGAINED>Accepted \w+</F-MLFGAINED></F-MLFFORGET> for <F-USER>\S+</F-USER> from <HOST>(?:\s|$)
cmnfailed-any = \S+
cmnfailed-ignore = \b(?!publickey)\S+
cmnfailed-invalid = <cmnfailed-ignore>
cmnfailed-nofail = (?:<F-NOFAIL>publickey</F-NOFAIL>|\S+)
cmnfailed = <cmnfailed-<publickey>>
mdre-normal =
# used to differentiate "connection closed" with and without `[preauth]` (fail/nofail cases in ddos mode)
mdre-normal-other = ^<F-NOFAIL><F-MLFFORGET>(Connection closed|Disconnected)</F-MLFFORGET></F-NOFAIL> (?:by|from)%(__authng_user)s <HOST>(?:%(__suff)s|\s*)$
mdre-ddos = ^Did not receive identification string from <HOST>
^kex_exchange_identification: (?:[Cc]lient sent invalid protocol identifier|[Cc]onnection closed by remote host)
^Bad protocol version identification '.*' from <HOST>
^<F-NOFAIL>SSH: Server;Ltype:</F-NOFAIL> (?:Authname|Version|Kex);Remote: <HOST>-\d+;[A-Z]\w+:
^Read from socket failed: Connection <F-MLFFORGET>reset</F-MLFFORGET> by peer
# same as mdre-normal-other, but as failure (without <F-NOFAIL>) and [preauth] only:
mdre-ddos-other = ^<F-MLFFORGET>(Connection (?:closed|reset)|Disconnected)</F-MLFFORGET> (?:by|from)%(__authng_user)s <HOST>%(__on_port_opt)s\s+\[preauth\]\s*$
mdre-extra = ^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*14: No(?: supported)? authentication methods available
^Unable to negotiate with <HOST>%(__on_port_opt)s: no matching <__alg_match> found.
^Unable to negotiate a <__alg_match>
^no matching <__alg_match> found:
# part of mdre-ddos-other, but user name is supplied (invalid/authenticating) on [preauth] phase only:
mdre-extra-other = ^<F-MLFFORGET>Disconnected</F-MLFFORGET>(?: from)?(?: (?:invalid|authenticating)) user <F-USER>\S+|.*?</F-USER> <HOST>%(__on_port_opt)s \[preauth\]\s*$
mdre-aggressive = %(mdre-ddos)s
%(mdre-extra)s
# mdre-extra-other is fully included within mdre-ddos-other:
mdre-aggressive-other = %(mdre-ddos-other)s
# Parameter "publickey": nofail (default), invalid, any, ignore
publickey = nofail
# consider failed publickey for invalid users only:
cmnfailre-failed-pub-invalid = ^Failed publickey for invalid user <F-USER>(?P<cond_user>\S+)|(?:(?! from ).)*?</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
# consider failed publickey for valid users too (don't need RE, see cmnfailed):
cmnfailre-failed-pub-any =
# same as invalid, but consider failed publickey for valid users too, just as no failure (helper to get IP and user-name only, see cmnfailed):
cmnfailre-failed-pub-nofail = <cmnfailre-failed-pub-invalid>
# don't consider failed publickey as failures (don't need RE, see cmnfailed):
cmnfailre-failed-pub-ignore =
cfooterre = ^<F-NOFAIL>Connection from</F-NOFAIL> <HOST>
failregex = %(cmnfailre)s
<mdre-<mode>>
%(cfooterre)s
# Parameter "mode": normal (default), ddos, extra or aggressive (combines all)
# Usage example (for jail.local):
# [sshd]
# mode = extra
# # or another jail (rewrite filter parameters of jail):
# [sshd-aggressive]
# filter = sshd[mode=aggressive]
#
mode = normal
#filter = sshd[mode=aggressive]
ignoreregex =
maxlines = 1
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd
各关键部分逐条解释:
| 部分 | 内容 | 作用 |
|---|---|---|
before = common.conf | 引入公共前缀定义 | common.conf 中定义了 __prefix_line(时间/主机/进程前缀的匹配模板),供 prefregex 复用 |
prefregex | ^<F-MLFID>%(__prefix_line)s</F-MLFID>%(__pref)s<F-CONTENT>.+</F-CONTENT>$ | 把日志行拆成"前缀(MLFID 段)"与"内容(CONTENT 段)"两段;__pref 吞掉 error:/fatal:/PAM: 前缀 |
cmnfailre 第 1 条 | ^[aA]uthentication (?:failure|error|failed) for ... from <HOST> | 匹配"authentication failure/error/failed for user from IP"(PAM 风格日志) |
cmnfailre 第 4 条 | ^Failed <cmnfailed> for (?P<cond_inv>invalid user )?... from <HOST>... | 本文实战命中的核心规则:匹配 Failed password for root from 127.0.0.1 port 36650 ssh2;<cmnfailed> 按 publickey 参数展开(默认 nofail=publickey 不计失败);(?P<cond_inv>invalid user )? 兼容 invalid user 行 |
cmnfailre 第 6 条 | ^[iI](?:llegal|nvalid) user ... from <HOST> | 匹配不存在用户名的尝试(Illegal user/Invalid user) |
cmnfailre 第 8~11 条 | AllowUsers/DenyUsers/组限制 | 匹配 sshd_config 白名单/黑名单拒绝的日志 |
cmnfailre 第 14 条 | pam_unix(sshd:auth): authentication failure; ... rhost=<HOST> | 匹配 PAM 模块直接输出的认证失败(journal 常见形态) |
cmnfailre 第 16、18 条 | maximum authentication attempts exceeded / Too many authentication failures | 匹配"尝试次数过多"被 sshd 主动断开的日志 |
mdre-normal | 空 | normal 模式无额外规则 |
mdre-ddos | Did not receive identification / kex_exchange_identification / Bad protocol version 等 | ddos 模式:扫描/握手失败类(未收到客户端标识、协议版本非法等) |
mdre-extra | No supported authentication methods / no matching algo found | extra 模式:算法协商失败类 |
mdre-aggressive | %(mdre-ddos)s + %(mdre-extra)s | aggressive 模式 = ddos + extra 全部 |
failregex | %(cmnfailre)s + <mdre-<mode>> + %(cfooterre)s | 实际生效的正则集合 = 公共失败规则 + 当前 mode 规则 + 连接来源记录 |
mode = normal | 默认模式 | 在 jail.local 用 filter = sshd[mode=aggressive] 可切换 |
journalmatch | _SYSTEMD_UNIT=sshd.service + _COMM=sshd | systemd backend 专用:从 journal 中过滤 sshd 服务、进程名 sshd 的条目(status sshd 中 Journal matches 即此项) |
maxlines = 1 | 单行匹配 | 每条日志只按 1 行做正则 |
实战中实测命中:
Failed password for root from 127.0.0.1 port 36650 ssh2这类行由 prefregex 剥离前缀后,被 cmnfailre 第 4 条命中,<HOST>提取出127.0.0.1。
5.4 action.d 常用 action 说明
Ubuntu 默认 banaction = iptables-multiport(jail.conf 第 208 行实测)。本机 action.d/iptables-multiport.conf 完整内容:
# Fail2Ban configuration file
#
# Author: Cyril Jaquier
# Modified by Yaroslav Halchenko for multiport banning
#
[INCLUDES]
before = iptables-common.conf
[Definition]
# Option: actionstart
# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false).
# Values: CMD
#
actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
# Option: actionstop
# Notes.: command executed at the stop of jail (or at the end of Fail2Ban)
# Values: CMD
#
actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
<actionflush>
<iptables> -X f2b-<name>
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
[Init]
其中 actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype> 即封禁时执行的命令,<ip>/<name>/<blocktype> 等标签由 fail2ban 填充。实际填充值来自 action.d/iptables-common.conf 的 [Init] 段(实测关键参数):
chain = INPUT
name = default
port = ssh
protocol = tcp
blocktype = REJECT --reject-with icmp-port-unreachable
returntype = RETURN
iptables = iptables <lockingopt>
lockingopt = -w
把标签展开后,本文实战封禁 127.0.0.1 时实际执行的命令等价于:
iptables -N f2b-sshd
iptables -A f2b-sshd -j RETURN
iptables -I INPUT -p tcp -m multiport --dports 22 -j f2b-sshd
iptables -I f2b-sshd 1 -s 127.0.0.1/32 -j REJECT --reject-with icmp-port-unreachable
可用 fail2ban-client get sshd action iptables-multiport actionban 直接查看(实测回显):
<iptables> -I f2b-sshd 1 -s <ip> -j <blocktype>
Ubuntu 自带的其它常用 action(ls /etc/fail2ban/action.d/ 实测摘录):
| action 文件 | 用途 | 适用场景 |
|---|---|---|
iptables-multiport.conf | iptables 多端口链封禁(默认) | Ubuntu/Debian 通用,无 firewalld |
iptables-allports.conf | 封禁来源 IP 的所有端口 | 攻击者全端口扫描时 |
iptables-ipset-proto4.conf | ipset(hash:ip)+ iptables 引用 | 大量 IP 时性能更好 |
firewallcmd-ipset.conf / firewallcmd-rich-rules.conf | firewalld + ipset / rich rule | 装了 firewalld 的机器(CentOS 系习惯) |
ufw.conf | ufw 规则封禁 | 用 ufw 管理防火墙的 Ubuntu |
systemd.conf | 通过 systemd 的 IP 访问控制(sshd 源黑名单) | 不想动 iptables 的场景 |
sendmail.conf / mail-whois.conf | 封禁时发送邮件通知 | 需配置 sendmail 或 mailutils |
cloudflare.conf | 调用 Cloudflare API 封禁 | 网站被 CF 代理时 |
若改用 ufw 封禁,jail.local 的 [DEFAULT] 写
banaction = ufw,并保证 ufw 已启用(ufw enable);ufw 动作实测需另行验证,本文未采集回显。
六、命令详解
语法以 v0.11.2 实测
fail2ban-client -h为准;全部命令均有真实输入输出。
6.1 基础:version / -V / -h / ping / -t
fail2ban-client version —— 返回服务端版本号(经 socket 与运行中的 server 通信):
fail2ban-client version
0.11.2
注意:server 未运行时该命令报错(实测):
ERROR Failed to access socket path: /var/run/fail2ban/fail2ban.sock. Is fail2ban running?
fail2ban-client -V —— 机器可读短格式版本号(不需 server 运行):
fail2ban-client -V
0.11.2
fail2ban-client ping —— 探测 server 是否存活:
fail2ban-client ping
Server replied: pong
fail2ban-client -t —— 仅测试配置语法,不启动服务:
fail2ban-client -t
OK: configuration test is successful
fail2ban-client -h —— 完整帮助(v0.11.2 实测全文,参数含义逐条见 6.1~6.10):
Usage: fail2ban-client [OPTIONS] <COMMAND>
Fail2Ban v0.11.2 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
Options:
-c, --conf <DIR> configuration directory
-s, --socket <FILE> socket path
-p, --pidfile <FILE> pidfile path
--pname <NAME> name of the process (main thread) to identify instance (default fail2ban-server)
--loglevel <LEVEL> logging level
--logtarget <TARGET> logging target, use file-name or stdout, stderr, syslog or sysout.
--syslogsocket auto|<FILE>
-d dump configuration. For debugging
--dp, --dump-pretty dump the configuration using more human readable representation
-t, --test test configuration (can be also specified with start parameters)
-i interactive mode
-v increase verbosity
-q decrease verbosity
-x force execution of the server (remove socket file)
-b start server in background (default)
-f start server in foreground
--async start server in async mode (for internal usage only, don't read configuration)
--timeout timeout to wait for the server (for internal usage only, don't read configuration)
--str2sec <STRING> convert time abbreviation format to seconds
-h, --help display this help message
-V, --version print the version (-V returns machine-readable short format)
Command:
BASIC
start starts the server and the jails
restart restarts the server
restart [--unban] [--if-exists] <JAIL> restarts the jail <JAIL> (alias
for 'reload --restart ... <JAIL>')
reload [--restart] [--unban] [--all] reloads the configuration without
restarting of the server, the
option '--restart' activates
completely restarting of affected
jails, thereby can unban IP
addresses (if option '--unban'
specified)
reload [--restart] [--unban] [--if-exists] <JAIL>
reloads the jail <JAIL>, or
restarts it (if option '--restart'
specified)
stop stops all jails and terminate the
server
unban --all unbans all IP addresses (in all
jails and database)
unban <IP> ... <IP> unbans <IP> (in all jails and
database)
banned return jails with banned IPs as
dictionary
banned <IP> ... <IP>] return list(s) of jails where
given IP(s) are banned
status gets the current status of the
server
ping tests if the server is alive
echo for internal usage, returns back
and outputs a given string
help return this output
version return the server version
LOGGING
set loglevel <LEVEL> sets logging level to <LEVEL>.
Levels: CRITICAL, ERROR, WARNING,
NOTICE, INFO, DEBUG, TRACEDEBUG,
HEAVYDEBUG or corresponding
numeric value (50-5)
get loglevel gets the logging level
set logtarget <TARGET> sets logging target to <TARGET>.
Can be STDOUT, STDERR, SYSLOG or a
file
get logtarget gets logging target
set syslogsocket auto|<SOCKET> sets the syslog socket path to
auto or <SOCKET>. Only used if
logtarget is SYSLOG
get syslogsocket gets syslog socket path
flushlogs flushes the logtarget if a file
and reopens it. For log rotation.
DATABASE
set dbfile <FILE> set the location of fail2ban
persistent datastore. Set to
"None" to disable
get dbfile get the location of fail2ban
persistent datastore
set dbmaxmatches <INT> sets the max number of matches
stored in database per ticket
get dbmaxmatches gets the max number of matches
stored in database per ticket
set dbpurgeage <SECONDS> sets the max age in <SECONDS> that
history of bans will be kept
get dbpurgeage gets the max age in seconds that
history of bans will be kept
JAIL CONTROL
add <JAIL> <BACKEND> creates <JAIL> using <BACKEND>
start <JAIL> starts the jail <JAIL>
stop <JAIL> stops the jail <JAIL>. The jail is
removed
status <JAIL> [FLAVOR] gets the current status of <JAIL>,
with optional flavor or extended
info
JAIL CONFIGURATION
set <JAIL> idle on|off sets the idle state of <JAIL>
set <JAIL> ignoreself true|false allows the ignoring of own IP
addresses
set <JAIL> addignoreip <IP> adds <IP> to the ignore list of
<JAIL>
set <JAIL> delignoreip <IP> removes <IP> from the ignore list
of <JAIL>
set <JAIL> ignorecommand <VALUE> sets ignorecommand of <JAIL>
set <JAIL> ignorecache <VALUE> sets ignorecache of <JAIL>
set <JAIL> addlogpath <FILE> ['tail'] adds <FILE> to the monitoring list
of <JAIL>, optionally starting at
the 'tail' of the file (default
'head').
set <JAIL> dellogpath <FILE> removes <FILE> from the monitoring
list of <JAIL>
set <JAIL> logencoding <ENCODING> sets the <ENCODING> of the log
files for <JAIL>
set <JAIL> addjournalmatch <MATCH> adds <MATCH> to the journal filter
of <JAIL>
set <JAIL> deljournalmatch <MATCH> removes <MATCH> from the journal
filter of <JAIL>
set <JAIL> addfailregex <REGEX> adds the regular expression
<REGEX> which must match failures
for <JAIL>
set <JAIL> delfailregex <INDEX> removes the regular expression at
<INDEX> for failregex
set <JAIL> addignoreregex <REGEX> adds the regular expression
<REGEX> which should match pattern
to exclude for <JAIL>
set <JAIL> delignoreregex <INDEX> removes the regular expression at
<INDEX> for ignoreregex
set <JAIL> findtime <TIME> sets the number of seconds <TIME>
for which the filter will look
back for <JAIL>
set <JAIL> bantime <TIME> sets the number of seconds <TIME>
a host will be banned for <JAIL>
set <JAIL> datepattern <PATTERN> sets the <PATTERN> used to match
date/times for <JAIL>
set <JAIL> usedns <VALUE> sets the usedns mode for <JAIL>
set <JAIL> attempt <IP> [<failure1> ... <failureN>]
manually notify about <IP> failure
set <JAIL> banip <IP> ... <IP> manually Ban <IP> for <JAIL>
set <JAIL> unbanip [--report-absent] <IP> ... <IP>
manually Unban <IP> in <JAIL>
set <JAIL> maxretry <RETRY> sets the number of failures
<RETRY> before banning the host
for <JAIL>
set <JAIL> maxmatches <INT> sets the max number of matches
stored in memory per ticket in
<JAIL>
set <JAIL> maxlines <LINES> sets the number of <LINES> to
buffer for regex search for <JAIL>
set <JAIL> addaction <ACT>[ <PYTHONFILE> <JSONKWARGS>]
adds a new action named <ACT> for
<JAIL>. Optionally for a Python
based action, a <PYTHONFILE> and
<JSONKWARGS> can be specified,
else will be a Command Action
set <JAIL> delaction <ACT> removes the action <ACT> from
<JAIL>
COMMAND ACTION CONFIGURATION
set <JAIL> action <ACT> actionstart <CMD>
sets the start command <CMD> of
the action <ACT> for <JAIL>
set <JAIL> action <ACT> actionstop <CMD> sets the stop command <CMD> of the
action <ACT> for <JAIL>
set <JAIL> action <ACT> actioncheck <CMD>
sets the check command <CMD> of
the action <ACT> for <JAIL>
set <JAIL> action <ACT> actionban <CMD> sets the ban command <CMD> of the
action <ACT> for <JAIL>
set <JAIL> action <ACT> actionunban <CMD>
sets the unban command <CMD> of
the action <ACT> for <JAIL>
set <JAIL> action <ACT> timeout <TIMEOUT>
sets <TIMEOUT> as the command
timeout in seconds for the action
<ACT> for <JAIL>
GENERAL ACTION CONFIGURATION
set <JAIL> action <ACT> <PROPERTY> <VALUE>
sets the <VALUE> of <PROPERTY> for
the action <ACT> for <JAIL>
set <JAIL> action <ACT> <METHOD>[ <JSONKWARGS>]
calls the <METHOD> with
<JSONKWARGS> for the action <ACT>
for <JAIL>
JAIL INFORMATION
get <JAIL> banned return banned IPs of <JAIL>
get <JAIL> banned <IP> ... <IP>] return 1 if IP is banned in <JAIL>
otherwise 0, or a list of 1/0 for
multiple IPs
get <JAIL> logpath gets the list of the monitored
files for <JAIL>
get <JAIL> logencoding gets the encoding of the log files
for <JAIL>
get <JAIL> journalmatch gets the journal filter match for
<JAIL>
get <JAIL> ignoreself gets the current value of the
ignoring the own IP addresses
get <JAIL> ignoreip gets the list of ignored IP
addresses for <JAIL>
get <JAIL> ignorecommand gets ignorecommand of <JAIL>
get <JAIL> failregex gets the list of regular
expressions which matches the
failures for <JAIL>
get <JAIL> ignoreregex gets the list of regular
expressions which matches patterns
to ignore for <JAIL>
get <JAIL> findtime gets the time for which the filter
will look back for failures for
<JAIL>
get <JAIL> bantime gets the time a host is banned for
<JAIL>
get <JAIL> datepattern gets the patern used to match
date/times for <JAIL>
get <JAIL> usedns gets the usedns setting for <JAIL>
get <JAIL> banip [<SEP>|--with-time] gets the list of of banned IP
addresses for <JAIL>. Optionally
the separator character ('<SEP>',
default is space) or the option '
--with-time' (printing the times
of ban) may be specified. The IPs
are ordered by end of ban.
get <JAIL> maxretry gets the number of failures
allowed for <JAIL>
get <JAIL> maxmatches gets the max number of matches
stored in memory per ticket in
<JAIL>
get <JAIL> maxlines gets the number of lines to buffer
for <JAIL>
get <JAIL> actions gets a list of actions for <JAIL>
COMMAND ACTION INFORMATION
get <JAIL> action <ACT> actionstart gets the start command for the
action <ACT> for <JAIL>
get <JAIL> action <ACT> actionstop gets the stop command for the
action <ACT> for <JAIL>
get <JAIL> action <ACT> actioncheck gets the check command for the
action <ACT> for <JAIL>
get <JAIL> action <ACT> actionban gets the ban command for the
action <ACT> for <JAIL>
get <JAIL> action <ACT> actionunban gets the unban command for the
action <ACT> for <JAIL>
get <JAIL> action <ACT> timeout gets the command timeout in
seconds for the action <ACT> for
<JAIL>
GENERAL ACTION INFORMATION
get <JAIL> actionproperties <ACT> gets a list of properties for the
action <ACT> for <JAIL>
get <JAIL> actionmethods <ACT> gets a list of methods for the
action <ACT> for <JAIL>
get <JAIL> action <ACT> <PROPERTY> gets the value of <PROPERTY> for
the action <ACT> for <JAIL>
Report bugs to https://github.com/fail2ban/fail2ban/issues
Options(命令行全局选项)逐条含义:
| 选项 | 含义 |
|---|---|
-c, --conf <DIR> | 指定配置目录(默认 /etc/fail2ban) |
-s, --socket <FILE> | 指定 socket 路径(默认 /var/run/fail2ban/fail2ban.sock) |
-p, --pidfile <FILE> | 指定 pidfile 路径 |
--loglevel <LEVEL> | 指定日志级别(覆盖 fail2ban.conf) |
--logtarget <TARGET> | 指定日志输出目标:文件名或 stdout/stderr/syslog/sysout |
-d | dump 配置(调试用) |
--dp, --dump-pretty | 以更易读的形式 dump 配置 |
-t, --test | 只测试配置不启动 |
-i | 交互模式 |
-v / -q | 提高 / 降低输出冗长度 |
-x | 强制启动(删除残留 socket) |
-b | 后台启动(默认) |
-f | 前台启动 |
--str2sec <STRING> | 把时间缩写转成秒数(如 1h → 3600,实测) |
-h, --help | 显示帮助 |
-V, --version | 打印机器可读版本 |
6.2 服务控制:start / stop / restart / reload
fail2ban-client start
启动 server 与全部 jail(以 systemd 管理时一般不用手敲;server 已在运行时实测报错):
2026-10-06 14:03:05,346 fail2ban [338097]: ERROR Server already running
fail2ban-client stop
停止全部 jail 并终止 server。实测注意:在 systemd 环境下用 fail2ban-client restart 会因 socket 残留报错(真实故障演示,见 6.7),恢复方法:
rm -f /var/run/fail2ban/fail2ban.sock
systemctl restart fail2ban
fail2ban-client ping
Server replied: pong
fail2ban-client reload —— 重新加载配置(jail.local 改动后使用,不中断 server):
fail2ban-client reload
OK
reload 后立即生效(实测:曾用 stop sshd 移除 jail 后,reload 使 jail 列表从 0 恢复为 1):
fail2ban-client status
Status
|- Number of jail: 1
`- Jail list: sshd
扩展:
reload --restart --unban <JAIL>可在重载单个 jail 的同时解除其封禁;reload --all重载全部。
6.3 status 与 status <jail>(完整字段解释)
fail2ban-client status —— 全局状态:
fail2ban-client status
Status
|- Number of jail: 1
`- Jail list: sshd
字段含义:Number of jail 为已启用 jail 数;Jail list 为 jail 名单。
fail2ban-client status sshd —— 单个 jail 详细状态:
fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
字段逐项解释:
| 字段 | 含义 | 本测试含义 |
|---|---|---|
Currently failed | 当前处于 findtime 窗口内、尚未达到 maxretry 的失败次数(会随窗口滑动归零) | 封禁触发后回落为 0 |
Total failed | 自 jail 启动以来累计匹配到的失败次数(含已封禁与未封禁) | 随错误登录递增,解封不清零 |
Journal matches | systemd backend 的 journal 过滤表达式(systemd backend 显示此项,而非 File list) | _SYSTEMD_UNIT=sshd.service + _COMM=sshd |
Currently banned | 当前处于封禁状态的 IP 数量 | 封禁中为 1,解封后 0 |
Total banned | 累计封禁过的 IP 次数(sqlite 持久化,重启不清零) | 解封后仍为 1 |
Banned IP list | 当前被封禁的 IP 清单 | 封禁中显示 127.0.0.1 |
若 jail 使用文件 backend(pyinotify/polling),
Journal matches字段位置会显示File list(监听的文件列表)。本环境为 systemd backend,因此无 File list(get sshd logpath实测返回No file is currently monitored)。
6.4 get <jail> <key>(查询类)
get sshd bantime:
fail2ban-client get sshd bantime
600
get sshd maxretry:
fail2ban-client get sshd maxretry
5
get sshd findtime:
fail2ban-client get sshd findtime
600
get sshd ignoreip —— 白名单列表(空时输出提示):
fail2ban-client get sshd ignoreip
No IP address/network is ignored
说明:Ubuntu jail.conf 中
ignoreip默认被注释(#ignoreip = 127.0.0.1/8 ::1),jail.local 也未设置,因此白名单为空(与 CentOS 默认带 127.0.0.1/8 ::1 不同)。建议生产环境显式配置,见 FAQ-6。
get sshd logpath —— 监听日志路径(systemd backend 下无需文件,输出提示):
fail2ban-client get sshd logpath
No file is currently monitored
get sshd usedns —— DNS 解析策略(warn=仅告警不阻塞):
fail2ban-client get sshd usedns
warn
get sshd actions —— 该 jail 启用的动作:
fail2ban-client get sshd actions
The jail sshd has the following actions:
iptables-multiport
get sshd journalmatch —— 当前 journal 过滤表达式:
fail2ban-client get sshd journalmatch
Current match filter:
_SYSTEMD_UNIT=sshd.service + _COMM=sshd
get sshd banned —— 被封 IP 的 Python 列表:
fail2ban-client get sshd banned
['127.0.0.1']
get sshd banip --with-time —— 带封禁起止时间:
fail2ban-client get sshd banip --with-time
127.0.0.1 2026-10-06 13:59:35 + 600 = 2026-10-06 14:09:35
即 127.0.0.1 于 13:59:35 被封,持续 600 秒,预计 14:09:35 自动解封。
get loglevel / set loglevel —— 运行时调整日志级别:
fail2ban-client get loglevel
Current logging level is 'INFO'
fail2ban-client set loglevel DEBUG
Current logging level is 'DEBUG'
fail2ban-client get loglevel
Current logging level is 'DEBUG'
fail2ban-client set loglevel INFO
Current logging level is 'INFO'
get dbfile / get dbpurgeage —— 数据库位置与封禁历史保留时长:
fail2ban-client get dbfile
Current database file is:
`- /var/lib/fail2ban/fail2ban.sqlite3
fail2ban-client get dbpurgeage
Current database purge age is:
`- 86400seconds
6.5 set <jail> <key> <value>(动态配置类,改后立即生效,不落盘)
注意:
set修改的是运行中内存配置,不写入配置文件;重启服务后失效。要持久化请改 jail.local 并fail2ban-client reload。
set sshd bantime 600:
fail2ban-client set sshd bantime 600
600
fail2ban-client get sshd bantime
600
set sshd maxretry 3 / 5(配合 get 验证):
fail2ban-client set sshd maxretry 3
3
fail2ban-client get sshd maxretry
3
fail2ban-client set sshd maxretry 5
5
set sshd findtime:
fail2ban-client set sshd findtime 600
600
set sshd addignoreip / delignoreip —— 动态增删白名单(实测,增删后立即生效):
fail2ban-client set sshd addignoreip 10.0.0.0/8
These IP addresses/networks are ignored:
`- 10.0.0.0/8
fail2ban-client get sshd ignoreip
These IP addresses/networks are ignored:
`- 10.0.0.0/8
fail2ban-client set sshd delignoreip 10.0.0.0/8
No IP address/network is ignored
set sshd banip / unbanip —— 手工封禁/解封指定 IP(实测):
fail2ban-client set sshd banip 192.168.99.99
1
fail2ban-client get sshd banned
['192.168.99.99']
fail2ban-client set sshd unbanip 192.168.99.99
1
fail2ban-client get sshd banned
[]
手工 banip 与自动封禁等价:同样会写入 iptables f2b-sshd 链,并按 bantime 到期自动解封。
其它常用 set(语法见 6.1 帮助,未逐条回显):set <JAIL> addlogpath <FILE>(文件 backend 时动态追加监听文件)、set <JAIL> addfailregex <REGEX>(动态加匹配正则)、set <JAIL> maxlines <N>、set <JAIL> usedns <yes|no|warn>、set <JAIL> ignoreself true|false。
6.6 unban(全局解封)
fail2ban-client unban <IP> —— 在全部 jail 与数据库中解封指定 IP:
fail2ban-client unban 127.0.0.1
1
(返回 1 表示确有一个 IP 被解封;返回 0 表示原本未封。)
fail2ban-client unban --all —— 解封所有 IP(实测,无封禁时返回 0):
fail2ban-client unban --all
0
fail2ban-client banned —— 列出各 jail 的被封 IP 字典(实测):
fail2ban-client banned
[{'sshd': []}]
6.7 restart 的 socket 残留故障(实测复现与恢复)
在 systemd 托管环境下直接执行 fail2ban-client restart,实测会先成功停止旧进程,但随即因旧 socket 未清理而启动失败:
fail2ban-client restart
Shutdown successful
2026-10-06 05:31:15,346 fail2ban [222749]: ERROR Could not start server. Maybe an old socket file is still present. Try to remove /var/run/fail2ban/fail2ban.sock. If you used fail2ban-client to start the server, adding the -x option will do it
恢复方法(删除残留 socket 后用 systemd 重启):
rm -f /var/run/fail2ban/fail2ban.sock
systemctl restart fail2ban
fail2ban-client ping
Server replied: pong
教训:管理 Fail2Ban 一律用 systemctl,不要手敲 fail2ban-client restart(详见八、systemd 服务管理与注意事项 ⚠️5)。
6.8 fail2ban-server -h(守护进程帮助)
fail2ban-server -h
真实回显(完整):
Usage: fail2ban-server [OPTIONS]
Fail2Ban v0.11.2 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
Options:
-c, --conf <DIR> configuration directory
-s, --socket <FILE> socket path
-p, --pidfile <FILE> pidfile path
--pname <NAME> name of the process (main thread) to identify instance (default fail2ban-server)
--loglevel <LEVEL> logging level
--logtarget <TARGET> logging target, use file-name or stdout, stderr, syslog or sysout.
--syslogsocket auto|<FILE>
-d dump configuration. For debugging
--dp, --dump-pretty dump the configuration using more human readable representation
-t, --test test configuration (can be also specified with start parameters)
-i interactive mode
-v increase verbosity
-q decrease verbosity
-x force execution of the server (remove socket file)
-b start server in background (default)
-f start server in foreground
--async start server in async mode (for internal usage only, don't read configuration)
--timeout timeout to wait for the server (for internal usage only, don't read configuration)
--str2sec <STRING> convert time abbreviation format to seconds
-h, --help display this help message
-V, --version print the version (-V returns machine-readable short format)
Report bugs to https://github.com/fail2ban/fail2ban/issues
常用参数说明:
| 参数 | 含义 | 典型用法 |
|---|---|---|
-t, --test | 只测试配置不启动(等价 fail2ban-client -t) | 改配置后校验 |
-f / -b | 前台 / 后台运行 | systemd 单元用 -xf start(-x 清 socket、-f 前台) |
-x | 强制启动(删除残留 socket) | 修复 socket 残留故障 |
-d / --dp | dump 配置(调试) | 查看解析后的完整配置 |
--str2sec <STRING> | 时间缩写转秒 | 如 fail2ban-client --str2sec 1h → 3600(实测) |
6.9 fail2ban-regex(正则调试工具)
fail2ban-regex -h 用法(实测摘录):
Usage: /usr/bin/fail2ban-regex [OPTIONS] <LOG> <REGEX> [IGNOREREGEX]
Fail2Ban reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
This tools can test regular expressions for "fail2ban".
LOG:
string a string representing a log line
filename path to a log file (/var/log/auth.log)
systemd-journal search systemd journal (systemd-python required),
optionally with backend parameters, see `man jail.conf`
for usage and examples (systemd-journal[journalflags=1]).
REGEX:
string a string representing a 'failregex'
filter name of filter, optionally with options (sshd[mode=aggressive])
filename path to a filter file (filter.d/sshd.conf)
IGNOREREGEX:
string a string representing an 'ignoreregex'
filename path to a filter file (filter.d/sshd.conf)
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-c CONFIG, --config=CONFIG
set alternate config directory
-d DATEPATTERN, --datepattern=DATEPATTERN
set custom pattern used to match date/times
--timezone=TIMEZONE, --TZ=TIMEZONE
set time-zone used by convert time format
-e ENCODING, --encoding=ENCODING
File encoding. Default: system locale
-r, --raw Raw hosts, don't resolve dns
--usedns=USEDNS DNS specified replacement of tags <HOST> in regexp
('yes' - matches all form of hosts, 'no' - IP
addresses only)
-L MAXLINES, --maxlines=MAXLINES
maxlines for multi-line regex.
用法一:裸正则(必须含 <HOST> 占位符)。不含 <HOST> 直接报错(实测):
fail2ban-regex /var/log/auth.log "sshd.*Failed password"
Running tests
=============
Use failregex line : sshd.*Failed password
ERROR: No failure-id group in 'sshd.*Failed password'
含 <HOST> 但 ^ 锚定在行首会全部漏报(实测:12 行全 missed,因为日志行首是时间/主机/进程前缀):
fail2ban-regex /var/log/auth.log "^Failed password for .* from <HOST>"
Results
=======
Failregex: 0 total
Ignoreregex: 0 total
Date template hits:
|- [# of hits] date format
| [12] {^LN-BEG}(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
`-
Lines: 12 lines, 0 ignored, 0 matched, 12 missed
[processed in 0.00 sec]
|- Missed line(s):
| Oct 06 05:29:08 b3d88cde7ef0 sshd[194644]: Failed password for root from 127.0.0.1 port 36650 ssh2
| Oct 06 05:29:10 b3d88cde7ef0 sshd[195414]: Failed password for root from 127.0.0.1 port 36660 ssh2
| Oct 06 05:29:14 b3d88cde7ef0 sshd[196881]: Failed password for root from 127.0.0.1 port 36662 ssh2
| Oct 06 05:29:16 b3d88cde7ef0 sshd[197762]: Failed password for root from 127.0.0.1 port 36668 ssh2
| Oct 06 05:29:18 b3d88cde7ef0 sshd[198177]: Failed password for root from 127.0.0.1 port 48892 ssh2
| Oct 06 05:29:47 b3d88cde7ef0 sshd[205277]: Failed password for root from 127.0.0.1 port 52102 ssh2
| Oct 06 06:14:16 b3d88cde7ef0 sshd[271841]: Failed password for root from 127.0.0.1 port 49574 ssh2
| Oct 06 06:14:20 b3d88cde7ef0 sshd[271845]: Failed password for root from 127.0.0.1 port 58864 ssh2
| Oct 06 06:14:24 b3d88cde7ef0 sshd[271849]: Failed password for root from 127.0.0.1 port 58872 ssh2
| Oct 06 06:14:28 b3d88cde7ef0 sshd[271853]: Failed password for root from 127.0.0.1 port 58874 ssh2
| Oct 06 06:14:32 b3d88cde7ef0 sshd[271857]: Failed password for root from 127.0.0.1 port 51106 ssh2
| Oct 06 06:14:38 b3d88cde7ef0 sshd[271885]: Failed password for root from 127.0.0.1 port 51122 ssh2
`-
不锚定行首、直接匹配含 <HOST> 的子串则全部命中(实测 12/12):
fail2ban-regex /var/log/auth.log "Failed password for .* from <HOST>"
Lines: 12 lines, 0 ignored, 12 matched, 0 missed
[processed in 0.00 sec]
用法二:用 filter 文件测试(推荐,最准确):
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
真实回显(完整):
Running tests
=============
Use failregex filter file : sshd, basedir: /etc/fail2ban
Use maxlines : 1
Use datepattern : {^LN-BEG} : Default Detectors
Use log file : /var/log/auth.log
Use encoding : UTF-8
Results
=======
Prefregex: 12 total
| ^(?P<mlfid>(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?|[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?)(?:(?:error|fatal): (?:PAM: )?)?(?P<content>.+)$
`-
Failregex: 12 total
|- #) [# of hits] regular expression
| 4) [12] ^Failed (?:<F-NOFAIL>publickey</F-NOFAIL>|\S+) for (?P<cond_inv>invalid user )?<F-USER>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)</F-USER> from <HOST>(?: (?:port \d+|on \S+)){0,2}(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
`-
Ignoreregex: 0 total
Date template hits:
|- [# of hits] date format
| [12] {^LN-BEG}(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
`-
Lines: 12 lines, 0 ignored, 12 matched, 0 missed
[processed in 0.01 sec]
输出解读:
| 输出段 | 含义 |
|---|---|
Use failregex filter file : sshd, basedir: /etc/fail2ban | 使用的 filter 名与目录 |
Prefregex: 12 total | prefregex 剥离前缀成功 12 行(全部日志前缀被正确剥掉) |
Failregex: 12 total + #) [12] | 第 4 条 failregex(^Failed ... from <HOST>)命中 12 次——即样本全部命中 |
Ignoreregex: 0 total | 无被 ignore 的行 |
Lines: 12 lines, 0 ignored, 12 matched, 0 missed | 最终结论:12 行全部匹配、0 漏报 |
调试自定义 filter 的三步法见 FAQ-5。
6.10 stop/start <jail> 与 help 命令(实测行为)
fail2ban-client stop <JAIL> —— 停止并移除指定 jail(实测):
fail2ban-client stop sshd
Jail stopped
fail2ban-client status
Status
|- Number of jail: 0
`- Jail list:
fail2ban-client start <JAIL> —— 启动 jail。注意该命令只能启动用 add <JAIL> <BACKEND> 动态创建的 jail,对配置文件中定义的 jail 实测报错:
fail2ban-client start sshd
2026-10-06 14:01:42,149 fail2ban [318438]: ERROR NOK: ('sshd',)
Sorry but the jail 'sshd' does not exist
恢复(重新加载配置文件即可):
fail2ban-client reload
OK
fail2ban-client help —— v0.11.2 实测报错(帮助菜单中列出的 help 命令实际不可用,属该版本怪癖):
fail2ban-client help
2026-10-06 14:03:13,595 fail2ban [341785]: ERROR NOK: ('Invalid command',)
Invalid command
查看帮助请用
fail2ban-client -h(6.1 节)或man fail2ban-client。
fail2ban-client --str2sec —— 时间缩写转秒数(实测):
fail2ban-client --str2sec 1h
3600
七、实战演练:模拟错误登录触发封禁与解封
本演练完整复现"攻击者 → 触发封禁 → 服务拒绝 → 手工解封 → 恢复访问"全流程,全部输出真实。
7.1 前置确认
确保 sshd 运行、fail2ban 运行、sshd jail 已启用(前面章节已配置 jail.local:maxretry=5、bantime=600、ignoreself=false):
systemctl is-active ssh
active
systemctl is-active fail2ban
active
fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
7.2 准备"错误密码"登录工具(模拟攻击者)
Ubuntu 22.04 源内有 sshpass(1.09-1),安装:
apt-get install -y sshpass
真实回显(摘录):
Reading package lists...
Building dependency tree...
Reading state information...
sshpass is already the newest version (1.09-1).
0 upgraded, 0 newly installed, 0 to remove and 7 not upgraded.
sshpass -V
sshpass 1.09
(C) 2006-2011 Lingnu Open Source Consulting Ltd.
(C) 2015-2016, 2021 Shachar Shemesh
This program is free software, and can be distributed under the terms of the GPL
See the COPYING file for more information.
Using "assword" as the default password prompt indicator.
7.3 执行 6 次错误密码登录
下面的
for循环仅用于封禁演示测试(模拟 6 次失败),属于验证性命令;生产部署/巡检脚本中不应使用循环刷错误登录。
for i in $(seq 1 6); do echo "== 第${i}次错误密码登录 =="; sshpass -p wrong$(date +%s) ssh -o StrictHostKeyChecking=no -o ConnectTimeout=3 root@127.0.0.1 exit 2>&1; done
真实输出(第 6 次连接被拒,说明已封禁):
== 第1次错误密码登录 ==
Permission denied, please try again.
== 第2次错误密码登录 ==
Permission denied, please try again.
== 第3次错误密码登录 ==
Permission denied, please try again.
== 第4次错误密码登录 ==
Permission denied, please try again.
== 第5次错误密码登录 ==
Permission denied, please try again.
== 第6次错误密码登录 ==
ssh: connect to host 127.0.0.1 port 22: Connection refused
- 前 5 次:
Permission denied, please try again.(密码错,但连接可达,sshd 正常响应)。 - 第 6 次:
Connection refused——因为 maxretry=5,第 5 次失败后 127.0.0.1 已被封禁,iptables REJECT 生效,TCP 层直接拒绝。
说明:sshpass 会在 sshd 再次提示密码时重复发送同一错误密码,因此单次"循环尝试"可能产生 1~2 条 Failed password 记录(journal 实测见 7.4),累计达到 maxretry=5 即封禁——这正符合真实暴力破解的形态。
7.4 观察封禁结果
fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 1
| |- Total failed: 12
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 1
|- Total banned: 2
`- Banned IP list: 127.0.0.1
(注:Total failed: 12 含此前多次演练累计的失败计数;本次演练从 6 起跳。)
fail2ban-client get sshd banned
['127.0.0.1']
fail2ban-client get sshd banip --with-time
127.0.0.1 2026-10-06 13:59:35 + 600 = 2026-10-06 14:09:35
fail2ban 日志(/var/log/fail2ban.log)中的事件链(真实):
tail -8 /var/log/fail2ban.log
2026-10-06 13:59:25,548 fail2ban.filter [224422]: INFO [sshd] Found 127.0.0.1 - 2026-10-06 13:59:25
2026-10-06 13:59:27,338 fail2ban.filter [224422]: INFO [sshd] Found 127.0.0.1 - 2026-10-06 13:59:26
2026-10-06 13:59:29,046 fail2ban.filter [224422]: INFO [sshd] Found 127.0.0.1 - 2026-10-06 13:59:28
2026-10-06 13:59:31,543 fail2ban.filter [224422]: INFO [sshd] Found 127.0.0.1 - 2026-10-06 13:59:31
2026-10-06 13:59:35,338 fail2ban.filter [224422]: INFO [sshd] Found 127.0.0.1 - 2026-10-06 13:59:35
2026-10-06 13:59:35,533 fail2ban.actions [224422]: NOTICE [sshd] Ban 127.0.0.1
2026-10-06 13:59:38,088 fail2ban.filter [224422]: INFO [sshd] Found 127.0.0.1 - 2026-10-06 13:59:37
- 前 5 条
Found 127.0.0.1:失败日志命中 failregex,逐条计数; Ban 127.0.0.1(NOTICE):第 5 次失败后立即触发封禁;- Ban 之后的第 6 条
Found(事件时间 13:59:37):来自第 5 次尝试会话中 sshpass 应答第二次密码提示的日志,发生在 REJECT 规则落地前后——封禁动作与最后一次失败之间存在毫秒级时间窗竞态,属正常现象,不影响封禁结果。
sshd 侧 journal 记录的"攻击"痕迹(真实):
journalctl -u ssh --no-pager --since "2026-10-06 13:59:00" --until "2026-10-06 14:00:30"
Oct 06 13:59:25 b3d88cde7ef0 sshd[273818]: Failed password for root from 127.0.0.1 port 38840 ssh2
Oct 06 13:59:26 b3d88cde7ef0 sshd[273818]: Failed password for root from 127.0.0.1 port 38840 ssh2
Oct 06 13:59:26 b3d88cde7ef0 sshd[273818]: Connection closed by authenticating user root 127.0.0.1 port 38840 [preauth]
Oct 06 13:59:28 b3d88cde7ef0 sshd[273823]: Failed password for root from 127.0.0.1 port 57170 ssh2
Oct 06 13:59:29 b3d88cde7ef0 sshd[273823]: Connection closed by authenticating user root 127.0.0.1 port 57170 [preauth]
Oct 06 13:59:31 b3d88cde7ef0 sshd[273828]: Failed password for root from 127.0.0.1 port 57184 ssh2
Oct 06 13:59:33 b3d88cde7ef0 sshd[273828]: Connection closed by authenticating user root 127.0.0.1 port 57184 [preauth]
Oct 06 13:59:35 b3d88cde7ef0 sshd[273833]: Failed password for root from 127.0.0.1 port 57200 ssh2
Oct 06 13:59:35 b3d88cde7ef0 sshd[273833]: Connection closed by authenticating user root 127.0.0.1 port 57200 [preauth]
Oct 06 13:59:37 b3d88cde7ef0 sshd[273838]: Failed password for root from 127.0.0.1 port 57214 ssh2
Oct 06 13:59:55 b3d88cde7ef0 sshd[275330]: Failed password for root from 127.0.0.1 port 60882 ssh2
Oct 06 13:59:55 b3d88cde7ef0 sshd[275330]: Connection closed by authenticating user root 127.0.0.1 port 60882 [preauth]
Oct 06 14:00:07 b3d88cde7ef0 sshd[273838]: Connection closed by authenticating user root 127.0.0.1 port 57214 [preauth]
可见:同一会话(同一 sshd PID)可连续输出多条 Failed password(sshpass 重复应答);第 6 次循环尝试因 SYN 被 REJECT 直接拒绝,journal 中无对应 sshd 日志。
7.5 查看防火墙层封禁规则
Ubuntu 使用 iptables-multiport,封禁体现为 iptables 链(真实):
iptables -S | grep -i f2b
-N f2b-sshd
-A INPUT -p tcp -m multiport --dports 22 -j f2b-sshd
-A f2b-sshd -s 127.0.0.1/32 -j REJECT --reject-with icmp-port-unreachable
-A f2b-sshd -j RETURN
-N f2b-sshd:新建专用链(actionstart 创建);-A INPUT -p tcp -m multiport --dports 22 -j f2b-sshd:INPUT 链对 TCP 22 端口跳入 f2b-sshd 链;-A f2b-sshd -s 127.0.0.1/32 -j REJECT:来源 127.0.0.1 的包被 REJECT(icmp-port-unreachable)——这就是封禁本体;-A f2b-sshd -j RETURN:其余 IP 放行回 INPUT 继续匹配。
7.6 手工解封并验证恢复
fail2ban-client unban 127.0.0.1
1
(返回 1 表示确有一个 IP 被解封;返回 0 表示原本未封。)
fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 1
| |- Total failed: 12
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 0
|- Total banned: 2
`- Banned IP list:
解封后再试一次错误登录——恢复为 Permission denied(连接可达):
sshpass -p wrong$(date +%s) ssh -o StrictHostKeyChecking=no -o ConnectTimeout=3 root@127.0.0.1 exit 2>&1
Permission denied, please try again.
iptables 中 REJECT 规则已删除(真实):
iptables -S | grep -i f2b
-N f2b-sshd
-A INPUT -p tcp -m multiport --dports 22 -j f2b-sshd
-A f2b-sshd -j RETURN
7.7 演练结论对照表
| 阶段 | 客户端表现 | status sshd | 说明 |
|---|---|---|---|
| 1~4 次失败 | Permission denied | Currently failed 递增 | 未达 maxretry=5,仅计数 |
| 第 5 次失败 | Permission denied | Ban 127.0.0.1(fail2ban.log) | 触发封禁 |
| 第 6 次尝试 | Connection refused | Banned IP list: 127.0.0.1 | iptables REJECT 生效 |
| unban 后 | Permission denied | Banned IP list 空 | 恢复访问 |
八、systemd 服务管理
管理 Fail2Ban 建议一律走 systemd(避免 6.7 的 socket 残留问题)。以下每行均为一个独立单元命令。
systemctl enable --now fail2ban
systemctl start fail2ban
systemctl stop fail2ban
systemctl restart fail2ban
systemctl reload fail2ban
systemctl status fail2ban --no-pager
systemctl is-active fail2ban
systemctl is-enabled fail2ban
systemctl disable fail2ban
journalctl -u fail2ban --no-pager -n 50
journalctl -u fail2ban -f
systemctl enable --now ssh
systemctl start ssh
systemctl stop ssh
systemctl restart ssh
systemctl status ssh --no-pager
systemctl is-active ssh
journalctl -u ssh --no-pager -n 20
服务单元说明:
- fail2ban.service(Ubuntu 包自带单元,实测完整内容):
[Unit]
Description=Fail2Ban Service
Documentation=man:fail2ban(1)
After=network.target iptables.service firewalld.service ip6tables.service ipset.service nftables.service
PartOf=firewalld.service
[Service]
Type=simple
Environment="PYTHONNOUSERSITE=1"
ExecStart=/usr/bin/fail2ban-server -xf start
# if should be logged in systemd journal, use following line or set logtarget to sysout in fail2ban.local
# ExecStart=/usr/bin/fail2ban-server -xf --logtarget=sysout start
ExecStop=/usr/bin/fail2ban-client stop
ExecReload=/usr/bin/fail2ban-client reload
RuntimeDirectory=fail2ban
PIDFile=/run/fail2ban/fail2ban.pid
Restart=on-failure
RestartPreventExitStatus=0 255
Environment="PYTHONNOUSERSITE=yes"
[Install]
WantedBy=multi-user.target
-
主进程
/usr/bin/fail2ban-server -xf start(-x 强制清理旧 socket,-f 前台由 systemd 托管);Restart=on-failure保证崩溃自动拉起;ExecReload走fail2ban-client reload。 -
ssh.service:Ubuntu 上 OpenSSH 的单元名(不是 sshd.service),
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS、Restart=on-failure。 -
开机自启清单(真实):
Created symlink /etc/systemd/system/multi-user.target.wants/fail2ban.service → /lib/systemd/system/fail2ban.service.
九、注意事项(⚠️)
-
⚠️ Ubuntu 服务单元名是 ssh.service 不是 sshd.service:
systemctl enable --now ssh,勿写成sshd。但 Fail2Ban 内部仍以sshd命名 jail/filter(_SYSTEMD_UNIT=sshd.service中 unit 名是 ssh,_COMM=sshd匹配进程名,两者配合在 journalmatch 中生效)。 -
⚠️ ignoreself 默认忽略本机 IP:jail.conf 语义默认
ignoreself = true,本机自身的失败不封禁。生产环境保持默认 true,本文演练中显式ignoreself = false只是为了演示 127.0.0.1 封禁效果,切勿照搬到生产。 -
⚠️ bantime 语义:
bantime = -1表示永久封禁,只有 unban 或 reload 才能解除;bantime = 0表示回退到全局默认值。时间可写600(秒)或10m/1h/1d(时间缩写,实测--str2sec 1h→3600)。 -
⚠️ 生产建议关闭密码登录:密码暴力破解正是 Fail2Ban 的防御对象,更彻底的做法是
PasswordAuthentication no+ 仅密钥认证 +PermitRootLogin prohibit-password(Ubuntu 默认即 prohibit-password,实测sshd -T),让攻击面彻底消失;Fail2Ban 作为纵深防御保留。 -
⚠️ fail2ban-client restart 在 systemd 环境会 socket 残留报错(实测复现):务必用
systemctl restart fail2ban;若已发生,删 socket 后重启即可(见 6.7)。 -
⚠️ fail2ban-regex 裸正则必须含
<HOST>:v0.11.x 下不含<HOST>直接报ERROR: No failure-id group(实测);且裸正则的^锚定在行首会全部漏报(实测 12 行全 missed,因为行首有时间/主机/进程前缀)。调试一律用 filter 文件(6.9)。 -
⚠️ 最小化环境无 rsyslog 时 auth.log 不实时写入:若 rsyslog 未运行(实测 inactive),
/var/log/auth.log不会自动追加,文件 backend 会失效。此时必须backend = systemd从 journal 读取;判断见 5.2 与 FAQ-1。 -
⚠️
set命令不落盘:fail2ban-client set sshd bantime 600只改运行中内存配置,重启即失效;要持久化必须写 jail.local 并fail2ban-client reload(实测验证)。 -
⚠️ Ubuntu 的 fail2ban 装完即启用 sshd jail:
jail.d/defaults-debian.conf默认[sshd] enabled = true(与 CentOS 默认全部禁用不同),装完不要惊讶status里已有 1 个 jail;如需完全自定义,删除该片段或在 jail.local 覆盖。 -
⚠️ iptables 规则仅存在于运行期:f2b-sshd 链是 actionstart 动态创建的,
iptables-save未持久化;重启主机后需要 fail2ban 服务启动时重新创建。若同时用 ufw,注意 ufw 规则与 f2b 链的先后(ufw 的 INPUT 链跳转优先于自定义链时可能出现冲突)。
十、常见问题(FAQ)
FAQ-1:Ubuntu 上 logtarget 到底该写 auth.log 还是 journal?
取决于 sshd 日志的落点:
- 默认 Ubuntu 22.04 服务器:rsyslog 常驻,
/var/log/auth.log由 rsyslog 实时写入(sshd_log = %(syslog_authpriv)s = /var/log/auth.log),backend = auto解析到文件 backend 或 systemd 均可工作。 - 最小化/无 rsyslog 环境(本文实测环境):rsyslog inactive,auth.log 不追加。此时必须
backend = systemd,Fail2Ban 从 journal 读取(journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd),get sshd logpath返回No file is currently monitored(实测)。 - 判断方法:
fail2ban-client status sshd显示Journal matches还是File list;或ls -l /var/log/auth.log看文件是否持续增长。
FAQ-2:backend 选 auto 与 systemd 的差别?
backend = auto(jail.conf 默认)在 Ubuntu 上会依次探测 systemd → pyinotify → polling(实测解析为 systemd,因 python3-systemd 已随包安装)。差别:
| 维度 | systemd | 文件(pyinotify/polling) |
|---|---|---|
| 日志来源 | journald(journalmatch 过滤) | logpath 文件尾部 |
| 前提 | python3-systemd + journald 有日志 | 日志文件存在且持续写入 |
| 无 rsyslog 时 | 可用(本文实测) | 失效(文件不更新) |
| 状态显示 | Journal matches | File list |
get logpath | No file is currently monitored | 列出文件路径 |
排错顺序:
fail2ban-client status sshd
fail2ban-client get sshd logpath
看输出再决定显式写 backend = systemd 还是补齐 rsyslog/python3-pyinotify。
FAQ-3:sshd jail 在 systemd 环境下需要什么特殊配置?
Ubuntu 上用 systemd backend 时,关键在 filter 的 journalmatch(不是 logpath):_SYSTEMD_UNIT=sshd.service + _COMM=sshd。若 sshd 不是由 systemd 托管(如手工 /usr/sbin/sshd 启动、或用 ssh.socket 激活),journal 字段不同,需调整:
fail2ban-client get sshd journalmatch
Current match filter:
_SYSTEMD_UNIT=sshd.service + _COMM=sshd
若 sshd 由 ssh.socket 按需激活启动(Ubuntu 22.04 可配 ssh.socket),journal 的 _SYSTEMD_UNIT 可能是 ssh.socket 派生的瞬时单元,匹配不到时需改用 _COMM=sshd 单独过滤或切回文件 backend。保证 ssh 服务以长驻方式运行(systemctl status ssh 显示 active (running))最稳妥。
FAQ-4:误封自己怎么办?
三种自救手段(按优先级):
- 若还能从其它入口(控制台/带外)登录:
fail2ban-client unban <你的IP>(实测返回 1)或fail2ban-client unban --all(实测返回 0)。 - 直接改白名单:
fail2ban-client set sshd addignoreip <你的IP>(实测回显These IP addresses/networks are ignored),并在 jail.local 落盘。 - 最坏情况(彻底被锁):
systemctl stop fail2ban会执行 actionstop 清掉所有 f2b 链与 REJECT 规则(实测iptables -S | grep f2b仅剩空链或全清),或用iptables -D f2b-sshd -s <你的IP> -j REJECT手工删除单条规则。
FAQ-5:怎么用 fail2ban-regex 调试自定义 filter?
三步法(全部实测):
- 准备样本:把真实失败日志拷成文件(最小化环境可用
journalctl -u ssh --no-pager | grep "Failed password" > /var/log/auth.log,实测 12 行); - 用 filter 文件测试:
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf,看Lines: 12 lines, 0 ignored, 12 matched, 0 missed; - 若漏报,把
|- Missed line(s):列出的行与你写的 failregex 逐字比对;改完用fail2ban-client reload生效。
切勿直接传裸正则:不含 <HOST> 报 No failure-id group,含 <HOST> 但前缀不对会 0 匹配(均实测,见 6.9)。
FAQ-6:ignoreip 建议怎么配?
把管理网段/堡垒机/监控源/回环加入白名单,防止管理员被封在门外。Ubuntu 的 jail.conf 中 ignoreip 默认被注释(实测 #ignoreip = 127.0.0.1/8 ::1),get sshd ignoreip 实测返回 No IP address/network is ignored——即默认不忽略任何地址,生产必须显式配置。运行时增删(实测):
fail2ban-client set sshd addignoreip 10.0.0.0/8
These IP addresses/networks are ignored:
`- 10.0.0.0/8
fail2ban-client set sshd delignoreip 10.0.0.0/8
No IP address/network is ignored
落盘则在 jail.local 的 [DEFAULT] 中写:
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16
FAQ-7:unban 返回 0/1 代表什么?
返回 1 表示确实解封了该 IP(实测 unban 127.0.0.1 → 1);返回 0 表示该 IP 本就不在封禁列表(实测 unban --all 无封禁时 → 0)。脚本里可据此判断是否真的发生过封禁。
FAQ-8:如何区分"计数失败"与"已被封禁"?
看 status sshd 三个字段:Currently failed(未达阈值、只计数)、Total failed(累计)、Currently banned + Banned IP list(已封禁)。实测数据:第 5 次失败后 Banned IP list: 127.0.0.1、Currently banned: 1;解封后 Total failed 仍累计、Currently banned 归 0、Total banned 保持 2(累计次数)。
FAQ-9:bantime 负数/0 的封禁语义?
-1:永久封禁,只有 unban 或 reload 解除;0:使用全局默认值;- 正数:秒数,也接受
10m、1h等缩写(--str2sec可换算,实测1h→3600)。
FAQ-10:改了 jail.local 不生效?
jail.local 修改后必须重载:fail2ban-client reload(实测返回 OK)或 systemctl reload fail2ban。set 命令的临时修改重启即丢(注意事项 ⚠️8)。另外检查是否被 jail.d/*.conf 与 jail.local 的优先级覆盖(加载顺序 jail.conf < jail.d/*.conf < jail.local)。
十一、卸载方式
11.1 卸载预演(真实输出,未实际删除)
先用 dry-run 预演确认将移除的包(实测):
apt-get purge --dry-run fail2ban
Reading package lists...
Building dependency tree...
Reading state information...
The following packages were automatically installed and are no longer required:
python3-pyinotify python3-systemd whois
Use 'apt autoremove' to remove them.
The following packages will be REMOVED:
fail2ban*
0 upgraded, 0 newly installed, 1 to remove and 7 not upgraded.
Purg fail2ban [0.11.2-6]
注意:
python3-pyinotify、python3-systemd、whois是被自动安装的依赖,卸载 fail2ban 后成为"不再需要的包",可apt autoremove清理(仅当无其它包引用时)。
11.2 正式卸载
systemctl stop fail2ban
systemctl disable fail2ban
apt-get purge -y fail2ban
apt-get autoremove -y
清理残留文件与数据(配置、日志、sqlite 数据库、socket):
rm -rf /etc/fail2ban
rm -f /var/log/fail2ban.log
rm -rf /var/lib/fail2ban
rm -f /var/run/fail2ban/fail2ban.sock
清理防火墙残留(若 f2b-sshd 链仍在):
iptables -F f2b-sshd
iptables -D INPUT -p tcp -m multiport --dports 22 -j f2b-sshd
iptables -X f2b-sshd
验证已卸载:
dpkg -l fail2ban 2>/dev/null | tail -1
(输出 un ... <none> 或报 No packages found 即为已卸载)
十二、参考链接
- Fail2Ban 官方网站:https://www.fail2ban.org/
- Fail2Ban GitHub 仓库(Issue 与源码):https://github.com/fail2ban/fail2ban
- man 手册:
man fail2ban-clientman fail2ban-serverman fail2ban-regexman jail.confman fail2ban.conf
- 官方 wiki(如何自定义 filter/action):https://github.com/fail2ban/fail2ban/wiki
- Ubuntu 22.04 (jammy) universe 源包:fail2ban 0.11.2-6(
apt-cache policy fail2ban实测) - Ubuntu 官方软件包页:https://packages.ubuntu.com/jammy/fail2ban
- OpenSSH 日志与 LogLevel VERBOSE 说明:
man sshd_config