Ubuntu 22.04 Fail2Ban 入侵防御部署与命令详解

Ubuntu 22.04 系统 Fail2Ban 入侵防御工具的安装(在线 apt 与离线 deb)与 fail2ban-client 全部常用命令参数实测详解,含 jail/filter 配置与错误登录封禁实战

Linux 安全与审计Fail2ban安全加固服务安全Ubuntu 22.04入侵防御2026/10/06

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
Fail2Ban0.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 解释执行
OpenSSHopenssh-server 1:8.9p1-3ubuntu0.17(amd64)dpkg -l openssh-server
客户端工具openssh-client(ssh 命令)、sshpass 1.09sshpass -V → sshpass 1.09
防火墙iptables(legacy 链),banaction=iptables-multiportiptables -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   │   │                  │
        └────────────────┘   └──────────────────┘

一次完整的"封禁事件"流程如下:

  1. 采集:jail 的 backend(本文为 systemd,即 journald)持续读取 sshd 产生的认证日志;若为文件 backend(pyinotify/polling/auto),则监听日志文件尾部。
  2. 解析时间:用 datepattern 从每行日志中解析出时间戳,判断是否落在 findtime 窗口内。
  3. 提取内容:prefregex 先把"日志前缀(时间/主机/进程)"与"日志内容"分离。
  4. 匹配失败:failregex 对内容做正则匹配,命中则从 <HOST> 占位符中提取来源 IP;ignoreregex 命中的行被排除。
  5. 计数:同一 IP 在 findtime 秒内累计命中 maxretry 次,即触发封禁(本文配置 maxretry=5,实测第 5 次失败后封禁)。
  6. 封禁:执行 action 的 actionban 命令。本文实测 action 为 iptables-multiport:创建名为 f2b-sshd 的链,在 INPUT 链按 multiport 端口 22 跳入该链,并在链首插入 REJECT --reject-with icmp-port-unreachable。
  7. 到期解封:达到 bantime 秒后自动执行 actionunban 从链中删除该 IP;也可用 fail2ban-client unban <ip> 手工提前解封。
  8. 持久化:封禁历史写入 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/*.confjail 片段,按文件名顺序在 jail.conf 之后加载;Ubuntu 的 defaults-debian.conf 内容为 [sshd] enabled = true(见 5.1)
jail.local用户覆盖文件,优先级最高;本测试将 sshd jail 与 DEFAULT 参数写在这里
filter.d/sshd.confsshd 失败匹配正则,含 prefregex/cmnfailre/failregex/journalmatch 等(完整见 5.3)
action.d/iptables-multiport.confiptables 的 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)含义
enabledtruefalse(Debian 包在 jail.d 默认开 sshd)是否启用该 jail
maxretry55findtime 窗口内失败多少次触发封禁
findtime10m(600 秒)10m统计失败的时间回溯窗口
bantime10m(600 秒)10m封禁时长;-1 表示永久封禁
ignoreip(未设)注释状态(#ignoreip = 127.0.0.1/8 ::1)永不封禁的 IP/网段白名单
ignoreselffalsetrue(jail.conf 注释为 true 语义)是否忽略本机自身 IP(本测试置 false 以便模拟 127.0.0.1 封禁)
backendsystemdauto日志来源:systemd=journald、auto=自动探测、pyinotify/polling=文件轮询
banaction(未设,继承 iptables-multiport)iptables-multiport封禁使用的 action;banaction_allports = iptables-allports
portsshssh(各 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 上的实测结论:

  1. auto 在 Ubuntu 上默认解析为 systemd(paths-common.conf:default_backend = %(default/backend)s,而 auto 探测时 systemd 可用则优先)。因此即使不写 backend = systemd,fail2ban-client status sshd 同样显示 Journal matches。
  2. 显式 backend = systemd 时,get sshd logpath 返回 No file is currently monitored(实测),日志改从 journal 的 journalmatch(_SYSTEMD_UNIT=sshd.service + _COMM=sshd)过滤。
  3. 若改用文件 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-ddosDid not receive identification / kex_exchange_identification / Bad protocol version 等ddos 模式:扫描/握手失败类(未收到客户端标识、协议版本非法等)
mdre-extraNo supported authentication methods / no matching algo foundextra 模式:算法协商失败类
mdre-aggressive%(mdre-ddos)s + %(mdre-extra)saggressive 模式 = ddos + extra 全部
failregex%(cmnfailre)s + <mdre-<mode>> + %(cfooterre)s实际生效的正则集合 = 公共失败规则 + 当前 mode 规则 + 连接来源记录
mode = normal默认模式在 jail.local 用 filter = sshd[mode=aggressive] 可切换
journalmatch_SYSTEMD_UNIT=sshd.service + _COMM=sshdsystemd 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.confiptables 多端口链封禁(默认)Ubuntu/Debian 通用,无 firewalld
iptables-allports.conf封禁来源 IP 的所有端口攻击者全端口扫描时
iptables-ipset-proto4.confipset(hash:ip)+ iptables 引用大量 IP 时性能更好
firewallcmd-ipset.conf / firewallcmd-rich-rules.conffirewalld + ipset / rich rule装了 firewalld 的机器(CentOS 系习惯)
ufw.confufw 规则封禁用 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
-ddump 配置(调试用)
--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 matchessystemd 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 / --dpdump 配置(调试)查看解析后的完整配置
--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 totalprefregex 剥离前缀成功 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 deniedCurrently failed 递增未达 maxretry=5,仅计数
第 5 次失败Permission deniedBan 127.0.0.1(fail2ban.log)触发封禁
第 6 次尝试Connection refusedBanned IP list: 127.0.0.1iptables REJECT 生效
unban 后Permission deniedBanned 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.

九、注意事项(⚠️)

  1. ⚠️ Ubuntu 服务单元名是 ssh.service 不是 sshd.service:systemctl enable --now ssh,勿写成 sshd。但 Fail2Ban 内部仍以 sshd 命名 jail/filter(_SYSTEMD_UNIT=sshd.service 中 unit 名是 ssh,_COMM=sshd 匹配进程名,两者配合在 journalmatch 中生效)。

  2. ⚠️ ignoreself 默认忽略本机 IP:jail.conf 语义默认 ignoreself = true,本机自身的失败不封禁。生产环境保持默认 true,本文演练中显式 ignoreself = false 只是为了演示 127.0.0.1 封禁效果,切勿照搬到生产。

  3. ⚠️ bantime 语义:bantime = -1 表示永久封禁,只有 unban 或 reload 才能解除;bantime = 0 表示回退到全局默认值。时间可写 600(秒)或 10m/1h/1d(时间缩写,实测 --str2sec 1h → 3600)。

  4. ⚠️ 生产建议关闭密码登录:密码暴力破解正是 Fail2Ban 的防御对象,更彻底的做法是 PasswordAuthentication no + 仅密钥认证 + PermitRootLogin prohibit-password(Ubuntu 默认即 prohibit-password,实测 sshd -T),让攻击面彻底消失;Fail2Ban 作为纵深防御保留。

  5. ⚠️ fail2ban-client restart 在 systemd 环境会 socket 残留报错(实测复现):务必用 systemctl restart fail2ban;若已发生,删 socket 后重启即可(见 6.7)。

  6. ⚠️ fail2ban-regex 裸正则必须含 <HOST>:v0.11.x 下不含 <HOST> 直接报 ERROR: No failure-id group(实测);且裸正则的 ^ 锚定在行首会全部漏报(实测 12 行全 missed,因为行首有时间/主机/进程前缀)。调试一律用 filter 文件(6.9)。

  7. ⚠️ 最小化环境无 rsyslog 时 auth.log 不实时写入:若 rsyslog 未运行(实测 inactive),/var/log/auth.log 不会自动追加,文件 backend 会失效。此时必须 backend = systemd 从 journal 读取;判断见 5.2 与 FAQ-1。

  8. ⚠️ set 命令不落盘:fail2ban-client set sshd bantime 600 只改运行中内存配置,重启即失效;要持久化必须写 jail.local 并 fail2ban-client reload(实测验证)。

  9. ⚠️ Ubuntu 的 fail2ban 装完即启用 sshd jail:jail.d/defaults-debian.conf 默认 [sshd] enabled = true(与 CentOS 默认全部禁用不同),装完不要惊讶 status 里已有 1 个 jail;如需完全自定义,删除该片段或在 jail.local 覆盖。

  10. ⚠️ 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 matchesFile list
get logpathNo 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:误封自己怎么办?

三种自救手段(按优先级):

  1. 若还能从其它入口(控制台/带外)登录:fail2ban-client unban <你的IP>(实测返回 1)或 fail2ban-client unban --all(实测返回 0)。
  2. 直接改白名单:fail2ban-client set sshd addignoreip <你的IP>(实测回显 These IP addresses/networks are ignored),并在 jail.local 落盘。
  3. 最坏情况(彻底被锁):systemctl stop fail2ban 会执行 actionstop 清掉所有 f2b 链与 REJECT 规则(实测 iptables -S | grep f2b 仅剩空链或全清),或用 iptables -D f2b-sshd -s <你的IP> -j REJECT 手工删除单条规则。

FAQ-5:怎么用 fail2ban-regex 调试自定义 filter?

三步法(全部实测):

  1. 准备样本:把真实失败日志拷成文件(最小化环境可用 journalctl -u ssh --no-pager | grep "Failed password" > /var/log/auth.log,实测 12 行);
  2. 用 filter 文件测试:fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf,看 Lines: 12 lines, 0 ignored, 12 matched, 0 missed;
  3. 若漏报,把 |- 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 即为已卸载)

十二、参考链接