fail2ban配置

正在完善中

防火墙

系统防火墙 正确 action 名称 查看规则命令
iptables 旧版 iptables-multiport iptables -L -n
nftables 新版 nftables-multiport nft list ruleset
1
iptables -L -n
1
nft list ruleset

更新

手动更新

刷新软件包列表

这一步非常重要,因为你的源配置可能阻止了部分索引,强制刷新可以确保获取最新的(且被允许的)软件包信息。

1
sudo apt update --allow-releaseinfo-change
1
sudo apt install fail2ban

1
sudo apt upgrade fail2ban

二选一就可以了

自动更新

安装 PPA 依赖

1
2
# 18.04需先安装添加PPA的工具
sudo apt update -y && sudo apt install -y software-properties-common apt-transport-https ca-certificates

创建专属配置文件 /etc/apt/apt.conf.d/61-fail2ban-auto-upgrade

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat > /etc/apt/apt.conf.d/61-fail2ban-auto-upgrade <<'EOF'
# Fail2ban 独立自动更新配置 Ubuntu 24.04 noble
# 仅放行 Ubuntu 官方安全更新、系统更新源(无需额外PPA)
Unattended-Upgrade::Origins-Pattern {
"origin=Ubuntu,archive=noble-security";
"origin=Ubuntu,archive=noble-updates";
};
# 白名单:所有 fail2ban 相关包自动升级
Unattended-Upgrade::Package-Whitelist {
"^fail2ban";
};
# 升级后无需重启整机
Unattended-Upgrade::Automatic-Reboot "false";
# 升级完成自动重启 fail2ban 服务(可选,建议开启)
Unattended-Upgrade::Automatic-Restart-Services "true";
EOF

校验文件是否生成成功

1
cat /etc/apt/apt.conf.d/61-fail2ban-auto-upgrade

重要配置

fail2ban ** 动作规则配置(触发封禁后的操作)**

使用nano /etc/fail2ban/jail.d/nftables.conf打开这个文件,找到blocktype这个配置改成blocktype = drop,配置完成后,测试配置如果没有错误,就重启服务

1
2
3
4
5
6
7
nano /etc/fail2ban/jail.d/nftables.conf

blocktype = drop

fail2ban-client --test

systemctl reload-or-restart fail2ban.service # 或者 fail2ban-client reload

fail2ban 优化

配置nano /etc/fail2ban/fail2ban.conf即便重启服务器,屏蔽的IP还是处于屏蔽状态

1
2
3
4
5
6
# 开启持久化数据库(默认路径)
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
# 数据库清理周期:过期封禁记录自动清理,7天=604800秒 这个值一定要大于你设置封锁IP最长一个的时间,这里设置了90天
dbpurgeage = 90d
# 每条IP最多保存几条匹配日志,减少库体积
dbmaxmatches = 5

按照如上设置,

dbpurgeage 含义:超过 90 天的历史记录,定期从 sqlite 里彻底删除

dbmaxmatches = 5 每个 IP 最多存 5 条触发封禁的日志记录,仅用来缩小 sqlite 体积,和重启恢复封禁无关。

测试fail2ban配置是否有问题

1
fail2ban-client --test

重启fail2ban服务

1
systemctl reload-or-restart fail2ban.service

查看底层配置防火墙

1
sudo nft list ruleset

默认配置

创建一个defaults-debian.conf文件,存放以下内容;注意文件保存的格式,把这个文件放入到/etc/fail2ban/jail.d/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# ============================================================
# [DEFAULT] 段 - 全局默认配置
# ============================================================
# 此段配置会被所有 jail 继承,除非 jail 内单独覆盖
# ============================================================
[DEFAULT]

# ------------------------------------------------------------
# 封禁动作配置(核心!)
# ------------------------------------------------------------

# 封禁动作(针对指定端口)
# 作用:当触发封禁时,使用此动作阻止攻击者
# 可选值:
# - iptables-multiport → REJECT(发送拒绝响应)
# - iptables-multiport-drop → DROP(直接丢弃,无响应)⭐推荐
# - nftables → 使用 nftables 防火墙
# - firewallcmd-ipset → 使用 firewalld
# Ubuntu 20.04+ nftables 原生支持

banaction = nftables
# banaction = iptables-multiport-drop

# 封禁动作(针对所有端口)
# 作用:当需要封禁所有端口时使用此动作
# 场景:某些 jail 配置了 allport = true
# banaction_allports = iptables-allports-drop
banaction_allports = nftables[type=allports]

# IPv6 封禁动作(可选,如需要 IPv6 支持)
# banaction_ipv6 = iptables-multiport-drop
# banaction_allports_ipv6 = iptables-allports-drop

banaction_ipv6 = nftables
banaction_allports_ipv6 = nftables[type=allports]

# ------------------------------------------------------------
# 日志后端配置
# ------------------------------------------------------------

# 日志读取后端
# 作用:指定如何读取系统日志
# 可选值:
# - systemd → 使用 systemd journal(推荐,新版系统)
# - auto → 自动检测
# - polling → 轮询日志文件
# - syslog → 使用 syslog
backend = systemd

# 轮询间隔,单位秒,默认1,数值越大CPU越低,日志捕获延迟越高 这个值与backend = polling有关
polltime = 60

# ------------------------------------------------------------
# 白名单配置
# ------------------------------------------------------------

# 忽略的 IP 地址(白名单)
# 作用:这些 IP 永远不会被封禁
# 重要:务必添加自己的管理 IP,防止被锁在外面!
# 格式:支持 CIDR 表示法
ignoreip = 127.0.0.1/8 ::1 100.104.0.0/16

# 启用递增封禁
bantime.increment = true
# 递增因子
bantime.factor = 2
# 最大封禁时间
bantime.maxtime = 604800

