fail2ban 十天只记 1 次失败:先验证它到底在读哪份日志

举报
逍遥渝枫 发表于 2026/11/06 08:46:13 2026/11/06
【摘要】 只允许密钥登录的 22.04 云主机上,fail2ban 跑了十天,Total failed 只有 1、banned 为 0,sshd 日志里却躺着几十行扫描痕迹。这篇不讲怎么装,只讲怎么自证:它读 journal 还是文件、failregex 认哪些行、封禁有没有落进 iptables、ufw reload 会不会冲掉规则,附四步验证命令与八条报错对照。

加固完一台 Ubuntu 22.04 的云主机(1.7G 内存,公网直连),隔了十天回去看 fail2ban 的统计:

fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed:	1
|  |- Total failed:	1
|  `- Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:

十天,1 次失败,0 次封禁。同一台机器的 sshd journal 有 444 条条目,其中 47 行是"连接被断开"、5 行是"Invalid user"、34 行是协议协商阶段被对面直接掐断。

两个数字差了一个量级。这时候最容易做的两件事——把 maxretry 从 5 改成 3,或者怀疑服务没启动——都是猜。下面四步能把"它在读什么、认什么、封没封、会不会被冲掉"逐条钉死,而且每一步都能复现。

一、先确认它读的是哪条流

backend = systemd 的 jail 读的是 journal,不是文件。这一条决定了后面所有排查方向:日志里没有、/var/log/auth.log 里也翻不到,都是正常的。

# /etc/fail2ban/jail.local
[DEFAULT]
bantime  = 3600
findtime = 600
maxretry = 5
backend  = systemd

[sshd]
enabled = true
backend = systemd

判断依据不在配置文件里,而在 status 输出的那一行:

  • 走 journal 的 jail 显示 Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
  • 走文件的 jail 显示 File list: /var/log/auth.log

两个反直觉的地方,我在 0.11.2 上都撞到了:

fail2ban-client get sshd backend
# ERROR NOK: ('Invalid command (no get action or not yet implemented)',)

fail2ban-client get sshd logpath
# No file is currently monitored

第一行报错不是配置错,0.11.2 根本没实现这个 getter;第二行说的是实话——systemd 后端没有文件可监控。看到 No file is currently monitored 就跑去 tail -f /var/log/auth.log 找线索,是最常见的白跑一趟。

二、把"读什么"和"认什么"分开验证

fail2ban-client status 只给汇总数,fail2ban-regex 能给每一行的去向。用同一个 journalmatch 跑一遍:

fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf \
  --journalmatch "_SYSTEMD_UNIT=sshd.service + _COMM=sshd"

我这台机器的真实输出(截关键部分):

Failregex: 142 total
|-  #) [# of hits] regular expression
|   6) [5] ^[iI](?:llegal|nvalid) user <F-USER>.*?</F-USER> from <HOST>...
|  19) [8] ^<F-NOFAIL>Received <F-MLFFORGET>disconnect</F-MLFFORGET>...
|  20) [38] ^<F-NOFAIL><F-MLFFORGET>(Connection closed|Disconnected)</F-MLFFORGET>...
|  21) [91] ^<F-MLFGAINED>Accepted \w+</F-MLFGAINED>...

Lines: 1006 lines, 137 ignored, 5 matched, 864 missed

三个数字要连起来读:

  • matched 5:真正被当成"失败事件"的行,全部来自第 6 条正则(Invalid user)。
  • ignored 137:命中了正则,但正则里带 <F-NOFAIL> 标记,直接扔掉。第 19(Received disconnect)、20(Connection closed)、21(Accepted publickey)都是这一类。
  • missed 864:连正则都没碰到,包括 kex_exchange_identification 这种协议层报错。

注意 Lines: 1006 和 journalctl -u ssh -o short-iso | wc -l 得到的 444 不是一回事:journal 里有些条目带多行字段,fail2ban 按行读,所以行数更多。做对照时别拿这两个数互相校验。

三、几十行扫描痕迹,为什么只算一两次失败

把 journal 拉成一行一条,再按模式点一遍数:

journalctl -u ssh --no-pager -o short-iso > /tmp/ssh.log
wc -l /tmp/ssh.log

for p in "Invalid user" "Failed password" "Connection closed by" \
         "kex_exchange_identification" "Received disconnect" \
         "Accepted publickey" "Unable to negotiate" "banner exchange"; do
  printf "%-38s %s\n" "$p" "$(grep -c "$p" /tmp/ssh.log)"
done

实测结果:

444                                    (总行数)
Invalid user                           5
Failed password                        0
Connection closed by                   47
kex_exchange_identification            34
Received disconnect                    9
Accepted publickey                     93
Unable to negotiate                    2
banner exchange                        4

Failed password 是 0,这一行是全文的关键。因为 PasswordAuthentication no 之后,扫描器连口令阶段都进不去——它枚举用户名、被拒、断开,全程不会出现一条"密码错误"。而 fail2ban 的 sshd 过滤器,主要认的就是"密码错误"和"非法用户"这两类。

对应到日志行的处理方式:

日志行 是否计入失败 原因
Invalid user ubuntu from 117.157.89.25 计入 命中 failregex #6,这也是唯一贡献计数的一类
Failed password for root from ... 计入 但本机 PasswordAuthentication no,实际 0 行
Connection closed by authenticating user root ... [preauth] 不计入 命中 #20,带 <F-NOFAIL>,被归入 ignored
error: kex_exchange_identification: Connection closed by remote host 不计入 prefregex 剥掉 error: 前缀后无匹配,计入 missed
Received disconnect from ...: 11: Bye Bye [preauth] 不计入 命中 #19,<F-NOFAIL>,正常断开不惩罚
Accepted publickey for root from ... 不计入 命中 #21,自己的登录成功记录
Unable to negotiate with ... no matching key exchange method 不计入 missed,过滤器不认协商失败

结论有点反常识:在只允许密钥登录的机器上,fail2ban 的 sshd jail 基本处于空转状态。它统计的是"认证失败",而绝大多数扫描在认证之前就被掐断了。它记到的那 1 次,是窗口内唯一一条真正走到用户名枚举的请求。

顺带说清它怎么算"一次":同一连接的多行日志用 [preauth] 和内部的连接标识串起来,只有最后判定失败的那一行进计数器,findtime 内累计到 maxretry 才触发 banaction。所以调 maxretry 之前先确认有行能命中——没有命中行,把数字调到 1 也不会封任何人。

另一个要提前想清楚的代价:封禁粒度是 IP,不是行为。办公出口是 NAT 的时候,一个人连错几次,整间办公室一起被 REJECT 一小时。要么把自己的固定出口写进 ignoreip,要么把 bantime 压到几分钟级别,别一上来就 24 小时。

这也解释了 Total failed: 1 与 Invalid user: 5 的差距:计数器是内存里的状态,跟着 jail 的生命周期走,不是历史累计。我执行 fail2ban-client reload --restart sshd 之后,它立刻回到 Total failed: 0、Total banned: 1(这个 1 是我下面测试封禁留下的)。所以这个数字既不能当扫描量看,也不能当防护效果看。

四、验证封禁真的落到了 iptables 上

不封一次人,你永远不知道 banaction 通不通。用一个保留网段的地址做靶子,测完立刻解封:

fail2ban-client set sshd banip 203.0.113.7
iptables -S | grep -i f2b
-N f2b-sshd
-A INPUT -p tcp -m multiport --dports 22 -j f2b-sshd
-A f2b-sshd -s 203.0.113.7/32 -j REJECT --reject-with icmp-port-unreachable
-A f2b-sshd -j RETURN

三个细节值得记下来:

  1. 跳转在 INPUT 的第 1 位。iptables -L INPUT -n --line-numbers 里,f2b-sshd 排在 ufw-before-logging-input 之前,所以封禁优先于 ufw 对 22 端口的放行,不存在"ufw 先 ACCEPT 导致 fail2ban 拦不住"的问题。
  2. 链只在第一次封人时才创建。封禁前 iptables -S | grep -ci f2b 返回 0,执行 reload --restart sshd 之后仍然是 0,直到 banip 才出现。所以"看不到 f2b 链"不能证明 fail2ban 没生效,只能证明它从没封过人。
  3. REJECT 而不是 DROP,--reject-with icmp-port-unreachable。对方会立刻收到拒绝,不会挂在超时里。

测完记得收:

fail2ban-client set sshd unbanip 203.0.113.7
fail2ban-client status sshd | tail -4

五、ufw reload 会不会把规则冲掉

网上一直有"ufw reload 会清掉 fail2ban 规则"的说法。我在同一台机器上实测了一遍:

iptables -S | grep -ci f2b        # 封禁后为 3
ufw reload                        # Firewall reloaded
iptables -S | grep -ci f2b        # 仍为 3
iptables -S INPUT | head -3

f2b-sshd 链和 INPUT 里的跳转都还在。这台机器是 ufw 0.36 + iptables 1.8.7(nf_tables 后端)。老版本或 legacy 后端上确实有人复现过规则被清,所以这条结论只对当前组合负责——升级或换后端之后,请自己再跑一次这两行,别直接搬。

六、报错与现象对照表

现象 原因 处理
get sshd backend 报 Invalid command (no get action or not yet implemented) 0.11.2 未实现该 getter,不是配置错误 看 status 里的 Journal matches / File list 行
get sshd logpath 返回 No file is currently monitored systemd 后端不监控文件 属正常,不用去找 auth.log
iptables -S | grep f2b 为空 从未封禁过,链未创建 用 banip 打一发再确认
reload 后 Total failed 归零 计数器在内存,随 jail 生命周期重置 历史统计看 journal,不看这个数
大量扫描行,Total banned 一直是 0 那些行带 <F-NOFAIL> 或 missed;密码登录已关 属正常,别急着调 maxretry
banip 后对方仍能连 命中 ignoreip(默认含 127.0.0.1/8、::1)或规则被其他工具 flush fail2ban-client get sshd ignoreip;再查 INPUT 位次
fail2ban.log 里找不到 Jail 'sshd' started 日志按周轮转,启动记录已归档 zgrep -h "started" /var/log/fail2ban.log.*.gz
status 报 jail 不存在 jail.d/ 里的同名段覆盖了 jail.local,或 enabled 未置 true fail2ban-client -d | grep -A6 "jail 'sshd'" 看合并后的结果

七、把自证变成一条命令

每次改完配置,用这段跑一遍,四个数对上就算通了:

set -u
echo "--- jail 读哪条流"
fail2ban-client status sshd | sed -n '/Filter/,/Actions/p'

echo "--- 最近 24h 的候选失败行"
journalctl -u ssh --since "-24h" --no-pager -o short-iso \
  | grep -cE "Invalid user|Failed password"

echo "--- 过滤器实跑"
fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf \
  --journalmatch "_SYSTEMD_UNIT=sshd.service + _COMM=sshd" | tail -4

echo "--- 规则是否落地"
iptables -S | grep -ci f2b

八、边界

  • 本文所有输出都在 Ubuntu 22.04 + fail2ban 0.11.2 + ufw 0.36 + iptables 1.8.7 nf_tables 上得到。fail2ban 1.x 的命令与输出有变化(1.x 提供了 get backend),0.10 的 journalmatch 行为也不完全一致,换版本请重新验证。
  • 只测了 sshd 这一个 jail。nginx-*、postfix 等 jail 的 failregex 与 NOFAIL 标记各不相同,不能套用本文的命中数结论。
  • 容器里跑 fail2ban 且没有挂载宿主 journal 时,systemd 后端会直接读不到东西,那种场景要用 backend = polling 加文件日志。
  • 更重要的边界:fail2ban 是"事后计数"的工具,不是边界防护。这台机器上真正挡住扫描的是安全组只放行 22/80/443、ufw 默认拒绝、以及关掉密码登录。指望它替你挡量,会把配置调得越来越激进,最后误封自己。

回到开头那个数字。Total failed: 1 不是故障信号,也不是防护成绩单,它只说明:十天里只有一个请求走到了用户名枚举这一步,其余全在更早的阶段被拦掉了。这个结论比"把 maxretry 改成 3"有用得多。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。