Fail2ban 防 SSH 爆破实战:jail 配置详解

Fail2ban 防 SSH 爆破是公网服务器最基础的自动封禁方案:通过匹配 sshd 日志中的失败认证记录,按阈值触发 iptables/nftables 封禁来源 IP。本文给出可直接复用的完整配置——jail.local 参数调优、systemd 日志后端选择、ipset 高性能封禁、白名单防误封、状态监控与解封操作,并附配置验证与常见问题排查。

适用场景

  • 服务器 SSH 端口暴露在公网,/var/log/auth.log 或 journal 中出现大量 Failed password 记录。
  • 已禁用密码登录、仅保留密钥认证,但仍需拦截持续探测与服务指纹扫描。
  • 需要按 IP 维度自动封禁,避免人工维护防火墙黑名单。
  • 需要在云主机、VPS 或独立服务器上统一落地,且与 firewalld/nftables 环境兼容。

前置条件

  • Linux 发行版:Ubuntu/Debian 或 CentOS/Rocky/AlmaLinux,systemd 可用。
  • Fail2ban 0.11 及以上(推荐 1.0.x),后端封禁组件为 iptables 或 nftables。
  • SSH 服务已启用日志记录,且能通过 journalctl -u ssh(CentOS/Rocky)或 journalctl -u sshd(Debian/Ubuntu)读到失败认证记录。
  • 具备 root 权限,且已保留一个可用的带外登录通道(控制台 VNC 或另一条 SSH 会话),防止误封自己。

原理说明

Fail2ban 由三部分组成:filter 用正则从日志中提取「失败事件」与「来源 IP」;jail 定义监控哪个日志、匹配哪个 filter、达到多少次失败后封禁多久;action 定义封禁动作,默认通过 iptables 在专用链中插入 DROP 规则。

判定逻辑由三个参数决定:findtime 是观察时间窗,maxretry 是窗口内允许的最大失败次数,bantime 是封禁时长。以 maxretry=3findtime=600 为例,同一 IP 在 10 分钟内出现 3 次失败认证即被封禁;若 bantime 设为负数则永久封禁,实践中更推荐按天数设置并配合定期复查。日志来源通过 backend 决定:容器化或最小化系统常常没有 /var/log/auth.log,此时必须切换到 systemd 后端读取 journal,否则会出现「装好了但不封禁」的典型问题。

操作步骤

步骤 1:安装并确认后端

# Debian/Ubuntu
apt update && apt install -y fail2ban ipset

# CentOS/Rocky/AlmaLinux
dnf install -y epel-release && dnf install -y fail2ban ipset

fail2ban-client --version
systemctl enable --now fail2ban

确认日志可读:

journalctl -u ssh --no-pager | grep -Ei "Failed password|Invalid user" | tail -5
journalctl -u sshd --no-pager | grep -Ei "Failed password|Invalid user" | tail -5

两条命令中能命中其中一条即可,命中 ssh 用 CentOS 系列默认 jail,命中 sshd 用 Debian 系列默认 jail。

步骤 2:编写 jail.local 基础配置

不要直接修改 /etc/fail2ban/jail.conf(升级会被覆盖),统一写到 /etc/fail2ban/jail.local

[DEFAULT]
# 封禁动作:iptables 多端口模式,兼容 firewalld 环境
banaction = iptables-multiport
backend  = systemd
# 白名单:本机、内网段、堡垒机、监控扫描节点
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 203.0.113.7
findtime = 600
bantime  = 86400
maxretry = 3
# 封禁与解封时发送告警邮件(可选)
# action = %(action_mwl)s

[sshd]
enabled  = true
port     = ssh
mode     = aggressive
filter   = sshd
logpath  = %(sshd_log)s
maxretry = 3
findtime = 600
bantime  = 604800

关键参数说明:mode = aggressive 同时匹配 sshdsshd-ddos 两组规则,能识别 Connection closed by authenticating user 这类不完全握手行为;bantime = 604800 表示封禁 7 天,比默认 10 分钟更能压制持续性爆破;ignoreip 必须包含堡垒机与拨号出口段,否则容易把自己锁在外面。

如目标发行版未提供 iptables-multiport,可用 ls /etc/fail2ban/action.d/ | grep -Ei "iptables|nftables" 查看可选动作,nftables 环境改用 banaction = nftables-multiport

步骤 3:启用 ipset 提升封禁性能

默认模式每封一个 IP 就插入一条规则,黑名单达到数千条后 iptables 规则链会显著变长。改用 ipset 集合可以常数级匹配:

ls /etc/fail2ban/action.d/ | grep ipset

若列出 iptables-ipset-proto4.confiptables-ipset-proto6.conf,在 jail.local 中覆盖动作即可:

[sshd]
banaction = iptables-ipset-proto6
banaction_allports = iptables-ipset-proto6-allports

若发行版包内没有 ipset 动作文件,则保持 iptables-multiport,并在巡检中关注规则条数。