action

确认action_的定义位置

1
grep -r "^action_" /etc/fail2ban/jail.conf /etc/fail2ban/jail.local 2>/dev/null

配置action可以参考如下

1
2
3
4
5
6
# 在配置时候可以自己定义如下参数
filter = 这里可以自己定义
banaction = 底层防火墙
port = 端口号
protocol = 协议
action = %(banaction)s[name=%(filter)s-udp,port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]

name= 后面的名字可以自定义,下面有这个参数的写法

chain 是防火墙规则链名称:

  • iptables:内置链 INPUT、FORWARD、OUTPUT,fail2ban 默认用 INPUT

  • nftables:对应 table + chain,fail2ban 会自动创建专用链,如 f2b-xxx

journalmatch配置

(如果不想折腾,就直接跳过改章节)

只有在backend = systemd时候才会用到 journalmatch配置

如果同时配置了如下配置: journalmatch 这一行直接被 fail2ban 忽略,完全失效

1
2
3
logpath = /var/log/auth.log
backend = pyinotify
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd

如果配置了 backend = systemd,就会执行下面这个功能,关于配置值按如下方式查找

1
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd

_SYSTEMD_UNIT 是 systemd-journal 日志自带的元标签(metadata),每条日志都会自动打上这个标记,记录这条日志是哪个 systemd 服务程序输出的。可以直接进入下面目录查看现在有哪些服务,这个就是写的服务名称

1
cd /etc/systemd/system/

**+ ** 符号 含义

逻辑 AND(并且),代表前后两个条件必须同时满足,日志才会被保留,缺一不可。

**_COMM=**含义

_COMM 同样是 journal 内置元标签,代表进程启动的二进制程序文件名(可理解为进程本名)。

_COMM的值查找使用如下命令

1
2
# 完整输出:PID / 内核进程名_COMM / 完整启动命令 | grep 查找相关的数据(只需要一个模糊的名字就可以,实在找不到就查百度)
ps -eo pid,comm,cmd | grep "ssh"

pid:进程 ID

comm = journal 里的 _COMM 值(内核短进程名,最多 15 字符)

cmd:完整启动命令路径 + 参数

在backend = systemd配置时候,可以添加如下值,如果backend不是配置的systemd时候也可以使用,

1
2
3
4
# 预过滤下放journalctl执行,配合journalmatch必开
prefilter = true
# 公网服务器高频扫描设1000,抵御批量IP攻击;超大流量可上调至2000
maxmatches = 1000

详细工作逻辑
不开启 prefilter=false
Fail2ban 先拉取该服务单元下全部原始日志(包含大量无关杂日志),再在 Python 内部逐条跑正则过滤,大量无用文本占用 CPU。
开启 prefilter=true
journalctl 依靠日志内置元数据索引,在把日志传给 fail2ban 之前,直接丢弃不满足 journalmatch 的日志;
最终只有目标服务(sshd/hbbr/nginx)的日志流入正则匹配环节。
带来的效果
减少 90% 以上正则运算量,大幅降低 CPU 占用;
和 _SYSTEMD_UNIT + _COMM 组合是标准性能优化搭配;
backend=pyinotify 读取文本日志时,此参数完全失效,不参与任何逻辑。
一句话总结
prefilter 是日志源头过滤开关,用来提前砍掉无关日志,减少后续计算压力。

_SYSTEMD_UNIT=sshd.service + _COMM=sshd作用

精准锁定进程,排除同名服务下的杂项子进程:

ssh配置

添加完成后,要重启fail2ban服务

预认证洪水攻击检测配置规则

创建一个sshd-aggressive.conf文件,存放以下内容;注意文件保存的格式,把这个文件放入到/etc/fail2ban/filter.d/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[INCLUDES]
before = common.conf

[Definition]

# 纯 Aggressive 攻击检测:暴力破解 / 扫描 / 无效用户 / 协议攻击
# 按威胁等级排序,高优先级规则在前
failregex =
# 🔴 等级1:预认证洪水攻击(最高优先级)
^%(__prefix_line)sConnection (?:closed|reset) by authenticating user <F-USER>\S+</F-USER> <HOST>(?: port \d+)? \[preauth\]$
^%(__prefix_line)sConnection closed by <HOST> port \d+ \[preauth\]$

# 🔴 等级2:root专项攻击
^%(__prefix_line)sDisconnected from authenticating user root <HOST>(?: port \d+)? \[preauth\]$
^%(__prefix_line)sFailed \w+ for root from <HOST>(?: port \d+)?(?: ssh\d*)?$

# 🟠 等级3:认证超限攻击
^%(__prefix_line)s(?:error: )?maximum authentication attempts exceeded for (?:invalid user )?<F-USER>\S+</F-USER>(?: from \S+)? from <HOST>(?: port \d+)?$

# 🟠 等级4:无效用户扫描
^%(__prefix_line)sInvalid user <F-USER>\S+</F-USER> from <HOST>(?: port \d+)?(?: ssh\d*)?$

# 🟡 等级5:密码暴力破解
^%(__prefix_line)sFailed \w+ for (?:invalid user )?<F-USER>\S+</F-USER> from <HOST>(?: port \d+)?(?: ssh\d*)?$

# 🟡 等级6:协议协商攻击
^%(__prefix_line)s(?:fatal: )?(?:Unable to negotiate with|Connection reset by) <HOST>.*: no matching (?:cipher|mac|key exchange|host key|kex) found

# 🟢 等级7:未授权用户
^%(__prefix_line)sUser <F-USER>\S+</F-USER> not allowed because not listed in AllowUsers from <HOST>(?: port \d+)?$

