一天363,000次假429错误:Gutenberg揭露的限流Bug
发布于 2026年4月7日

问题:单日363,000次假429错误
一个高流量WordPress网站开始间歇性地向合法用户返回429(请求过多)错误。限流配置使用了limit_req_zone与$binary_remote_addr的组合——看起来是正确的。但封锁大批量发生,没有真实的滥用规律。经过20天调查,累计错误905,311次。峰值:单日363,000次。
Gutenberg编辑器是可见的触发点。打开文章进行编辑时,它会并行发出50多个资源请求(JS、CSS、字体)。在burst=10的配置下,任何登录的编辑器都会立即超过限流阈值——但问题并不在于burst。
根本原因:$binary_remote_addr与多可用区ALB
在AWS应用负载均衡器(ALB)后面的生产环境中,nginx看不到真实的客户端IP——它看到的是转发请求的ALB节点IP。多可用区配置的ALB在每个可用区的每个子网中运行一个节点。所有客户端请求都通过少量ALB节点IP到达。
实际效果:当$binary_remote_addr指向ALB IP时,限流将所有用户的请求计为来自同一客户端。任何适度的流量都会耗尽共享桶并向所有人返回429。
nginx的real_ip_module可以解决这个问题——它从X-Forwarded-For请求头读取真实客户端IP,但需要通过set_real_ip_from明确声明受信任的IP(ALB节点)。错误在于只声明了第一个可用区的子网,忽略了其他可用区的子网。
修复前的配置
# nginx.conf — 不完整配置(只声明了1个可用区)
limit_req_zone $binary_remote_addr zone=wp_limit:10m rate=10r/s;
# 只声明了1个子网
set_real_ip_from 10.0.0.0/22; # ALB us-east-1a
real_ip_header X-Forwarded-For;只声明一个子网时,通过其他可用区ALB节点(10.0.4.0/22和10.0.8.0/22)路由的请求不会经过real_ip_recursive处理——nginx将ALB节点IP保留为源地址。
修复方案:nginx.conf中的3行配置
# nginx.conf — 正确配置(声明所有可用区)
limit_req_zone $binary_remote_addr zone=wp_limit:10m rate=10r/s;
set_real_ip_from 10.0.0.0/22; # ALB us-east-1a
set_real_ip_from 10.0.4.0/22; # ALB us-east-1b ← 新增
set_real_ip_from 10.0.8.0/22; # ALB us-east-1c ← 新增
real_ip_header X-Forwarded-For;
real_ip_recursive on;real_ip_recursive on 在有多个链式代理时至关重要。启用后,nginx从右到左遍历整个X-Forwarded-For链,丢弃受信任列表中的IP,从而得到真实的客户端IP。
为什么Gutenberg是可见的触发点
WordPress块编辑器在打开任何文章进行编辑时会并行加载50多个JavaScript和CSS资源。在burst=10的配置下,初始的请求爆发已经超过了限流阈值——但在使用ALB IP而非真实IP的情况下,问题呈指数级放大:WordPress集群中的所有编辑器共享同一个限流桶。
症状很一致:打开编辑器时立即出现429错误,重新加载时偶尔恢复,封锁再次出现。进行大量编辑工作的用户报告无规律的间歇性错误——这正是共享桶被集体耗尽时的预期行为。
20天中,DDoS攻击假设被调查并排除。日志中显示的'攻击者'IP实际上是ALB自身的节点。问题从来不是流量大小——而是代理信任配置错误。
诊断:如何识别问题
要检查nginx接收到的源地址IP:
# 添加临时日志以检查源IP
log_format debug_ip '$remote_addr - $http_x_forwarded_for - $request';
access_log /var/log/nginx/debug_ip.log debug_ip;如果$remote_addr显示私有IP段(10.x.x.x、172.x.x.x),而$http_x_forwarded_for包含真实的客户端IP,则real_ip_module未正确解析。原因:set_real_ip_from中未声明ALB子网。
列出活跃的ALB节点IP:
# 通过AWS CLI — 按可用区列出ALB节点IP
aws elbv2 describe-load-balancers --names my-alb-name \
--query 'LoadBalancers[0].AvailabilityZones[*].{AZ:ZoneName,SubnetId:SubnetId}'
# 或通过dig解析ALB DNS名称
dig +short my-alb-name.us-east-1.elb.amazonaws.com注意:在set_real_ip_from中使用子网(CIDR),而不是单个节点IP。ALB节点IP会随每次重新部署、节点替换或扩缩容事件而变化。子网CIDR是稳定的。
经验教训
1. 在代理后面诊断限流问题时,始终使用$http_x_forwarded_for。
2. set_real_ip_from必须覆盖所有ALB可用区的所有子网——不仅仅是主可用区。
3. 存在多个代理(ALB → nginx等)时,需要加入real_ip_recursive on。
4. 在调查DDoS或滥用之前,先验证日志中的IP是否真的是外部客户端IP,还是内部代理IP。
5. Gutenberg作为触发点是可以预见的:50多个资源的并行加载会超过任何保守的burst配置。应考虑为/wp-admin和/wp-json设置限流例外。
结果
添加3行配置(两个缺失的子网和real_ip_recursive on)后,429错误立即降至零。经过20天调查和905,311次累计错误,修复耗时不到5分钟,不需要重启nginx——只需重新加载配置。
最快的修复方案通常是最后被测试的。在这个案例中,专注于流量分析和burst调整推迟了对代理配置的调查。'限流配置正确'这个前提从一开始就是错误的。
