以下是为您整理的专门针对 RedHat 系发行版(CentOS、RHEL、Rocky Linux、AlmaLinux 等)的 Markdown 格式配置文档。
Fail2ban + Caddy 404 封禁 + Firewalld (nftables) 持久化配置指南 (RedHat 系)
本文档详细介绍如何在 RedHat 系 Linux 发行版上,通过 Fail2ban 分析 Caddy 的 JSON 访问日志,自动提取 404 错误请求的客户端 IP,并利用 Firewalld(底层 nftables)进行永久封禁,同时确保服务器重启后封禁规则不丢失。
一、 整体架构与依赖说明
客户端请求 → Caddy (JSON 访问日志) → Fail2ban (正则匹配 404) → firewall-cmd (firewalld) → nftables 内核规则
所需软件包:
| 软件 | 作用 |
|---|---|
caddy |
Web 服务器,输出 JSON 格式访问日志 |
fail2ban |
日志分析引擎,触发自动封禁 |
fail2ban-firewalld |
Fail2ban 的 firewalld 动作支持插件 |
firewalld |
防火墙管理守护进程(底层自动调用 nftables) |
logrotate |
日志轮转工具,防止日志文件无限增长 |
二、 安装所需软件
在 RedHat 系系统中,使用 dnf 包管理器进行安装。
# 1. 安装 EPEL 源(提供 fail2ban 等额外软件包)
sudo dnf install epel-release -y
# 2. 添加 Caddy 官方 COPR 仓库(如果系统尚未安装 Caddy)
sudo dnf install 'dnf-command(copr)' -y
sudo dnf copr enable @caddy/caddy -y
# 3. 安装所有必需软件
sudo dnf install caddy fail2ban fail2ban-firewalld firewalld logrotate -y
# 4. 设置 firewalld 和 fail2ban 开机自启并立即启动
sudo systemctl enable --now firewalld
sudo systemctl enable --now fail2ban
三、 配置 Caddy 输出 JSON 访问日志
Caddy v2 默认使用 JSON 格式记录日志,但需要显式配置将其输出到指定文件。
编辑您的 Caddyfile(通常位于 /etc/caddy/Caddyfile),在站点块中添加 log 指令:
example.com {
# ... 您的其他站点配置 ...
reverse_proxy localhost:8080
# 开启 JSON 格式访问日志并输出到文件
log {
output file /var/log/caddy/access.log {
roll_size 100mb
roll_keep 5
roll_keep_for 720h
}
format json
sampling {
interval 1
}
}
}
确保日志目录权限正确:
sudo mkdir -p /var/log/caddy
sudo chown caddy:caddy /var/log/caddy
sudo touch /var/log/caddy/access.log
sudo chown caddy:caddy /var/log/caddy/access.log
重载 Caddy 使配置生效:
sudo systemctl reload caddy
四、 配置 Fail2ban 过滤器 (Filter)
创建一个专门用于匹配 Caddy JSON 日志中 404 状态的过滤器。
sudo nano /etc/fail2ban/filter.d/caddy-404.conf
写入以下内容:
# /etc/fail2ban/filter.d/caddy-404.conf
# 匹配 Caddy JSON 访问日志中 status 为 404 的请求
[INCLUDES]
before = common.conf
[Definition]
# 优化后的正则:使用非贪婪匹配,并兼容行尾可能存在的 \r 换行符
failregex = ^.*"remote_ip":"<HOST>".*?"status":\s*404.*\}?\s*$
ignoreregex =
测试过滤器匹配效果:
# 使用 fail2ban-regex 工具测试真实日志
sudo fail2ban-regex /var/log/caddy/access.log /etc/fail2ban/filter.d/caddy-404.conf
如果输出中包含 Lines: X matched,则说明正则表达式工作正常。
五、 配置 Fail2ban 封禁动作 (Action)
创建一个自定义 Action,确保封禁操作同时写入 Firewalld 的运行时和持久化配置中。
sudo nano /etc/fail2ban/action.d/firewalld-permanent.conf
写入以下内容:
# /etc/fail2ban/action.d/firewalld-permanent.conf
# 通过 firewalld rich rules 永久封禁 IP(兼容 nftables 后端)
[Definition]
# 启动时不需要额外操作
actionstart =
# 停止时不清除规则(因为是永久封禁,交由 firewalld 自身管理)
actionstop =
# 封禁 IP:同时写入 runtime(立即生效)和 permanent(持久化)
actionban = firewall-cmd --add-rich-rule='rule family="ipv4" source address="<ip>" drop'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="<ip>" drop'
firewall-cmd --add-rich-rule='rule family="ipv6" source address="<ip>" drop'
firewall-cmd --permanent --add-rich-rule='rule family="ipv6" source address="<ip>" drop'
# 解封 IP:同时从 runtime 和 permanent 中移除
actionunban = firewall-cmd --remove-rich-rule='rule family="ipv4" source address="<ip>" drop'
firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="<ip>" drop'
firewall-cmd --remove-rich-rule='rule family="ipv6" source address="<ip>" drop'
firewall-cmd --permanent --remove-rich-rule='rule family="ipv6" source address="<ip>" drop'
[Init]
name = default
六、 配置 Fail2ban 规则 (Jail)
创建本地 Jail 配置文件(请勿直接修改默认的 jail.conf)。
sudo nano /etc/fail2ban/jail.local
写入以下内容:
# /etc/fail2ban/jail.local
[DEFAULT]
# ===== 全局默认设置 =====
# 使用自定义的 firewalld 持久化动作
banaction = firewalld-permanent
banaction_allports = firewalld-permanent
# 永久封禁(-1 表示永不自动解封)
bantime = -1
# 检测时间窗口(秒)
findtime = 600
# 触发封禁的失败次数阈值
maxretry = 5
# 忽略的 IP(白名单:本地回环、内网网段、您的管理 IP)
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
# 使用 SQLite 数据库持久化封禁记录(Fail2ban 重启不丢失)
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
# 日志后端
backend = auto
# ===== Caddy 404 封禁规则 =====
[caddy-404]
enabled = true
port = http,https
filter = caddy-404
logpath = /var/log/caddy/access.log
maxretry = 10
findtime = 600
bantime = -1
action = firewalld-permanent[name=caddy-404]
七、 配置 Logrotate 日志轮转
防止 Caddy 日志文件占用过多磁盘空间,并确保轮转后 Fail2ban 能继续正常读取。
sudo nano /etc/logrotate.d/caddy
写入以下内容:
/var/log/caddy/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 caddy caddy
sharedscripts
postrotate
# 通知 fail2ban 重新打开日志文件
/usr/bin/fail2ban-client flushlogs 2>/dev/null || true
# 通知 caddy 重新打开日志
/usr/bin/systemctl reload caddy 2>/dev/null || true
endscript
}
八、 确认 Firewalld 使用 nftables 后端
检查 Firewalld 的底层后端配置:
grep FirewallBackend /etc/firewalld/firewalld.conf
确保输出为 FirewallBackend=nftables。如果不是,请修改并重启服务:
sudo sed -i 's/FirewallBackend=.*/FirewallBackend=nftables/' /etc/firewalld/firewalld.conf
sudo systemctl restart firewalld
验证 nftables 规则集是否正常加载:
sudo nft list ruleset | head -10
九、 启动服务与验证测试
9.1 重载并检查状态
# 重载 fail2ban 配置
sudo fail2ban-client reload
# 查看 caddy-404 jail 状态
sudo fail2ban-client status caddy-404
9.2 手动测试封禁与持久化
# 1. 手动封禁一个测试 IP (例如 203.0.113.50)
sudo fail2ban-client set caddy-404 banip 203.0.113.50
# 2. 验证 firewalld 规则(应包含该 IP 的 rich rule)
sudo firewall-cmd --list-rich-rules
# 3. 验证底层 nftables 规则
sudo nft list ruleset | grep 203.0.113.50
# 4. 模拟重启 firewalld 验证持久化
sudo systemctl restart firewalld
sudo firewall-cmd --list-rich-rules # 规则应依然存在
# 5. 测试完成后解封
sudo fail2ban-client set caddy-404 unbanip 203.0.113.50
十、 日常管理命令速查
| 操作目的 | 命令 |
|---|---|
| 查看 Jail 运行状态 | sudo fail2ban-client status caddy-404 |
| 手动封禁指定 IP | sudo fail2ban-client set caddy-404 banip <IP地址> |
| 手动解封指定 IP | sudo fail2ban-client set caddy-404 unbanip <IP地址> |
| 解封所有 IP | sudo fail2ban-client unban --all |
| 查看 Fail2ban 运行日志 | sudo tail -f /var/log/fail2ban.log |
| 重新加载 Fail2ban 配置 | sudo fail2ban-client reload |
| 查看 Firewalld 富规则 | sudo firewall-cmd --list-rich-rules |
| 测试 Filter 正则匹配 | sudo fail2ban-regex /var/log/caddy/access.log /etc/fail2ban/filter.d/caddy-404.conf |
十一、 持久化原理与注意事项
持久化三重保障机制
- Fail2ban SQLite 数据库 (
dbfile):Fail2ban 重启时,会自动从/var/lib/fail2ban/fail2ban.sqlite3读取封禁列表并重新执行封禁动作。 - Firewalld Permanent 配置:自定义 Action 在封禁时同时执行
--permanent参数,将规则写入 Firewalld 的 XML 配置文件中,确保 Firewalld 重启或系统重启后规则不丢失。 - Systemd 开机自启:
firewalld和fail2ban均已设置为enable,确保系统启动时自动加载防火墙规则并启动日志监控。
⚠️ 重要注意事项
- 阈值设置:
maxretry不建议设置过低(推荐 5~20 之间),以免误封正常用户(例如浏览器自动请求不存在的favicon.ico或robots.txt)。 - CDN 代理问题:如果 Caddy 部署在 Cloudflare 等 CDN 之后,日志中的
remote_ip可能是 CDN 节点的 IP。您需要在 Caddy 中配置trusted_proxies以获取真实的客户端 IP,否则会导致 CDN 节点被封禁。 - 白名单配置:务必在
jail.local的ignoreip中添加您自己的固定管理 IP 和常用的 CDN 回源 IP 段。