# 🟢 等级8:其他异常
^%(__prefix_line)sReceived disconnect from <HOST>(?: port \d+)?:\s*3:\s*com\.juniper\.netscreen\.SSH2\.DisconnectReason\.ADMINISTRATIVELY_PROHIBITED

ignoreregex =
datepattern = {^LN-BEG}
maxlines = 3
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd

# 性能优化参数
maxmatches = 1000
prefilter = true
encoding = auto

验证上面语法是否对日志生效

1
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd-aggressive.conf --print-all-matched

洪水攻击检测配置规则

创建一个sshd-maxstartups.conf文件,存放以下内容;注意文件保存的格式,把这个文件放入到/etc/fail2ban/filter.d/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# SSH MaxStartups/Connection Flood Protection
# 检测:短时间内大量SSH连接请求
# 特征:Connection closed by authenticating user, MaxStartups exceeded

[INCLUDES]
before = common.conf

[Definition]
# 核心规则:按攻击严重程度排序
failregex =
# 1. 真正的MaxStartups限制(最高优先级)
^%(__prefix_line)serror: maxstartups: <HOST> beyond limit, refused to connect\s*$
^%(__prefix_line)serror: .*MaxStartups.*exceeded.*<HOST>.*$

# 2. 认证洪水攻击(高优先级)
^%(__prefix_line)s(?:error: )?maximum authentication attempts exceeded for .* from <HOST>.*$
^%(__prefix_line)sDisconnecting: Too many authentication failures(?: for .*?)? from <HOST>.*$

# 3. 预认证连接洪水
^%(__prefix_line)sConnection (?:closed|reset) by authenticating user <F-USER>\S+</F-USER> <HOST>(?: port \d+)? \[preauth\]\s*$
# ^%(__prefix_line)s(?:fatal: )?Connection closed by <HOST>.*\[preauth\]\s*$

# 4. 协议协商洪水
# ^%(__prefix_line)s(?:fatal: )?(?:Unable to negotiate with|Connection reset by) <HOST>.*: no matching (?:host|key|cipher|compression|MAC) found\s*$

# 5. 密码洪水攻击
^%(__prefix_line)s(?:fatal: )?read_passwd:.*from <HOST>.*\s*$

ignoreregex =

# 性能与兼容性优化
# datepattern = {^LN-BEG} 可以去除
datepattern = {^LN-BEG}
maxlines = 3
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd

# 检测模式优化
encoding = auto

验证上面语法是否对日志生效

1
2
# 检查过滤器语法
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd-maxstartups.conf --print-all-matched

兜底匹配规则

如果还有什么规则没有匹配到,直接写道这里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
[INCLUDES]
before = common.conf


[Definition]
# ====================== 核心攻击匹配正则(仅你指定单条规则) ======================
# failregex:匹配SSH非法握手banner错误日志,捕获攻击IP <HOST>
# 匹配日志样例:banner exchange: Connection from 31.14.32.7 port 38896: invalid format
# %(__prefix_line)s 内置前缀宏:自动匹配日志开头时间、主机名、sshd进程ID等无关内容
# <HOST> fail2ban内置变量,自动提取日志中的IPv4/IPv6地址用于封禁
failregex = ^%(__prefix_line)sbanner exchange: Connection from <HOST>.*invalid format
# 匹配:Connection closed by x.x.x.x port xxxx(去掉末尾$)
^%(__prefix_line)sConnection closed by <HOST> port \d+
# 匹配:banner exchange: Connection from x.x.x.x port xxxx: could not read protocol version
^%(__prefix_line)sbanner exchange: Connection from <HOST> port \d+: could not read protocol version
# 匹配:Connection closed by x.x.x.x port xxxx [preauth](去掉末尾$)
# ^%(__prefix_line)sConnection closed by <HOST> port \d+ \[preauth\]

# ====================== 白名单排除正则(无需修改,预留) ======================
# ignoreregex:匹配到该行则直接忽略,不会计入失败次数,这里留空无排除规则
ignoreregex =

# ====================== 日志时间匹配规则 ======================
# datepattern:日志开头时间戳匹配模板
# {^LN-BEG} 是fail2ban内置宏,代表日志行起始位置,适配syslog标准时间格式(Jul 5 11:43:24)
# 作用:精准锁定日志时间字段,避免日志内容干扰时间解析,不建议删除
datepattern = {^LN-BEG}

# ====================== 日志读取性能优化 ======================
# maxlines:单次日志读取最多扫描连续行数,默认1
# 设置3是兼容多行报错日志(同一次攻击输出多条关联错误),兼顾识别不漏报与性能开销
maxlines = 3

# ====================== 日志源过滤(systemd-journal专属) ======================
# journalmatch:仅从sshd服务的journal日志匹配,过滤CRON/nginx/其他服务无关日志
# 规则:同时满足两个条件:
# 1. _SYSTEMD_UNIT=sshd.service 仅匹配ssh服务日志
# 2. _COMM=sshd 进程名严格为sshd,排除sshd-keygen等无关ssh进程
# 仅使用journald日志存储(Ubuntu/debian默认)时生效,不影响文件日志读取逻辑
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd

# ====================== 编码兼容设置 ======================
# encoding:日志文件读取编码格式
# auto自动识别系统默认编码(UTF-8/ASCII),避免中文/特殊字符日志导致匹配失效
encoding = auto

# 预过滤下放journalctl执行,配合journalmatch必开
prefilter = true
# 公网服务器高频扫描设1000,抵御批量IP攻击;超大流量可上调至2000
maxmatches = 200

验证上面语法是否对日志生效

1
2
# 检查过滤器语法
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd-backstop.conf --print-all-matched

添加过滤器配置

