跳转至

abuse-guard:按错误响应率自动封禁滥用客户端

安装

您可以在任何基于 RHEL 的发行版上安装此模块,包括但不限于:

  • RedHat Enterprise Linux 7、8、9 和 10
  • CentOS 7、8、9
  • AlmaLinux 8、9
  • Rocky Linux 8、9
  • Amazon Linux 2 和 Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-abuse-guard
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-abuse-guard

通过在 /etc/nginx/nginx.conf 顶部添加以下内容来启用模块:

load_module modules/ngx_http_abuse_guard_module.so;

本文档描述的是 nginx-module-abuse-guard v2.1.0 发布于 2026 年 9 月 3 日。


您的错误日志就是一份自白书。Abuse Guard 实时读取它并将滥用者拒之门外。

每个扫描器、模糊测试器和撞库机器人都会留下相同的指纹: 一连串寻找隐藏路径的 404、敲击锁死房门的 403、 一次又一次失败的请求。Abuse Guard 监视您的服务器实际返回的状态码, 识别出流量中大部分是失败的客户端,并将它们封锁——这一切都在 NGINX worker 内部, 针对请求本身,在几微秒内完成决策。无需 sidecar。无需日志传输器。无需脚本层。 只有编译后的 C 语言在出色地完成一项工作。

规格说明

