将 Fail2ban 与 Cloudflare API 联动,是目前成本最低、效果最显著的服务器防御方案之一。你不需要昂贵的硬件防火墙,也不需要复杂的入侵检测系统,只需要一台运行 Ubuntu 的服务器、一个 Cloudflare 托管的域名,以及一段经过验证的配置逻辑。当恶意 IP 反复尝试暴力破解你的 SSH、WordPress 后台或邮件服务时,Fail2ban 会在本地将其拦截,同时通过 Cloudflare API 将该 IP 同步到 Cloudflare 的全局黑名单中,让攻击流量根本到达不了你的服务器端口。这种“本地+云端”双重封禁机制,能极大降低服务器负载,同时提升防御纵深。

为什么需要将 Fail2ban 与 Cloudflare 联动

很多人已经在用 Fail2ban 保护服务器,但默认配置只会在本地 iptables 或 ufw 中添加规则。这意味着恶意 IP 的请求依然会到达你的服务器网卡,消耗带宽和 CPU 资源。对于小规模的试探性攻击,这或许无关紧要,但面对动辄数千个肉鸡 IP 的分布式暴力破解,本地封禁会让 iptables 规则迅速膨胀,匹配效率直线下降,服务器资源被大量无效连接占用。

Cloudflare 作为反向代理,所有合法流量和攻击流量都会先经过它的边缘节点。如果你能在攻击流量进入 Cloudflare 网络的那一刻就将其丢弃,你的服务器根本不会感知到这些请求。Fail2ban 检测到恶意行为后,调用 Cloudflare API 将 IP 加入 Cloudflare 防火墙的 IP 列表,这个列表可以作用于 Cloudflare 的 WAF(Web 应用防火墙)规则,实现边缘封禁。被封禁的 IP 再次访问你的域名时,Cloudflare 会直接返回 403 或自定义拦截页面,请求不会回源到你的服务器。

前置条件与架构概览

在开始配置之前,你需要确认以下几点:你的域名已经通过 Cloudflare 的 NS 服务器解析,并且开启了代理模式(橙色云朵图标);你拥有一台运行 Ubuntu 20.04 或 22.04 的服务器,且已安装 Fail2ban;你需要在 Cloudflare 后台获取具有防火墙操作权限的 API Token。整个联动流程可以概括为:Fail2ban 监控日志文件,匹配到恶意行为后触发 action,action 脚本调用 Cloudflare API 将 IP 添加到指定 IP 列表,Cloudflare WAF 规则引用该列表进行拦截。

获取 Cloudflare API Token 并创建 IP 列表

登录 Cloudflare 控制台,在右上角点击头像,选择“我的个人资料”,进入“API 令牌”页面。不要使用全局 API Key,那个权限过高,存在安全风险。点击“创建令牌”,选择“自定义”模板。在权限设置中,需要添加两项权限:一项是“账户 - 防火墙规则 - 编辑”,另一项是“账户 - 账户过滤规则 - 编辑”。账户资源选择你的 Cloudflare 账户,客户端 IP 地址筛选可以留空或限制为你的服务器 IP。创建完成后,系统会显示一次 Token 值,立即复制保存,这个 Token 后续无法再次查看。

接下来创建一个专门存放恶意 IP 的列表。进入 Cloudflare 控制台,在左侧菜单找到“管理账户”下的“配置”,然后选择“列表”。点击“创建列表”,列表类型选择“IP”,名称为“fail2ban_blocklist”或其他你容易识别的名称。这个列表初始为空,后续由 Fail2ban 脚本自动填充。创建完成后,记录下列表 ID,它是一串类似“414d4b5c0a1b4c8e9f2a3b6c7d8e9f0a”的十六进制字符串,在后续脚本中需要使用。

创建 Cloudflare WAF 规则引用 IP 列表

进入你需要防护的域名控制台,在左侧菜单选择“安全性”下的“WAF”,然后点击“防火墙规则”标签页。点击“创建防火墙规则”,规则名称可以设为“Block Malicious IPs”。在“字段”下拉菜单中选择“IP 源地址”,运算符选择“在列表中”,值选择你刚才创建的“fail2ban_blocklist”。动作选择“阻止”,然后保存规则。这条规则会实时生效,只要 IP 出现在列表中,Cloudflare 就会在边缘节点直接拦截。

在 Ubuntu 上安装和配置 Fail2ban

Ubuntu 官方仓库中的 Fail2ban 版本通常足够稳定,直接使用 apt 安装即可:

sudo apt update
sudo apt install fail2ban -y

安装完成后,Fail2ban 默认会启动并监控 SSH 服务。但我们需要自定义配置,因此不要直接修改 /etc/fail2ban/jail.conf,而是创建 /etc/fail2ban/jail.local 文件,其中的配置会覆盖默认值。先复制默认配置作为基础:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

编辑 jail.local 文件,找到 [sshd] 部分,确认 enabled 为 true。你可以根据实际需求调整 maxretry(最大尝试次数)、findtime(时间窗口)和 bantime(封禁时长)。对于生产环境,建议 bantime 至少设置为 86400 秒(24 小时),甚至更长。恶意 IP 往往会在被封禁后持续尝试,较长的封禁时间能有效减少日志噪音。