步骤 4:加载配置并确认运行状态

fail2ban-client -t
systemctl restart fail2ban
fail2ban-client status
fail2ban-client status sshd

正常输出中应包含 FilterCurrently failedTotal failedCurrently bannedBanned IP list 五项。若 Filter 为空,说明 filter 名称与发行版不匹配(Debian 为 sshd,部分镜像需确认 /etc/fail2ban/filter.d/sshd.conf 是否存在)。

步骤 5:验证封禁与解封

# 从另一台测试机故意输错密码 3 次
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no root@SERVER_IP

# 查看封禁列表与规则链
fail2ban-client status sshd
iptables -L f2b-sshd -n --line-numbers
ipset list f2b-sshd 2>/dev/null | tail -20

# 解封单个 IP / 解封全部
fail2ban-client set sshd unbanip 203.0.113.10
fail2ban-client unban --all

# 手动封禁一个 IP(用于应急)
fail2ban-client set sshd banip 203.0.113.10

被封 IP 表现为 SSH 连接直接超时,且 fail2ban.log 中出现 Ban 203.0.113.10 与触发规则的日志行号,这两条记录是验证配置生效的直接证据。

步骤 6:配合 sshd 加固与监控

Fail2ban 只能减缓爆破速度,登录面本身仍需收紧。/etc/ssh/sshd_config 建议包含:

PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 20
AllowUsers deploy
sshd -t && systemctl reload sshd

需要按 IP 维度做长期观察时,把封禁动作接上告警与统计:

# 导出当前封禁名单(可写入 WAF/防火墙黑名单)
fail2ban-client status sshd | sed -n 's/.*Banned IP list:[[:space:]]*//p' | tr ' ' '\n' | grep -v '^$'
# 复盘近期封禁记录
grep -E "Ban |Unban " /var/log/fail2ban.log | tail -30

配置验证

  • 语法验证fail2ban-client -t 输出 OK,无 filter 加载报错。
  • 封禁验证:测试机连续 3 次密码错误后,fail2ban-client status sshdBanned IP list 出现测试机 IP,且 iptables -L f2b-sshd -n 可见对应 DROP 规则。
  • 白名单验证:从 ignoreip 内的主机连续失败认证,不应出现在封禁列表中。
  • 重启验证systemctl restart fail2ban 后再次执行 status,封禁记录应从数据库恢复而非清零。

常见问题

FAQ 1:Fail2ban 装好了,日志里也有失败记录,但从来不封禁?

按顺序排查四点:其一,backend 与日志实际位置不匹配,最小化系统没有 /var/log/auth.log,需设为 systemd;其二,jail 名称用错,Debian 为 [sshd]、CentOS 为 [ssh],用 fail2ban-client status 确认实际加载的 jail;其三,datepattern 与日志时间格式不一致导致日志行不被识别,执行 fail2ban-regex "$(journalctl -u sshd -n 50 --no-pager)" /etc/fail2ban/filter.d/sshd.conf 检查匹配命中数;其四,maxretry 设得过大,短时间内达不到阈值。

FAQ 2:自己的 IP 被封了怎么办?

已保留会话的话,用 fail2ban-client set sshd unbanip <你的IP> 立即解封;若已断开会话,通过云控制台 VNC 或厂商救援模式登录后执行同样的命令。根本解法是把办公出口、堡垒机、监控扫描节点写入 ignoreip,并在变更前先在测试 jail 上验证阈值。

FAQ 3:Docker 或 Kubernetes 环境下为什么封禁不生效?

容器内的 Fail2ban 操作的是容器网络命名空间的 iptables,无法影响宿主机的 INPUT 链。正确做法是把 Fail2ban 部署在宿主机,日志路径指向宿主机上的 sshd 日志;或改用能对接云安全组、CDN 黑名单的封禁动作,在流量入口层完成拦截。

FAQ 4:封禁数据重启后会丢失吗?

默认不会。Fail2ban 会把封禁记录写入 /var/lib/fail2ban/fail2ban.sqlite3,重启后依据剩余 bantime 恢复。若希望封禁状态在重启后清零,可在 [DEFAULT] 中设置 dbpurgeage 并调整数据库策略;反之,若发现重启后名单为空,需检查该数据库文件是否被清理脚本删除,以及 dbfile 路径是否可写。

总结

Fail2ban 防 SSH 爆破的落地要点是「日志后端正确 + 阈值合理 + 白名单完备 + 封禁动作高效」。先用 backend = systemd 解决日志来源问题,再用 findtime/maxretry/bantime 定义可接受的安全水位,同时把堡垒机与内网段写入 ignoreip 避免自锁;黑名单规模上升后切换到 ipset 动作,配合 sshd 侧禁用密码登录与限制重试次数,才能把爆破成本抬升到攻击者不愿承受的水平。所有变更都应在保留带外通道的前提下进行,并在变更后立即验证封禁与解封两条路径。