创建一个customize-sshd.conf文件,存放以下内容;注意文件保存的格式,把这个文件放入到/etc/fail2ban/jail.d/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# ======================================================================
# 🔴 JAIL 1: sshd-aggressive (智能暴力破解防护)
# 防御目标:高级暴力破解攻击、预认证洪水、协议攻击等
# 威胁等级:★★★★★ (最高)
# ======================================================================
[sshd-aggressive]
# 启用此 jail
enabled = true

# 保护端口 - SSH 默认端口
port = 10000,20000

# 使用的过滤器 - 对应 /etc/fail2ban/filter.d/sshd-aggressive.conf
filter = sshd-aggressive

# 日志文件路径 - systemd 系统使用 journal
logpath = /var/log/auth.log

# 后端类型 - 使用 systemd journal
backend = systemd

# ❗ 关键安全参数:最大重试次数
# 3次失败即封禁 - 因为这是高级攻击,需要零容忍
# 普通暴力破解通常5次,这里更严格因为威胁更大
maxretry = 3

# ❗ 关键安全参数:检测时间窗口(秒)
# 60秒内累计3次失败 - 给予正常用户合理的时间窗口
# 同时能快速检测自动化攻击工具
findtime = 60

# ❗ 关键安全参数:基础封禁时长(秒)
# 1209600秒 = 14天 - 长期封禁高级攻击者
# 防止攻击者等待短时间后继续攻击
bantime = 1209600

# ❗ 高级功能:智能递增封禁
# 同一IP多次违规,封禁时间会指数级增长
# 防止攻击者使用僵尸网络轮番攻击
bantime.increment = true

# ❗ 递增因子:每次违规封禁时间乘以3
# 第1次:14天,第2次:42天,第3次:126天...
bantime.factor = 3

# ❗ 最大封禁时长限制(秒)
# 2592000秒 = 30天 - 防止无限增长,同时提供足够威慑
bantime.maxtime = 2592000

# ❗ 防火墙动作 - 使用 nftables
# 比 iptables 更高效,支持更大规模的封禁列表
banaction = nftables

# ❗ 防火墙链 - 在 input 链的最前面插入规则
# 确保最先匹配到封禁规则,提高性能
chain = input

# ❗ 协议类型 - SSH 使用 TCP
protocol = tcp

# ❗ 动作配置 - 使用基础动作模板
# 这里会应用上面定义的 action_ 模板
action = %(action_)s

# ======================================================================
# 🔵 JAIL 2: sshd-maxstartups (连接洪水防护)
# 防御目标:连接洪水攻击、端口扫描、资源耗尽攻击
# 威胁等级:★★★★☆ (高)
# ======================================================================
[sshd-maxstartups]
# 启用此 jail
enabled = true

# 保护端口 - SSH 默认端口
port = 10000,20000

# 使用的过滤器 - 对应 /etc/fail2ban/filter.d/sshd-maxstartups.conf
filter = sshd-maxstartups

# 日志文件路径 - systemd 系统使用 journal
logpath = /var/log/auth.log

# 后端类型 - 使用 systemd journal
backend = systemd

# ❗ 关键安全参数:最大重试次数
# 5次连接尝试即封禁 - 比 aggressive 宽松一些
# 因为正常用户可能会有短暂的重连,需要平衡安全性与可用性
maxretry = 5

# ❗ 关键安全参数:检测时间窗口(秒)
# 10秒内累计5次连接 - 非常短的时间窗口
# 专门检测快速连接洪水,正常用户不会在10秒内连接5次
findtime = 10

# ❗ 关键安全参数:封禁时长(秒)
# 3600秒 = 1小时 - 短期封禁
# 连接洪水通常是短暂的,1小时足够让攻击者放弃
# 同时避免误封合法用户
bantime = 3600

# ❗ 防火墙动作 - 使用 nftables
# 与 aggressive 使用相同的防火墙后端
banaction = nftables

# ❗ 防火墙链 - 在 input 链
chain = input

# ❗ 协议类型 - SSH 使用 TCP
protocol = tcp

# ❗ 动作配置 - 使用基础动作模板
# 不使用递增封禁,因为连接洪水通常是单次攻击
action = %(action_)s

# ======================================================================
# 🔴 JAIL 3: 基础 SSH 防护 (保留你原有的配置)
# 防御目标:常规密码暴力破解
# ======================================================================
[customize-sshd]
# 启用该规则
enabled = true
# 监控 SSH 端口(22)
port = 10000,20000
# 使用 sshd 过滤规则
filter = sshd
# 监控的日志文件
logpath = /var/log/auth.log
backend = systemd
# 5 次失败触发封禁
maxretry = 5
# 1 小时内统计失败次数
findtime = 3600
# 封禁 1 天
bantime = 86400
# IPv6 处理方式,auto 表示自动检测
allowipv6 = auto

# ======================================================================
# 🔴 JAIL 4: 低频/预认证攻击防护 (新增配置)
# 防御目标:你遇到的每10分钟试探一次的低密度攻击
# ======================================================================
[customize-sshd-low-freq]
enabled = true
port = 10000,20000
# 核心:使用 sshd 过滤器,并开启 aggressive 模式
filter = sshd[mode=aggressive]
logpath = /var/log/auth.log
backend = systemd
# 1小时内累计3次预认证断开即封禁
maxretry = 3
findtime = 3600
# 封禁 1 天
bantime = 86400

[sshd-backstop-invalid-banner]
# 是否启用该防护规则,true开启 false关闭
enabled = true
# 日志读取源:auto自动识别journald或/var/log/auth.log
logpath = auto
# 使用上面我们自定义的过滤器文件名(无需带.conf后缀)
filter = sshd-backstop
# 最大允许触发次数:同一IP出现2次非法SSH握手即判定为攻击
maxretry = 2
# 封禁时长:单位秒,86400 = 1天
bantime = 86400
# 查找窗口:最近10分钟内的日志会参与计数(600秒)
findtime = 3600
# 封禁动作,适配你的nftables防火墙
# banaction = nftables-multiport
# 防护端口:修改过端口同步替换此处
port = 10000,20000