编写 Cloudflare API 联动脚本

这是整个方案的核心。Fail2ban 的 action 机制允许你在封禁和解封时执行自定义脚本。我们需要两个脚本:一个用于封禁时调用 Cloudflare API 添加 IP,另一个用于解封时从列表中移除 IP。虽然解封操作不是必须的,但完善的脚本应该支持完整的生命周期管理。

首先创建封禁脚本 /etc/fail2ban/action.d/cloudflare-ban.sh:

#!/bin/bash

# Cloudflare API 配置
CF_API_TOKEN="你的API_Token"
CF_ACCOUNT_ID="你的账户ID"
CF_LIST_ID="你的列表ID"

# Fail2ban 传递的参数
ACTION=$1
IP=$2

# 封禁函数
ban_ip() {
    # 先检查 IP 是否已在列表中,避免重复添加
    EXISTING=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/rules/lists/${CF_LIST_ID}/items" \
        -H "Authorization: Bearer ${CF_API_TOKEN}" \
        -H "Content-Type: application/json" | grep -c "\"${IP}\"")
    
    if [ "$EXISTING" -gt 0 ]; then
        echo "IP ${IP} 已在 Cloudflare 列表中,跳过添加。"
        exit 0
    fi
    
    # 添加 IP 到列表
    RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/rules/lists/${CF_LIST_ID}/items" \
        -H "Authorization: Bearer ${CF_API_TOKEN}" \
        -H "Content-Type: application/json" \
        --data "[{\"ip\":\"${IP}\",\"comment\":\"Blocked by Fail2ban\"}]")
    
    SUCCESS=$(echo "$RESPONSE" | grep -c '"success":true')
    if [ "$SUCCESS" -gt 0 ]; then
        echo "成功将 ${IP} 添加到 Cloudflare 黑名单。"
        logger "Fail2ban-Cloudflare: 已封禁 IP ${IP}"
    else
        echo "添加 IP ${IP} 失败,响应:${RESPONSE}"
        logger "Fail2ban-Cloudflare: 封禁 IP ${IP} 失败"
    fi
}

# 解封函数
unban_ip() {
    # 获取列表中该 IP 的 item id
    ITEM_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/rules/lists/${CF_LIST_ID}/items" \
        -H "Authorization: Bearer ${CF_API_TOKEN}" \
        -H "Content-Type: application/json" | grep -B1 "\"${IP}\"" | grep '"id"' | awk -F'"' '{print $4}')
    
    if [ -z "$ITEM_ID" ]; then
        echo "未在 Cloudflare 列表中找到 IP ${IP}。"
        exit 0
    fi
    
    RESPONSE=$(curl -s -X DELETE "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/rules/lists/${CF_LIST_ID}/items" \
        -H "Authorization: Bearer ${CF_API_TOKEN}" \
        -H "Content-Type: application/json" \
        --data "{\"items\":[{\"id\":\"${ITEM_ID}\"}]}")
    
    SUCCESS=$(echo "$RESPONSE" | grep -c '"success":true')
    if [ "$SUCCESS" -gt 0 ]; then
        echo "成功将 ${IP} 从 Cloudflare 黑名单中移除。"
        logger "Fail2ban-Cloudflare: 已解封 IP ${IP}"
    else
        echo "移除 IP ${IP} 失败,响应:${RESPONSE}"
    fi
}

# 根据参数执行对应操作
case $ACTION in
    ban)
        ban_ip
        ;;
    unban)
        unban_ip
        ;;
    *)
        echo "用法: $0 {ban|unban} "
        exit 1
        ;;
esac

赋予脚本执行权限:

sudo chmod +x /etc/fail2ban/action.d/cloudflare-ban.sh

脚本中的 CF_ACCOUNT_ID 可以在 Cloudflare 控制台任意页面的 URL 中找到,格式为“/账号ID/域名/...”那串字符串。CF_LIST_ID 就是之前创建的 IP 列表 ID。注意,Cloudflare API 对 IP 列表有速率限制,免费版账户每分钟最多 50 次请求,对于大多数场景足够使用。如果你的服务器遭受大规模攻击,短时间内触发大量封禁请求,可能触发速率限制,此时脚本中的重复检查逻辑就显得尤为重要,它能避免不必要的 API 调用。

配置 Fail2ban Action 文件

接下来创建 Fail2ban 的 action 配置文件,告诉 Fail2ban 如何调用我们编写的脚本。创建 /etc/fail2ban/action.d/cloudflare.conf:

[Definition]

# 封禁时执行的命令
actionban = /etc/fail2ban/action.d/cloudflare-ban.sh ban 

# 解封时执行的命令
actionunban = /etc/fail2ban/action.d/cloudflare-ban.sh unban 

# 启动时不需要特殊操作
actionstart =

# 停止时不需要特殊操作
actionstop =

# 检查 action 是否正常
actioncheck =

