在 Ubuntu 24.04 上用 Fail2ban 拦截 SSH 攻击
在 Ubuntu 24.04 上安装并配置 Fail2ban,在防火墙层面封禁暴力破解 SSH 的攻击者:确认原装监狱、调整封禁策略、从把自己锁在外面的情况中恢复。
Fail2ban 到底做什么
Fail2ban 是一个读取日志的守护进程(daemon)。它监视您的 SSH 认证消息,当同一个地址在短时间内失败若干次后,就运行一条防火墙命令,把这个地址封禁一段时间。这就是它的全部思路。它的配置只有一个文件、大约三十行,在 Ubuntu 24.04 上只需一条 apt 命令即可安装,在您还没编辑任何东西之前就已经受到保护。
请弄清它是什么、不是什么。Fail2ban 不做身份认证,不做任何加密,也拦不住某个铁了心的单次登录尝试,它只针对来自同一来源的反复尝试。它是一个噪声过滤器和限速器,不是一把锁。它的职责是让持续不断地扫描 22 端口的背景流量停止浪费您的 CPU、带宽和日志空间,并拖慢任何只能一个地址一个地址来的攻击者。
Fail2ban 不能替代什么
Fail2ban 是第三层防护,不是第一层。如果您的服务器仍然接受 SSH 密码,那么分布在成千上万个地址上的僵尸网络(botnet)就可以持续猜测,因为每个地址都停在您的封禁阈值之下,永远触发不了封禁。真正能防住这种情况的是仅密钥认证,它让密码猜测彻底不可能,无论谁尝试多少次都没用。在仅密钥认证之上再加 Fail2ban 有两个实用价值:它把暴力破解的噪声从日志里剔除,并及早赶走扫描器,让它们不再猛敲端口。请把它当作纵深防御的一环。它位于密钥认证和防火墙之后,绝不在它们之前。
前提条件,以及 Ubuntu 24.04 的实际情况
您需要一台运行 Ubuntu 24.04 的 VPS,拥有 root 或 sudo 权限,并且 SSH 已经能用,最好已经用上密钥认证。Fail2ban 很省资源:占用几十兆内存,无需调整任何上限。
现在讲每一份旧指南都搞错的地方。多年来的标准建议是"装好 Fail2ban,然后加上 backend = systemd,因为 Ubuntu 不再写 /var/log/auth.log 了"。这条建议描述的是一个真实的变化:现代的服务器和云镜像不再自带 rsyslog,所以 SSH 只把日志写入 systemd 日志(journal),那个文本文件已经没有了。但在 Ubuntu 24.04 上,Fail2ban 软件包已经把这件事处理好了。软件包会放入 /etc/fail2ban/jail.d/defaults-debian.conf,您的服务器实际运行的是这个文件,而不是上游的默认值:
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
backend = systemd
[sshd]
enabled = true请仔细读这段,因为它在您动手之前就先解决了两个问题。backend = systemd 意味着 SSH 监狱(jail)读取 journal,所以缺失的 auth.log 无关紧要。banaction = nftables 意味着封禁通过 nftables 执行,而这正是 Ubuntu 24.04 实际使用的防火墙,而不是老旧的 iptables。而 [sshd] enabled = true 意味着这个监狱从首次开机起就已启用。结论是:在 Ubuntu 24.04 上,一个原封不动的 apt install fail2ban 开箱即可封禁 SSH 暴力破解。您的大部分工作是确认这一点、调整策略,并确保自己不会把自己锁在外面。
那个旧的 auth.log 陷阱仍会在三种情况下咬人,值得认清它们:您用 pip 而不是 apt 安装了 Fail2ban,因而没有 defaults-debian.conf;您身处一个没有 systemd journal 可读的非特权容器(container)里;或者您照着旧教程把 backend = auto 粘进了自己的 jail.local,覆盖了原本能用的默认值。后面的"故障排查"一节会精确展示每一种情况是什么样子。
第 1 步:安装并确认它已经在封禁
sudo apt update
sudo apt install -y fail2banUbuntu 24.04 自带 Fail2ban 1.0.2,软件包会把 python3-systemd 作为硬依赖一并拉入,所以 journal 后端所需的一切都齐备了。服务会自动启用并启动:
sudo systemctl status fail2ban您需要看到 active (running)。然后看看那个已经在干活的监狱:
sudo fail2ban-client status sshd在一台已经可以访问哪怕只有几分钟的公网 VPS 上,您往往已经能看到失败次数被统计、地址被封禁,因为互联网在持续不断地扫描 22 端口。这就是原装配置有效的证明。从这里开始,您是在打磨它,而不是从零搭建。
第 2 步:编辑 jail.local,绝不动 jail.conf
Fail2ban 把它的上游默认值保存在 /etc/fail2ban/jail.conf 里。不要编辑那个文件。软件包的每一次 apt upgrade 都可能替换它,您的改动会毫无预警地消失。Fail2ban 按固定顺序读取文件:先是 jail.conf,然后是 jail.d/ 里的所有文件,最后是 jail.local,最后读到的值胜出。.local 文件是您自己的,软件包升级永远不会碰它。同样的规则也适用于过滤器(filter),那里 *.local 文件会覆盖软件包自带的 filter.d/*.conf。
所以您写一个小小的 jail.local,只覆盖您在意的那几项设置,把 jail.conf 和软件包自带的 jail.d/defaults-debian.conf 都原封不动地留作参考。
第 3 步:编写 /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local把下面这些放进去,并把 ignoreip 那一行的地址改成您自己的公网 IP:
[DEFAULT]
# Ubuntu 24.04 already sets these two in jail.d/defaults-debian.conf.
# Pinning them here documents the dependency and survives if that
# file is ever removed or changed by an upgrade.
backend = systemd
banaction = nftables
# Ban for one hour ...
bantime = 1h
# ... if an address fails ...
maxretry = 5
# ... 5 times within 10 minutes.
findtime = 10m
# Never ban these. PUT YOUR OWN IP HERE.
ignoreip = 127.0.0.1/8 ::1 10.0.0.24
# Longer bans for repeat offenders: 1h, 2h, 4h ... up to a week.
bantime.increment = true
bantime.maxtime = 1w
[sshd]
enabled = true每一行都有它存在的理由:
bantime、findtime、maxretry是封禁策略。自带的默认bantime只有十分钟;一小时是更合理的下限。同一个地址在十分钟内失败五次就被封禁。真人会打错一两次密码;十分钟里失败五次是脚本。ignoreip是您的安全带。把您连接所用的公网地址放进去,这样 Fail2ban 就绝不会把您锁在自己的服务器外面。家庭宽带 IP 会变,这是更倾向于本文结尾那种 VPN 方案的理由,而不是跳过这一行的理由。bantime.increment = true让每一次重复封禁都比上一次更长:一小时,然后两小时,然后四小时,直到bantime.maxtime。屡屡回头的地址会被逐步锁得更久。
请从您 SSH 发起端的那台机器上查出要加入白名单的地址,而不是在服务器上查:
curl -s ifconfig.me您可以在这里生成一份按您的端口和封禁策略调好的 jail.local,然后把它粘进文件:
第 4 步:重启并确认它正在读取 journal
sudo fail2ban-client -t
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd-t 会先做一次配置测试,这样 jail.local 里的笔误会在这里高声报错,而不是让服务悄无声息地挂掉。一个健康的监狱状态是这样的:
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 14
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 1
|- Total banned: 3
`- Banned IP list: 10.0.0.66真正证明 Fail2ban 确实在读取您登录记录的数字是 Total failed。如果它大于零,或者当您从另一台机器上故意登录失败时它会往上涨,那就说明 journal 正在被读取,您就完成了。如果无论您失败多少次它都停在 0,而且您确定自己不是从 ignoreip 里的地址在测试,请直接跳到下面的故障排查。
请注意 Journal matches 那一行仍然写着 sshd.service。在 Ubuntu 上 SSH 的单元其实是 ssh.service,但自带的过滤器同时按 _COMM=sshd 匹配,而 24.04 上的 OpenSSH 是以一个名为 sshd 的进程记录失败的,所以匹配是成立的。这个细节只有当您用的是更新的 OpenSSH(9.8 或更高版本,其中每个连接的工作进程叫 sshd-session)时才要紧;故障排查一节会覆盖那种情况。
第 5 步:看一次真实封禁落地,或强制制造一次来测试
在任何公网 VPS 上,真实的封禁都会在几分钟内自行到来。要看一次,就跟踪日志:
sudo tail -f /var/log/fail2ban.log一次封禁看起来是这样的:
2026-07-15 10:31:40,502 fail2ban.filter [812]: INFO [sshd] Found 10.0.0.66 - 2026-07-15 10:31:40
2026-07-15 10:31:44,118 fail2ban.actions [812]: NOTICE [sshd] Ban 10.0.0.66要在不等待的情况下端到端地验证整套机制,就手动封禁一个文档示例地址,绝不封您自己的:
sudo fail2ban-client set sshd banip 10.0.0.66它会打印 1,然后这个地址会出现在 fail2ban-client status sshd 的 Banned IP list 下面。现在确认这个封禁在防火墙里确实存在。在 Ubuntu 24.04 上那是 nftables,不是 iptables:
sudo nft list table inet f2b-table您会看到一个名为 addr-set-sshd 的集合,里面装着 10.0.0.66,以及一条名为 f2b-chain 的链,它拒绝该集合里的任何来源。如果 fail2ban-client 说某个地址被封了,但 nft list 里什么都没有,那就是您的封禁动作和您的防火墙不匹配,请看故障排查里关于 nftables/iptables 的说明。
第 6 步:给自己解封,以及被锁在外面时如何恢复
如果您封禁了一个不该封的地址,也就是您自己的,就把它移除:
sudo fail2ban-client set sshd unbanip 10.0.0.66成功时它返回 1。要清除所有监狱里的全部封禁:
sudo fail2ban-client unban --all不要指望一个已经打开的 SSH 会话能救您:nftables 封禁会拒绝来自被封地址、发往 22 端口的每一个数据包,已建立的连接也包括在内,所以封禁一落地,现有会话就会冻住。如果您封了自己又没有 ignoreip 条目,那么在封禁到期之前您就被锁在外面了,请通过服务商的网页控制台(VNC 或串口)恢复,那条路不经过 SSH,您可以等 bantime 过去,或者在那里运行解封命令。
第 7 步:让封禁持久化并逐步升级
Fail2ban 把当前的封禁保存在 /var/lib/fail2ban/fail2ban.sqlite3 这个小小的 SQLite 数据库里,所以它们能在服务重启或重启机器后存活下来;您不会丢失它们。您已经加上的那几行 bantime.increment 会把每一个屡犯者变成他们自己越来越大的麻烦,封禁时长大致从一小时翻倍逼近一周。
要在此之上再加一条系统级的"三振出局"策略,Fail2ban 自带一个 recidive 监狱,它监视自己的 /var/log/fail2ban.log,对任何在所有监狱里被反复封禁过的地址施加长时封禁。因为您的 [DEFAULT] 现在用的是 systemd 后端,请把这个监狱重新固定回它本该读取的日志文件:
[recidive]
enabled = true
backend = auto
logpath = /var/log/fail2ban.log
bantime = 1w
findtime = 1d
maxretry = 5backend = auto 配上明确的 logpath,让 recidive 读取普通的 fail2ban.log,那正是它要统计的 Ban 行真正出现的地方,而您在全局设置的 systemd 默认会把它指向 journal,那些行并不在那里。
第 8 步:把它和仅密钥 SSH 搭配,最好再加一层 VPN
只有和密钥认证并肩,Fail2ban 才真正物有所值。在 /etc/ssh/sshd_config.d/ 下的一个 drop-in 文件里,比如 /etc/ssh/sshd_config.d/00-hardening.conf,设置:
PasswordAuthentication no
KbdInteractiveAuthentication no然后 sudo systemctl restart ssh。密码登录关掉后,暴力破解根本无法得逞;此时 Fail2ban 存在的意义就是削减日志噪声并及早赶走扫描器。更强的做法是把 SSH 彻底移出公网:把 SSH 放在自建的 WireGuard VPN 后面,再用防火墙管住 22 端口,让它只在隧道内应答。谁也无法暴力破解一个自己够不到的端口,而 Fail2ban 就从第一道防线退居为一道后备防线。
Fail2ban 不只用于 SSH。任何会记录失败登录的服务都可以配一个监狱:邮件服务器、nginx 站点,或者一个自建的 Vaultwarden 密码管理器,您大概也不愿意让它的网页登录门户敞开,任由撞库攻击。一旦某个 Web 应用坐落在一个带 Let's Encrypt 证书的 nginx 站点之后,就用 SSH 监狱指向 journal 的同样方式,让一个 Fail2ban 过滤器指向它的访问日志。
故障排查,以及您会看到的确切字符串
"Have not found any log file for sshd jail",而且 Fail2ban 起不来。 这就是那个旧的 auth.log 问题,在 Ubuntu 24.04 上,只有当某样东西覆盖了软件包的默认值时您才会碰到它:一次没有 defaults-debian.conf 的 pip 安装、一个没有 journal 的容器,或者您粘进 jail.local 的一个游离的 backend = auto。在一个没有 /var/log/auth.log 的文件后端上,sshd 监狱找不到它的日志,整个守护进程随之中止。fail2ban.log 显示:
ERROR Failed during configuration: Have not found any log file for sshd jail因为那个错误是致命的,服务永远起不来,于是 fail2ban-client status 接着报告下游的症状:
ERROR Failed to access socket path: /var/run/fail2ban/fail2ban.sock. Is fail2ban running?那行 "socket path" 并不意味着 Fail2ban 坏了,它意味着 Fail2ban 因为某个监狱找不到它的日志而根本没启动。在 [DEFAULT] 里设置 backend = systemd(Ubuntu 软件包已经替您做了这件事)会一次修好这两条消息。
监狱是启用的,但 Total failed 从不移动。 守护进程在运行,journal 也在被读取,可真实的失败在 journalctl -u ssh 里越堆越多,而计数器却停在 0。先排除最明显的:您正从 ignoreip 里列出的某个地址测试,所以您自己的失败按设计被豁免了。如果不是这个原因,那就是您用的 OpenSSH 版本里每个连接的工作进程是 sshd-session(9.8 及更高版本),它在 journal 里的 _COMM 是 sshd-session,而不是 sshd,所以自带的匹配漏掉了它。请在 [sshd] 块里放宽匹配:
[sshd]
enabled = true
backend = systemd
journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd + _COMM=sshd-session重启,从一个不在 ignoreip 里的地址故意登录失败一次,然后确认 Total failed 终于开始往上涨。
您封了自己:Connection refused。 您把自己的地址漏在了 ignoreip 之外,测试了几次错误登录,现在:
ssh: connect to host 10.0.0.10 port 22: Connection refused出现的是拒绝,而不是静默超时,这正是 nftables 动作默认的 reject 裁决在尽职地干活,只不过对象是您自己。按第 6 步来修:从另一个未被封禁的地址上的会话解封,或者从服务商控制台解封,来自被封地址的一个已打开的会话同样会冻住。然后把您的地址加进 ignoreip,让它不会再发生。
Fail2ban 说某个地址被封了,可它仍然能连上。 status sshd 里的计数器在涨,可那个地址仍然够得到 22 端口。这是封禁动作与防火墙不匹配,在 Ubuntu 24.04 上几乎总是意味着您用一份旧指南里抄来的 banaction = iptables-multiport 覆盖了原本能用的 banaction = nftables,而这台机器上并没有 iptables 那一层。fail2ban.log 显示:
fail2ban.actions [812]: ERROR Failed to execute ban jail 'sshd' action 'iptables-multiport'删掉那个覆盖,让软件包自带的 nftables 动作生效;或者,如果您完全通过 ufw 管理防火墙,并希望封禁在那里显示出来,就在 [DEFAULT] 里设置 banaction = ufw。重启,用 sudo nft list ruleset | grep f2b 确认规则出现。
编辑 jail.local 之后 Fail2ban 起不来。 一个笔误,比如一个游离的标题或一个错误的时间值,会让服务拒绝启动。请让 Fail2ban 在运行前检查配置:
sudo fail2ban-client -t它会点出有问题的文件和监狱名,例如 Errors in jail 'sshd'. Skipping...,这样您修的是问题的源头,而不是靠猜。
FAQ
Ubuntu 24.04 上原装的 Fail2ban 真的能封禁 SSH 攻击吗?
能。软件包自带 /etc/fail2ban/jail.d/defaults-debian.conf,它启用 sshd 监狱,设置 backend = systemd 让它读取 systemd journal 而不是那个缺失的 /var/log/auth.log,并设置 banaction = nftables 让封禁通过 Ubuntu 真正使用的防火墙执行。一个普通的 apt install fail2ban 从首次开机起就保护着 SSH。用 sudo fail2ban-client status sshd 确认,看 Total failed 是否非零。
为什么 Fail2ban 在我的机器上什么都不封?
按顺序排除三个常见原因。您可能正从 ignoreip 里的某个地址测试,它按设计被豁免。您可能照着旧指南把 backend = auto 粘进了 jail.local,从而覆盖了能用的默认值,在没有 auth.log 的镜像上破坏了 journal 读取。也可能您身处一个根本没有 systemd journal 可读的容器里。检查 fail2ban-client status sshd 里的 Total failed:如果它在 journalctl -u ssh 显示真实失败时却从不上涨,那就是监狱读错了地方。
我怎么给自己的 IP 地址解封?
运行 sudo fail2ban-client set sshd unbanip YOUR.IP.HERE,成功时它返回 1;或者用 sudo fail2ban-client unban --all 清除全部封禁。如果您被锁在 SSH 之外,用服务商的网页或 VNC 控制台运行同样的命令,因为封禁会拒绝来自您地址、发往 22 端口的每一个数据包,所以连一个原本已经打开的会话都会停止工作。然后把您的地址加进 ignoreip,让它不会再发生。
jail.conf 和 jail.local 有什么区别?
jail.conf 保存 Fail2ban 的上游默认值,每次软件包升级都会被覆盖,所以在那里做的任何编辑最终都会丢失。Debian/Ubuntu 软件包通过 jail.d/defaults-debian.conf 在其之上叠加自己的设置。您的改动属于 jail.local,它最后被读取,胜过前两者,而且升级永远不会碰它。把 jail.conf 当作只读参考留着。
Fail2ban 能替代基于密钥的 SSH 认证吗?
不能。Fail2ban 对来自同一个地址的反复失败限速;它对那种缓慢、分布式、每个地址都停在阈值之下的猜测毫无办法。仅密钥认证(PasswordAuthentication no)让密码猜测彻底不可能,然后 Fail2ban 再去削减日志噪声、及早赶走扫描器。两者都用上,最好再把 SSH 彻底移出公网。