nginx 配置

添加完成后,要重启fail2ban服务

可能没办法直接使用fail2ban提供的nginx防护能力,可以用作参考,需要手动添加自定义的文件

添加nginx错误日志检测规则

创建一个nginx-error-customize-banIP.conf文件,存放以下内容;注意文件保存的格式,把这个文件放入到/etc/fail2ban/filter.d/

1
2
3
4
5
6
7
8
9
10
11
12
13
[INCLUDES]
before = common.conf

[Definition]
# 修复1: 修正 datepattern 语法,使用单 %,并匹配日志中的完整时间格式
# datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S

# 规则1:nginx deny规则拦截
failregex = \[error\].*access forbidden by rule, client: <HOST>


ignoreregex =

匹配日志格式如下:

1
2026/06/28 16:01:01 [error] 926#926: *845 access forbidden by rule, client: 2409:8c44:9400:1:0:4:100:8, server: www.goupos.cn, request: "GET /categories/Verilog%E8%AF%AD%E6%B3%95/ HTTP/2.0", host: "www.goupos.cn"

验证上面语法是否对日志生效

1
2
# 检查过滤器语法
sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-error-customize-banIP.conf --print-all-matched

验证结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Running tests
=============

Use failregex filter file : nginx-error-customize-banIP, basedir: /etc/fail2ban
Use datepattern : {^LN-BEG} : Default Detectors
Use log file : /var/log/nginx/error.log
Use encoding : UTF-8


Results
=======

Failregex: 12 total
|- #) [# of hits] regular expression
| 1) [12] ^.*\[error\].* access forbidden by rule, client: <HOST>
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
| [18] {^LN-BEG}ExYear(?P<_sep>[-/.])Month(?P=_sep)Day(?:T| ?)24hour:Minute:Second(?:[.,]Microseconds)?(?:\s*Zone offset)?
`-

Lines: 18 lines, 0 ignored, 12 matched, 6 missed
[processed in 0.00 sec]

|- Matched line(s):
| 2026/06/28 01:28:38 [error] 1605418#1605418: *4624 access forbidden by rule, client: 119.0.68.61, server: www.goupos.cn, request: "GET /page/9/ HTTP/2.0", host: "www.goupos.cn"

添加ningx访问日志检测规则

创建文件 /etc/fail2ban/filter.d/nginx-protocol-customize-scan.conf,写入以下内容:加入customize好区分是不是自己写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[INCLUDES]
before = common.conf

[Definition]
# 匹配包含 RDP 探测特征和 SSH 探测特征的 400 444错误日志
# 规则1: RDP探测包 \x03\x00\x00 + mstshash Cookie,返回400
failregex = ^<HOST> - \S+ \[.*?\] "\\x03\\x00\\x00.*Cookie: mstshash=.*" 400 .*$
# 规则2: SSH直连探测 SSH-2.0- 开头请求,返回400
^<HOST> - \S+ \[.*?\] "SSH-2\.0-.*" 400 .*$
# 规则3: 同时满足状态码400/444 + 日志结尾 "-" "-" "-",第四段和第四段以后忽略
^<HOST> - - \[.*?\] ".*?" (400|444) .* "-" "-" "-"
# 规则4:HTTP/1.0 恶意请求 400/444,强制三段"-",第四段和第四段以后忽略
^<HOST> - - \[.*?\] ".* HTTP\/1\.0" (400|444) .* "-" "-" "-"
# 规则5:新增 —— 444状态码,XFF字段为"-",UA不限,第四段和第四段以后忽略
^<HOST> - - \[.*?\] ".*" 444 \d+ ".*" ".*" "-"

ignoreregex =

匹配日志格式如下:

1
2
45.227.254.155 - - [28/Jun/2026:05:58:31 +0800] "\x03\x00\x00/*\xE0\x00\x00\x00\x00\x00Cookie: mstshash=Administr" 400 150 "-" "-" "-"
139.59.78.146 - - [28/Jun/2026:12:35:33 +0800] "SSH-2.0-OpenSSH" 400 150 "-" "-" "-"

验证上面语法是否对日志生效

1
2
# 检查过滤器语法
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-protocol-customize-scan.conf --print-all-matched

验证结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Running tests
=============

Use failregex filter file : nginx-protocol-customize-scan, basedir: /etc/fail2ban
Use datepattern : {^LN-BEG} : Default Detectors
Use log file : /var/log/nginx/access.log
Use encoding : UTF-8


Results
=======

Failregex: 3 total
|- #) [# of hits] regular expression
| 1) [2] ^<HOST> - \S+ \[.*?\] "\\x03\\x00\\x00.*Cookie: mstshash=.*" 400 .*$
| 2) [1] ^<HOST> - \S+ \[.*?\] "SSH-2\.0-.*" 400 .*$
`-

Ignoreregex: 0 total

Date template hits:

Lines: 332 lines, 0 ignored, 3 matched, 329 missed
[processed in 0.04 sec]