这个 action 文件定义了 Fail2ban 在封禁和解封时分别执行什么命令。Fail2ban 会自动替换 <ip> 为实际的恶意 IP 地址。

将 Cloudflare Action 绑定到 Jail

现在需要修改 jail.local 文件,让 SSH 或其他服务的 jail 使用我们自定义的 cloudflare action。编辑 /etc/fail2ban/jail.local,在 [sshd] 部分添加 action 配置:

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 600
bantime = 86400
action = cloudflare
         iptables-multiport[name=sshd, port=ssh, protocol=tcp]

这里配置了两个 action:cloudflare 负责调用 Cloudflare API 进行云端封禁,iptables-multiport 负责本地 iptables 封禁。两者并行工作,互不干扰。即使 Cloudflare API 调用失败,本地封禁依然生效,形成双重保险。

如果你还需要保护其他服务,比如 WordPress 的登录页面,可以创建自定义 jail。例如监控 Nginx 访问日志中的 wp-login.php 暴力破解:

[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 300
bantime = 86400
action = cloudflare
         iptables-multiport[name=wordpress, port=http,https, protocol=tcp]

对应的 filter 文件 /etc/fail2ban/filter.d/wordpress.conf 内容如下:

[Definition]
failregex = ^ .* "POST /wp-login.php HTTP.* 200
            ^ .* "POST /xmlrpc.php HTTP.* 403
ignoreregex =

这个正则表达式会匹配对 wp-login.php 的 POST 请求(返回 200 表示登录失败,因为 WordPress 登录失败仍返回 200 状态码)以及对 xmlrpc.php 的请求。你可以根据实际日志格式调整正则表达式。

测试与验证

完成所有配置后,重启 Fail2ban 服务使配置生效:

sudo systemctl restart fail2ban

检查 Fail2ban 状态,确认 jail 正常运行:

sudo fail2ban-client status sshd

输出中应该能看到当前封禁的 IP 数量。接下来进行实际测试。你可以从另一台服务器或使用移动网络故意多次输入错误密码触发封禁。触发后,检查 Fail2ban 日志:

sudo tail -f /var/log/fail2ban.log

你应该能看到类似“NOTICE [sshd] Ban 1.2.3.4”的记录,以及我们脚本中通过 logger 写入的系统日志。然后登录 Cloudflare 控制台,进入列表页面,确认该 IP 已经出现在 fail2ban_blocklist 中。最后,从被封禁的 IP 访问你的域名,应该会看到 Cloudflare 的拦截页面。

如果 IP 没有出现在 Cloudflare 列表中,检查以下几点:脚本中的 API Token 权限是否正确;账户 ID 和列表 ID 是否填写正确;服务器能否正常访问 api.cloudflare.com(可以用 curl 测试);查看 /var/log/syslog 中脚本通过 logger 输出的错误信息。

优化与进阶配置

上述基础配置已经能满足大部分场景,但还有一些细节值得优化。首先是 IP 列表的清理问题。Cloudflare 免费版 IP 列表上限为 1000 个 IP,如果你封禁了大量 IP,可能会触及上限。建议设置一个定期清理任务,删除超过 30 天或 90 天的条目。可以编写一个简单的脚本,通过 Cloudflare API 获取列表中所有 IP 的添加时间,删除过期的条目,然后加入 cron 定时任务。

其次是 Fail2ban 的 recidive jail。这是一个特殊的 jail,用于监控 Fail2ban 自身的日志,如果某个 IP 在短时间内被多次封禁,说明这是一个顽固的攻击者,可以将其永久封禁。配置如下:

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = cloudflare
            iptables-allports
bantime = 604800
findtime = 86400
maxretry = 3

这样,如果一个 IP 在 24 小时内被 Fail2ban 封禁超过 3 次,就会触发 recidive jail,封禁一周。配合 Cloudflare action,这个顽固 IP 会被同步到 Cloudflare 边缘进行拦截。

另外,如果你的服务器前面还有一层反向代理(比如 Nginx 作为反向代理),需要确保 Fail2ban 能获取到真实的客户端 IP,而不是代理的 IP。这需要配置 mod_remoteip 或 real_ip 模块,让日志中记录的是 X-Forwarded-For 或 CF-Connecting-IP 头部中的真实 IP。Cloudflare 会在请求头中添加 CF-Connecting-IP 字段,你可以在 Nginx 中配置 log_format 将其记录到访问日志中。

最后,关于安全性。API Token 和脚本中的敏感信息应该妥善保管。建议将脚本中的 API Token 存储在单独的文件中,设置严格的权限(600),仅 root 可读。也可以使用环境变量传递,但需要注意 systemd 服务中的环境变量隔离问题。

这套方案在实际生产环境中经过验证,能有效拦截大量自动化攻击。与单纯依赖本地 iptables 封禁相比,服务器 CPU 和带宽占用明显下降,因为大量恶意请求在 Cloudflare 边缘就被丢弃了。对于个人博客、小型企业网站或 API 服务,这是一个性价比极高的安全加固手段。