触发器 您选择的每个客户端的错误响应率(默认为 403/404
动作 定时封锁——在固定时间窗口内的硬性封禁,而非限速
决策点 NGINX preaccess 阶段,在任何 handler 或 upstream 运行之前
内存模型 每个客户端固定字节数,与阈值无关 → 可应对僵尸网络规模
集群模式 可选:通过 Redis / Valkey 在节点间复制封禁信息
持久性 可选:退出快照在重启后保留活动封禁
资源占用 一个自包含模块;默认零运行时依赖
平台 RHEL / AlmaLinux / Rocky / CentOS Stream / Oracle / Amazon Linux

它解决的问题

合法访客几乎从不会产生一连串错误。而滥用者除了错误几乎不产生其他东西—— 这种不对称性就是全部关键。一个扫描您目录树的漏洞扫描器就是一堵 404 的墙。 一个试探 admin 端点的机器人就是一堵 403 的墙。一次暴力破解就是一面失败的墙。

限流器将这些流量视为普通流量:它们按请求量减缓所有人的速度, 并在攻击者稍一放松时就立刻放行。Abuse Guard 则相反。 它完全忽略行为良好的流量,并将其唯一的响应——一个真正的、有时间限制的封禁—— 保留给那些以错误为特征的客户端。

使用限流器来塑造负载。使用 Abuse Guard 来驱逐滥用。

封禁是如何决定的

三个活动部件,全部在 worker 内部:

1 · 每个客户端的泄漏分数。 每个客户端身份在共享内存中携带一个小的数字。 每个匹配的错误默认加一分,或加上您为该状态码分配的权重;分数会以 阈值 ÷ 间隔 每秒的速度持续流失。短暂而猛烈的爆发会将其推过线; 缓慢的涓涓细流永远不会。关键在于,无论您将阈值设置得多高, 该分数都是一个固定大小的记录,因此单个 zone 可以轻松跟踪 僵尸网络抛给您的数万个不同源地址。

2 · 一个硬性截止时间。 当分数超过您的阈值的那一刻, 该客户端就获得了一个 blocked_until 时间戳。在此之前它就这样消失了—— 每个请求都在 preaccess 阶段被拒绝,NGINX 不会花费任何周期在路由、 文件或 upstream 上。这种拒绝是最便宜的可能的处理结果。

3 · 一个隐私正确的拒绝。 被封禁的客户端会收到 429 Too Many Requests (您可以选择状态码),并带有标记,使得任何共享缓存都无法存储它, 也就不会将一个客户端的惩罚提供给另一个客户端,同时带有 Retry-After 告诉诚实的客户端何时可以返回。

身份被折叠成固定大小的摘要,因此使用像 $request_uri 或某个 header 这样臃肿的内容作为键,与使用 IP 作为键所消耗的内存完全相同。

一分钟内上线

Abuse Guard 以预编译、签名的模块形式从 GetPageSpeed 仓库提供—— 直接安装即可,无需构建工具链。

sudo yum -y install https://extras.getpagespeed.com/release-latest.rpm
sudo yum -y install nginx-module-abuse-guard

配置它:

load_module modules/ngx_http_abuse_guard_module.so;

http {
    abuse_guard_zone zone=clients:10m;     # 一个共享内存 zone

    server {
        location / {
            abuse_guard zone=clients;      # 在此处执行
        }
    }
}
sudo nginx -t && sudo systemctl reload nginx

这些默认值会封禁任何在 5 分钟窗口内返回 100 个 403/404 响应的 IP, 封禁一小时。 您可以收紧或放宽下面的每个数字。

配置

Abuse Guard 有四个指令。第一个声明策略;其余的用于应用策略、 豁免某些人,以及(可选地)在多台机器之间共享策略。

声明策略 — abuse_guard_zone

一个 http 级别的指令。 它划分出一个共享内存 zone 并设置管理该 zone 的策略。 您可以根据需要设置尽可能多或尽可能少的参数——zone 的名称和大小是唯一必须提供的; 合理的默认值会填补其余部分(下面显示的值正是这些默认值)。

abuse_guard_zone  zone=clients:10m             名称 + 大小(唯一必须项)
                  key=$binary_remote_addr      谁是“一个客户端”
                  statuses=403,404             哪些响应会增加分数
                  interval=300s                计分窗口
                  threshold=100                该窗口内的分数  触发封禁
                  block=60m;                   封禁持续多长时间

zone=clients:10m 是策略的身份和预算:一个您从 abuse_guard 引用的名称,以及共享内存大小。大约 10 MB 可以跟踪大约十万个活动客户端。

其他一切都是可选的调优:

  • key — 定义单个客户端的表达式。可以是任何 NGINX 变量; 默认的 $binary_remote_addr 以源 IP 为键。如果某个请求的 key 解析为空,则该请求会被完全跳过(在下面的 map 场景中很方便)。
  • statuses — 会增加分数的响应。单独的状态码或范围权重为 1, 保持原始语法:statuses=403,404,500-599。添加 :weight 可以使某个响应消耗更多的同一预算,例如 statuses=404,401:5,500-599:2。 权重是 1 到 1024 之间的整数,范围会将其权重应用于其覆盖的每个状态码。 使用相同权重重复或重叠某个状态码是无害的;权重冲突会导致 nginx -t 失败。 默认值仍为 403,404
  • interval — 分数衰减的窗口(默认 300s)。窗口内的突发会触发封禁; 分散在更长时间内的缓慢涓流永远不会累积。
  • threshold — 在该窗口内多少分才会越线,最高 1024(默认 100)。 一个高权重的响应可以消耗剩余预算并立即触发封禁。
  • block — 被触发的客户端保持封锁的时间(默认 60m)。
  • inactive — 一个不活跃的客户端在内存中停留多长时间后才被回收 (默认 max(1h, interval, block);任何显式值必须至少与 intervalblock 一样大)。
  • redison 表示将此 zone 的封禁信息复制到整个集群 (见下文);默认为 off
  • persist — 一个文件路径,在 worker 干净退出时将活动封禁转储到该文件, 并在启动时加载。
  • persist_secret — 在支持签名快照的构建中,一个十六进制密钥, 用于添加 HMAC-SHA256 认证,以便拒绝被篡改的文件。

为什么默认不包含 5xx 服务器错误通常是这边的问题, 将其计入会让一个有问题的后端导致无辜访客被封禁。 仅当您确实想要针对触发服务器错误的客户端采取行动时, 才添加 statuses=403,404,500-599

应用策略 — abuse_guard

httpserverlocation 块中均有效,因此您可以保护整个站点 或仅保护那些容易招致滥用的端点。指定 zone 名称即可启用; 在嵌套作用域中写入 abuse_guard off; 可将其关闭。

location /wp-login.php {
    abuse_guard zone=clients status=429 log_level=warn;
}
  • zone — 此处应用的 zone(在上面声明)的策略。
  • status — 被封禁客户端收到的状态码,范围在 400599 之间 (默认 429)。
  • dry_runon 表示仅观察而不执行:判定会被记录但不会写入封禁。 默认为 off。
  • log_level — 记录每个决策的详细程度:infonotice(默认)、 warnerror

使用 dry_run=on 放心地逐步上线。 它会记录每一个将要发出的封禁, 而不改变任何状态,因此您可以针对实时流量校准阈值——甚至可以与同一 zone 上 执行模式的 location 并行——然后切换为实时执行。

豁免好人 — abuse_guard_allow

上下文: http · server · location · 可重复,向下继承。

abuse_guard_allow 127.0.0.0/8;
abuse_guard_allow 10.0.0.0/8 192.168.0.0/16;

列出的客户端永远不会被计数,也永远不会被封禁。匹配基于真实的连接地址, 因此它与 realip 配合良好。这也是保护经过验证的搜索引擎爬虫的方法: 允许已发布的 Googlebot / Bingbot 地址范围,这样那些爬取过期 URL (并积累 404)的机器人永远不会被捕获。

在集群中共享封禁 — abuse_guard_redis

上下文: http

abuse_guard_redis host=10.0.0.5 password=… ;   # tls://host 用于 TLS
abuse_guard_zone  zone=clients:10m redis=on;

将每个节点指向同一个 Redis 或 Valkey,打开 redis=on, 任何一台机器上产生的封禁都会传播到所有机器。默认值:port=6379db=0prefix=ag_timeout=100ms。它如何保持快速将在下一节说明。

SELinux: 在强制模式下(RHEL、Rocky、AlmaLinux),内核会阻止 NGINX 打开到 Redis 的连接,直到您允许一次—— setsebool -P httpd_can_network_connect 1。跳过此步骤, 复制将静默地不执行任何操作,而本地执行则照常进行。

一个封禁,所有节点——且不拖慢任何单个请求

在负载均衡器后面,单服务器的封禁只是做做样子:攻击者只需落在不同的节点上即可。 Abuse Guard 弥补了这一差距,而绝不会将 Redis 置于请求路径中。

每个节点在本地决策并在本地计数。一旦发出封禁,它就向集群广播这一个事实 并记录一个持久副本。其他每个节点都会在毫秒内导入它, 任何离线的节点在重新连接的那一刻就会进行同步。由于执行始终由每个节点 自己的内存状态提供,访客的请求永远不会等待网络往返—— 集群化的唯一代价是,一个新被封禁的攻击者会在一个心跳之后 才在整个集群范围内被拒之门外,而不是立即生效。

这里的 Redis 是一个单向的警铃,而不是每个请求都要查询的共享账本—— 因此,一个缓慢或不可用的 Redis 永远不会给您的流量增加延迟。 在私有网络上运行它,并将其视为特权服务:任何能写入它的人都能发出封禁。

比重启更持久的封禁

将 zone 指向一个文件,活动封禁会在 worker 干净退出时被转储, 然后在启动时恢复。重新加载和有序重启会保留当前的封禁, 而无需运行周期性的全 zone 写入器。进程或机器突然故障可能会丢失 自上次干净退出以来发出的封禁;执行仍然会以失败开放的方式继续。

abuse_guard_zone zone=clients:10m
                 persist=/var/lib/nginx/abuse_guard/clients.state
                 persist_secret=00112233445566778899aabbccddeeff;

紧凑的快照仅包含身份摘要和封禁截止时间。CRC32 检测损坏, 原子重命名防止部分写入进入活动路径。支持签名快照的构建 还可以使用 persist_secret 对其进行认证。保持该目录仅对 worker 用户可读。

查看它做出的所有决策

三个变量将 Abuse Guard 的判定暴露给您的日志和配置:

变量
$abuse_guard_status BYPASSED · PASSED · COUNTED · BLOCKED · DRY_RUN
$abuse_guard_count 当前加权分数,四舍五入为整数。
$abuse_guard_blocked_until 封禁解除的 Unix 时间,或 0
log_format guard '$remote_addr "$request" $status '
                 'guard=$abuse_guard_status count=$abuse_guard_count';

在 CDN 或代理后面进行键控? 永远不要信任原始的 X-Forwarded-For。 先让 realip 解析出真实客户端,然后以 $binary_remote_addr 为键:

set_real_ip_from 10.0.0.0/8;
real_ip_header   X-Forwarded-For;
real_ip_recursive on;

需要按请求的豁免逻辑?任何 key 解析为空字符串的请求都会被忽略—— 因此 map 可以让您,例如,按 IP 跟踪匿名访客,同时让已认证用户不受影响。

为生产环境的信任而设计

Abuse Guard 的标准远高于“它能编译”。每次更改都要经过 AddressSanitizer、 UndefinedBehaviorSanitizer、Valgrind、静态分析以及对其解析器和磁盘格式的 持续模糊测试。其可选依赖——集群和签名快照——在设计上就是尽力而为: 如果 Redis 或磁盘出现异常,执行会安静地从本地内存继续。 您的流量永远不会被某个依赖所挟持。

获取 Abuse Guard

Abuse Guard 是来自 GetPageSpeed LLC 的商业 NGINX 模块, 通过 GetPageSpeed 订阅提供持续更新和支持。

© GetPageSpeed LLC. 保留所有权利。