|- Matched line(s):
| 194.165.16.167 - - [28/Jun/2026:00:51:28 +0800] "\x03\x00\x00/*\xE0\x00\x00\x00\x00\x00Cookie: mstshash=Administr" 400 150 "-" "-" "-"
| 93.123.109.72 - - [28/Jun/2026:05:41:11 +0800] "\x03\x00\x00/*\xE0\x00\x00\x00\x00\x00Cookie: mstshash=Administr" 400 150 "-" "-" "-"
| 139.59.78.146 - - [28/Jun/2026:12:46:06 +0800] "SSH-2.0-OpenSSH" 400 150 "-" "-" "-"
`-
Missed line(s): too many to print. Use --print-all-missed to print all 329 lines

添加过滤器配置

/etc/fail2ban/jail.d目录下添加一个nginx-defense.conf文件,存放以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 规则:匹配 access forbidden by rule 拒绝访问拉黑(自定义filter)
[nginx-errorlog-customize-banIP]
enabled = true
logpath = /var/log/nginx/error.log
# 自己写的filter,只匹配 access forbidden by rule
filter = nginx-error-customize-banIP
maxretry = 1
findtime = 3600
# 封禁30天
bantime = 2592000
# 启用递增封禁
bantime.increment = true
# 递增因子
bantime.factor = 2
# 最大封禁时间
bantime.maxtime = 2592000
allowipv6 = auto
backend = pyinotify
action = nftables-multiport[name=nginx-customize-tcp, port="80,443,10000", protocol=tcp]
nftables-multiport[name=nginx-customize-udp, port="80,443,10000", protocol=udp]

backend = polling 的作用是强制 Fail2ban 以轮询文件的方式读取日志,这样它就会严格使用您在 logpath 中指定的文件路径。

  • pyinotify:本地日志文件(如/var/log/auth.log)

  • systemd:systemd管理的日志(journald)

action = iptables-multiport[name=xxx, port="端口", protocol=tcp]

  • name=xxx:生成独立 iptables 链,区分限流 / 403 两种封禁来源,排查方便
  • port:封禁后该 IP 无法访问列表内所有 TCP 端口
  • 底层动作固定为 DROP,服务器直接丢弃数据包,不回响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[nginx-protocol-customize-scan]
enabled = true
port = 80,443,10000
filter = nginx-protocol-customize-scan
logpath = /var/log/nginx/access.log
findtime = 3600
maxretry = 1
bantime = 2592000
# 启用递增封禁
bantime.increment = true
# 递增因子
bantime.factor = 2
# 最大封禁时间
bantime.maxtime = 2592000
allowipv6 = auto
backend = pyinotify
action = nftables-multiport[name=nginx-protocol-customize-scan-tcp, port="80,443,10000", protocol=tcp]
nftables-multiport[name=nginx-protocol-customize-scan-udp, port="80,443,10000", protocol=udp]

RustDesk配置

如果在调试rustdesk,不建议开启,以下配置会很严格,配置错误就会导致IP被封禁

添加过滤规则

创建一个rustdesk.conf文件,存放以下内容;注意文件保存的格式,把这个文件放入到/etc/fail2ban/filter.d/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[INCLUDES]
before = common.conf

[Definition]
# 强制匹配核心特征,<HOST> 会自动识别并提取 IPv4 和 ::ffff:IPv6 格式的地址
# Relay authentication failed from [::ffff:217.146.80.105]:11392 - invalid key
failregex = Relay authentication failed from \[<HOST>\]:\d+ - invalid key$
# Authentication failed from [::ffff:1.1.2.133]:43808 for peer 120724860 - invalid key
Authentication failed from \[<HOST>\]:\d+ for peer \d+ - invalid key$


ignoreregex =

# ====================== 日志读取性能优化 ======================
# maxlines:单次日志读取最多扫描连续行数,默认1
# 设置3是兼容多行报错日志(同一次攻击输出多条关联错误),兼顾识别不漏报与性能开销
maxlines = 3

# ====================== 编码兼容设置 ======================
# encoding:日志文件读取编码格式
# auto自动识别系统默认编码(UTF-8/ASCII),避免中文/特殊字符日志导致匹配失效
encoding = auto

maxmatches = 200

添加过滤器配置

/etc/fail2ban/jail.d目录下添加一个rustdesk.conf文件,存放以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Rustdesk HBBR HBBS 中继服务器防护监狱
[rustdesk]

# 启用该防护规则,true开启 / false关闭
enabled = true

# 绑定对应的自定义日志过滤规则文件 filter.d/rustdesk.conf
filter = rustdesk

# 监控 Rustdesk HBBR HBBS 程序输出的日志文件路径
logpath = /var/log/rustdesk-hbbr.log
/var/log/rustdesk-hbbs.log

# 需要封禁的端口:Rustdesk HBBR HBBS 默认业务端口,黑名单阻断这些端口入站
port = 21115,21116,21117,21118,21119

# 阈值:1小时内出现1次恶意行为,直接触发封禁,
maxretry = 1
# 检测时间窗口:统计最近3600秒(1小时)内的异常记录
findtime = 3600
# 封禁时长:2592000秒 = 30天,拉黑IP一个月
bantime = 2592000
# 利用Linux内核inotify事件机制实时监听日志文件变更
backend = pyinotify
# 启用递增封禁
bantime.increment = true
# 递增因子
bantime.factor = 2
# 最大封禁时间
bantime.maxtime = 2592000
# IPv6处理策略:auto 自动跟随系统是否启用IPv6防火墙规则
allowipv6 = auto

# 封禁动作,留空会使用全局[DEFAULT]段定义的banaction(nftables/iptables-ipset等)
action = nftables-multiport[name=%(filter)s-tcp, port="%(port)s", protocol=tcp, chain="%(chain)s"]
nftables-multiport[name=%(filter)s-udp, port="%(port)s", protocol=udp, chain="%(chain)s"]

ICMP洪水防御配置(未测试)

添加过滤规则

创建一个icmp-flood.conf文件,存放以下内容;注意文件保存的格式,把这个文件放入到/etc/fail2ban/filter.d/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[INCLUDES]
before = common.conf

[Definition]
failregex = ^.*UFW_ICMP_FLOOD: .* SRC=<HOST>


ignoreregex =

# ====================== 日志读取性能优化 ======================
# maxlines:单次日志读取最多扫描连续行数,默认1
# 设置3是兼容多行报错日志(同一次攻击输出多条关联错误),兼顾识别不漏报与性能开销
maxlines = 3

# ====================== 编码兼容设置 ======================
# encoding:日志文件读取编码格式
# auto自动识别系统默认编码(UTF-8/ASCII),避免中文/特殊字符日志导致匹配失效
encoding = auto

maxmatches = 200

添加过滤器配置

/etc/fail2ban/jail.d目录下添加一个icmp-flood.conf文件,存放以下内容

1
2
3
4
5
6
7
8
9
10
[icmp-flood]
enabled = true
port = all
filter = icmp-flood
logpath = /var/log/syslog
maxretry = 10 # 如果在 findtime 内出现 10 次上述日志
findtime = 60 # 时间窗口 60 秒
bantime = 3600 # 封禁 1 小时
backend = pyinotify
action = ufw # 使用 ufw 动作进行封禁

工作原理:采用“令牌桶”机制。前 10 个包(burst)和之后每秒 5 个包(limit)会被直接 ACCEPT 放行。只有当攻击者发送的 Ping 频率超过了这个阈值,多出来的包才会命中后面的 LOG 和 DROP 规则。
对 Fail2ban 的影响:Fail2ban 监控的是 UFW_ICMP_FLOOD 前缀的日志。这意味着,只有真正的攻击者(高频 Ping)才会被 Fail2ban 封禁。正常的运维人员 Ping 一下服务器,不会产生日志,也不会被封禁。

待匹配日志数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
71.6.239.192 - - [1/Jun/2026:00:07:06 +0800] "" 400 0 "-" "-" "-"
71.6.239.192 - - [1/Jun/2026:00:07:08 +0800] "\x80\x00\x00(r\xFE\x1D\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xA0\x00\x01\x97|\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 400 150 "-" "-" "-"
71.6.239.192 - - [1/Jun/2026:00:07:09 +0800] "\x00\x1E\x00\x06\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07version\x04bind\x00\x00\x10\x00\x03" 400 150 "-" "-" "-"
71.6.239.192 - - [1/Jun/2026:00:07:09 +0800] "HELP" 400 150 "-" "-" "-"
71.6.239.192 - - [1/Jun/2026:00:07:10 +0800] "\x16\x03\x00\x00S\x01\x00\x00O\x03\x00?G\xD7\xF7\xBA,\xEE\xEA\xB2`~\xF3\x00\xFD\x82{\xB9\xD5\x96\xC8w\x9B\xE6\xC4\xDB<=\xDBo\xEF\x10n\x00\x00(\x00\x16\x00\x13\x00" 400 150 "-" "-" "-"
71.6.239.192 - - [1/Jun/2026:00:07:12 +0800] "\x16\x03\x01\x00\xEE\x01\x00\x00\xEA\x03\x03[Y\xE8\xA4#-\x93\xFB\x05uU&\xF4\xC1\x05\xC2b\xE4\xDE" 400 150 "-" "-" "-"

101.69.207.118 - - [27/Jun/2026:03:04:48 +0800] "GET /api/exclude/siteConfig/register HTTP/1.1" 444 0 "-" "okhttp/3.14.9" "-"
221.178.143.70 - - [27/Jun/2026:04:00:03 +0800] "GET / HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Linux; U; Android 5.1.1; zh-CN; SM-C7000 Build/MMB29M; 720*1080) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/40.0.2214.89 UCBrowser/11.6.2.948 Mobile Safari/537.36" "-"
221.178.143.70 - - [27/Jun/2026:04:00:04 +0800] "GET /favicon.ico HTTP/1.1" 444 0 "-" "Dalvik/2.1.0 (Linux; U; Android 9.0; ZTE BA520 Build/MRA58K)" "-"
221.178.143.70 - - [27/Jun/2026:04:00:14 +0800] "GET / HTTP/1.1" 444 0 "-" "Dalvik/2.1.0 (Linux; U; Android 9.0; ZTE BA520 Build/MRA58K)" "-"
221.178.143.70 - - [27/Jun/2026:04:14:46 +0800] "GET / HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11) AppleWebKit/601.1.27 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/601.1.27" "-"

124.117.194.104 - - [27/Jun/2026:08:54:05 +0800] "\x16\x03\x01\x05\xAC\x01\x00\x05\xA8\x03\x03X\xD7\xE6\xD8\x9B'\x8D\x02+Zi\xEC\xC4\x8F3&Q\xAB\x96w\xFB\xE8\xD5\xCE\x13\x90t\xF8\xFA9\xCAn |\xA7\x94\x1B\xA61M\xB1\x0E\xF5;\xCE\x1F\xF7j\xA2]^\xB2\x02\x90^W9Y\xCD\x8B\x81\xD5`\xA2\xAC\x00\x1A\xC0+\xC0/\xC0,\xC00\xCC\xA9\xCC\xA8\xC0\x09\xC0\x13\xC0" 400 150 "-" "-" "-"
222.176.200.74 - - [27/Jun/2026:08:54:06 +0800] "\x16\x03\x01\x05\xAC\x01\x00\x05\xA8\x03\x03o\xEA\x15\xED(\x1D\x0C{Y\xEF\x08\xECEY!q\x13\x02\x11]\xAF\x12\xD9wC\x86A#\x19\xBA\xEA\x95 \x80#\xBB\xB8Z\xA1(\xF8\xDD\xBE\xA0a\x91#&\x94i\xAF\xBEZ\xBB\x0B\xE9}I\xFEah\xAC\x10\xB7\xC3\x00\x1A\xC0+\xC0/\xC0,\xC00\xCC\xA9\xCC\xA8\xC0\x09\xC0\x13\xC0" 400 150 "-" "-" "-"
123.138.72.195 - - [27/Jun/2026:08:54:33 +0800] "USER anonymous" 400 150 "-" "-" "-"
185.132.176.32 - - [27/Jun/2026:20:22:40 +0800] "\x12\x01\x00/\x00\x00\x00\x00\x00\x00\x1A\x00\x06\x01\x00 \x00\x01\x02\x00!\x00\x01\x03\x00\x22\x00\x04\x04\x00&\x00\x01\xFF\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01" 400 150 "-" "-" "-"
101.133.224.200 - - [27/Jun/2026:21:08:37 +0800] "" 400 0 "-" "-" "-"
101.133.224.200 - - [27/Jun/2026:21:08:38 +0800] "POST /sdk HTTP/1.1" 400 650 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
101.133.224.200 - - [27/Jun/2026:21:08:38 +0800] "GET /nmaplowercheck1782565717 HTTP/1.1" 400 650 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
101.133.224.200 - - [27/Jun/2026:21:08:38 +0800] "GET /HNAP1 HTTP/1.1" 400 650 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
101.133.224.200 - - [27/Jun/2026:21:08:38 +0800] "GET /evox/about HTTP/1.1" 400 650 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
101.133.224.200 - - [27/Jun/2026:21:08:58 +0800] "GET / HTTP/1.1" 400 248 "-" "-" "-"
171.36.7.191 - - [27/Jun/2026:08:54:06 +0800] "GET / HTTP/1.1" 444 0 "-" "-" "-"
101.133.224.200 - - [27/Jun/2026:21:08:38 +0800] "POST /sdk HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
101.133.224.200 - - [27/Jun/2026:21:08:38 +0800] "GET /nmaplowercheck1782565717 HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
101.133.224.200 - - [27/Jun/2026:21:08:38 +0800] "GET /HNAP1 HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
101.133.224.200 - - [27/Jun/2026:21:08:38 +0800] "GET /evox/about HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
101.133.224.200 - - [27/Jun/2026:21:08:58 +0800] "GET / HTTP/1.1" 444 0 "-" "-" "-"

223.90.190.188 - - [27/Jun/2026:00:19:48 +0800] "GET / HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" "-"
112.111.232.48 - - [27/Jun/2026:01:09:16 +0800] "GET / HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" "-"
42.234.195.196 - - [27/Jun/2026:01:21:20 +0800] "GET / HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" "-"
39.154.11.67 - - [27/Jun/2026:01:31:19 +0800] "GET / HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" "-"
116.196.103.2 - - [27/Jun/2026:04:38:31 +0800] "GET / HTTP/1.1" 444 0 "-" "BaiduSpider,Sogou,Yisou,Haosou,360Spider,BlackBerry,IEMobile,MQQBrowser,JUC,Fennec,wOSBrowser,Googlebot,AndroidBaiduSpider,Sogou,Yisou,Haosou,360Spider,BlackBerry,IEMobile,MQQBrowser,JUC,Fennec,wOSBrowser,Googlebot,Android JDCLOUD-SECURITY/ContentDetect" "-"
121.37.87.48 - - [27/Jun/2026:16:16:59 +0800] "GET /tags/IPV4/ HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36" "-"
43.135.120.90 - - [27/Jun/2026:16:17:37 +0800] "GET /%E5%AF%86%E7%A0%81/ HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36" "-"
129.226.207.99 - - [27/Jun/2026:16:17:42 +0800] "GET /pdf%E8%AE%BE%E7%BD%AE/ HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36" "-"
185.132.176.32 - - [27/Jun/2026:19:33:16 +0800] "\x12\x01\x00/\x00\x00\x00\x00\x00\x00\x1A\x00\x06\x01\x00 \x00\x01\x02\x00!\x00\x01\x03\x00\x22\x00\x04\x04\x00&\x00\x01\xFF\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01" 400 150 "-" "-" "-"

报错数据

查看

1
2
3
2026-06-29 20:49:17,894 fail2ban.filter         [668]: WARNING [nginx-protocol-customize-scan] Simulate NOW in operation since found time has too large deviation None ~ 1782737357.8943663 +/- 60
2026-06-29 20:49:17,894 fail2ban.filter [668]: WARNING [nginx-protocol-customize-scan] Please check jail has possibly a timezone issue. Line with odd timestamp: x.x.x.x - - [29/Jun/2026:20:49:17 +0800] "GET /%E7%BC%96%E7%A8%8B/Verilog/list_of_terminal_names HTTP/1.1" 206 53225 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36" "x.x.x.x,x.x.x.x"

修改/etc/fail2ban/jail.d目录下nginx-defense.conf文件,添加这个logtimezone = +0800

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[nginx-protocol-customize-scan]
enabled = true
port = 80,443,10000
filter = nginx-protocol-customize-scan
logtimezone = +0800
logpath = /var/log/nginx/access.log
maxretry = 1
findtime = 3600
bantime = 2592000
# 启用递增封禁
bantime.increment = true
# 递增因子
bantime.factor = 2
# 最大封禁时间
bantime.maxtime = 2592000
allowipv6 = auto
backend = pyinotify
action = nftables-multiport[name=nginx-protocol-customize-scan-tcp, port="80,443,10000", protocol=tcp]
nftables-multiport[name=nginx-protocol-customize-scan-udp, port="80,443,10000", protocol=udp]

扩大时间偏差容错(如果有大量旧归档日志)

在 jail 增加 maxlines/timemaxoffset,允许更大时间差:

1
2
3
# 允许前后 3600 秒(1小时)时间偏差
timemaxoffset = 3600
logtimezone = +0800