15%

全场主机优惠15%

测试技能,享折扣

使用代码:

Skills
开始使用
10.10.2024

如何解决 429 请求次数过多错误

429 Too Many Requests 错误是 RFC 6585 中定义的 HTTP 状态码,表示客户端已超过服务器或中间代理设置的速率限制。服务器将拒绝进一步的请求,直到速率限制窗口重置,并可选择性地返回 Retry-After 标头,指示客户端需要等待多长时间。

与反映服务器端容量故障的 503 Service Unavailable 不同,429 是一种有意为之的、基于策略的拒绝。理解这一区别至关重要:解决方案并不总是扩展基础设施,而是要识别*谁*发送了过多请求、*为什么*,然后在堆栈的正确层面纠正该行为。

429 错误的实际原因

该错误出现在多个层面,混淆它们会导致误诊。根本原因属于以下四类之一:

  • 服务器端速率限制 — Web 服务器(Apache、Nginx)、反向代理(HAProxy、Varnish)或 CDN 边缘节点(Cloudflare、Fastly)对每个 IP 或每个令牌的请求数量设置阈值。
  • 应用层限流 — WordPress 插件、自定义中间件或 API 网关独立于 Web 服务器设置自己的限制。
  • 第三方 API 配额耗尽 — 您的应用程序调用外部 API(Google Maps、Stripe、OpenAI)的速度超过提供商的配额限制,429 错误传播回最终用户。
  • 恶意或不受控制的自动化流量 — 暴力破解登录尝试、激进的爬虫、配置错误的监控脚本或编写不当的爬虫耗尽请求预算。

一个经常被忽视的边缘情况:共享主机环境中,相邻租户的流量峰值消耗了共享连接池,导致您的应用程序从上游负载均衡器收到 429 响应,即使您自己的代码运行正常。如果您使用的是 共享虚拟主机 方案,并且在自身流量没有相应峰值的情况下看到间歇性 429 突发,这是首先需要验证的假设。

第一步:识别过多请求的来源

在未确定来源的情况下修复 429 错误只是猜测。从数据入手。

读取 Apache 访问日志

grep " 429 " /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

此命令提取所有 429 响应,统计每个 IP 地址的出现次数,并进行排名。在几分钟内出现数千次的 IP 要么是机器人、配置错误的脚本,要么是攻击者。

读取 Nginx 访问日志

awk '$9 == 429 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

与请求路径关联

grep " 429 " /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20

如果 /wp-login.php/xmlrpc.php 在输出中占主导地位,则您正在处理暴力破解或凭据填充攻击。如果 API 端点(如 /api/v1/search)位居榜首,则罪魁祸首可能是配置错误的客户端或爬虫。

使用 fail2ban 发现规律

fail2ban-client status
fail2ban-client status nginx-req-limit

如果 fail2ban 配置了 nginx-req-limit 规则,它将准确显示哪些 IP 因违反速率限制而被封禁,从而节省大量日志解析时间。

第二步:在 Web 服务器层面配置速率限制

Apache:使用 mod_ratelimitmod_evasive

网上流传的 .htaccess 代码片段通常使用 mod_rewrite 返回 403,而非正确的 429。更符合语义且更稳健的方法是使用 mod_evasive 进行 DoS 缓解。

在 Debian/Ubuntu 上安装并配置 mod_evasive

apt install libapache2-mod-evasive
a2enmod evasive

然后添加到您的 Apache 虚拟主机或全局配置中:

<IfModule mod_evasive20.c>
    DOSHashTableSize    3097
    DOSPageCount        5
    DOSSiteCount        50
    DOSPageInterval     1
    DOSSiteInterval     1
    DOSBlockingPeriod   10
    DOSEmailNotify      admin@yourdomain.com
    DOSLogDir           /var/log/mod_evasive
</IfModule>

这会封锁每秒访问同一页面超过 5 次、或每秒访问整个站点超过 50 次的 IP,冷却期为 10 秒。

要通过 Apache 上的 .htaccessmod_rewrite 返回正确的 429 响应,您需要自定义错误文档:

ErrorDocument 429 "Too Many Requests"

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP_USER_AGENT} ^.*(SemrushBot|AhrefsBot|MJ12bot|DotBot).*$ [NC]
    RewriteRule .* - [R=429,L]
</IfModule>

Nginx:带有正确突发处理的 limit_req_zone

Nginx 的 ngx_http_limit_req_module 是最有效的速率限制工具之一。经常被错误配置的关键参数是 burstnodelay

/etc/nginx/nginx.conf 或专用的包含文件中:

http {
    # Zone keyed by client IP, 10MB shared memory, 10 requests/second baseline
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;

    # Return 429 instead of the default 503
    limit_req_status 429;

    server {
        location /api/ {
            limit_req zone=api_limit burst=20 nodelay;
        }

        location /wp-login.php {
            limit_req zone=login_limit burst=3 nodelay;
        }
    }
}

关键细节:如果不设置 limit_req_status 429,Nginx 默认对速率受限的请求返回 503。将其设置为 429 在语义上更正确,并允许客户端实现正确的 Retry-After 退避逻辑。

nodelay 与不使用该标志的区别:

  • 不使用 nodelay:超出的请求会排队并在延迟后被处理,消耗工作连接。
  • 使用 nodelay:超出突发限制的请求立即以 429 被拒绝,更快地释放资源。对登录端点和公共 API 使用 nodelay

添加 Retry-After 标头

遵守 RFC 6585 的客户端将遵循 Retry-After 标头。在 Nginx 中,将其添加到错误响应中:

location /api/ {
    limit_req zone=api_limit burst=20 nodelay;
    add_header Retry-After 60 always;
}

第三步:诊断并修复 WordPress 插件冲突

WordPress 是自我造成 429 错误的常见来源。在每次页面加载时轮询外部 API 的插件——获取关键词数据的 SEO 工具、回调分析插件,或查询支付网关的 WooCommerce 扩展——可能会迅速耗尽速率限制。

系统性隔离步骤:

  1. 通过 WordPress 仪表板停用所有插件,或者如果仪表板无法访问,通过 WP-CLI 停用:
wp plugin deactivate --all
  1. 逐一重新激活插件,每次激活后进行测试:
wp plugin activate plugin-slug
  1. 在单独的终端中监控访问日志,同时重新激活插件:
tail -f /var/log/nginx/access.log | grep " 429 "
  1. 一旦确定了有问题的插件,在永久停用之前检查其 API 轮询间隔或请求频率选项的设置。

常见问题插件:Jetpack(统计和同步模块)、Yoast SEO(连接到 MyYoast 时)、WooCommerce Subscriptions,以及任何在循环中使用 WordPress 内置 wp_remote_get() 而没有瞬态缓存的插件。

正确的修复方法几乎从不是删除插件,而是确保使用 WordPress 瞬态缓存 API 响应:

$cached = get_transient( 'my_api_response' );
if ( false === $cached ) {
    $response = wp_remote_get( 'https://api.example.com/data' );
    set_transient( 'my_api_response', $response, HOUR_IN_SECONDS );
    $cached = $response;
}

第四步:在应用代码中实现 API 请求限流

当您的应用程序作为*客户端*调用第三方 API 时,您有责任遵守其速率限制。依赖被动捕获 429 响应是糟糕的工程实践——应实现主动限流。

Node.js 与 p-throttle

import pThrottle from 'p-throttle';

const throttle = pThrottle({
    limit: 10,       // max 10 calls
    interval: 1000   // per 1000ms (1 second)
});

const throttledFetch = throttle(async (url) => {
    const response = await fetch(url);
    return response.json();
});

Python 与 tenacity 实现指数退避

仅限流在 API 设有突发限制时是不够的。将限流与 429 响应的指数退避结合使用:

import requests
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception

def is_rate_limited(exception):
    return (
        isinstance(exception, requests.HTTPError)
        and exception.response.status_code == 429
    )

@retry(
    retry=retry_if_exception(is_rate_limited),
    wait=wait_exponential(multiplier=1, min=2, max=60),
    stop=stop_after_attempt(5)
)
def call_api(url):
    response = requests.get(url)
    response.raise_for_status()
    return response.json()

这将以指数退避(2秒、4秒、8秒、16秒、32秒)最多重试 5 次,上限为 60 秒——这种模式在尊重 API 提供商基础设施的同时能够优雅地恢复。

遵守 Retry-After 标头

如果 API 返回 Retry-After 标头,请使用它而非固定退避:

def call_with_retry_after(url):
    response = requests.get(url)
    if response.status_code == 429:
        retry_after = int(response.headers.get("Retry-After", 5))
        time.sleep(retry_after)
        return call_with_retry_after(url)
    response.raise_for_status()
    return response.json()

第五步:在多个层面封锁和管理机器人

机器人很少诚实地表明身份,但它们确实在用户代理字符串、请求模式和标头异常中留下痕迹。

在 Nginx 中封锁已知恶意机器人

map $http_user_agent $bad_bot {
    default         0;
    ~*SemrushBot    1;
    ~*AhrefsBot     1;
    ~*MJ12bot       1;
    ~*DotBot        1;
    ~*PetalBot      1;
    ~*serpstatbot   1;
}

server {
    if ($bad_bot) {
        return 429;
    }
}

通过 robots.txt 管理爬取速率

User-agent: Googlebot
Crawl-delay: 2

User-agent: *
Crawl-delay: 10

请注意,Crawl-delay 并非所有爬虫都遵守,但它表明了意图,并受到 Bing、Yandex 和大多数行为良好的机器人的尊重。

Web 应用防火墙(WAF)集成

在 CDN 边缘运行的 WAF(Cloudflare、AWS WAF、Sucuri)可以在流量到达源服务器之前执行速率限制,从而大幅减少负载。例如,Cloudflare 的速率限制规则可以配置为对超过阈值的 IP 进行质询或封锁,而无需对源服务器配置进行任何更改——这对高流量站点来说是一个显著的运营优势。

第六步:调整 WordPress 中的安全插件设置

如果您使用 Wordfence,速率限制阈值通常设置得较为保守,可能会对合法用户触发误报,尤其是在大量使用 AJAX 或已登录用户活动频繁的站点上。

导航至 Wordfence > Firewall > All Firewall Options > Rate Limiting 并检查:

  • 如何处理 Google 爬虫 — 设置为”已验证的 Google 爬虫不受速率限制”,以防止对 SEO 产生影响。
  • 如果任何人的请求超过 — 提高默认阈值(例如,对于内容丰富站点上的已登录用户,从每分钟 240 次提高到 480 次)。
  • 如果爬虫的页面浏览量超过 — 根据您的实际爬取预算需求进行调整。

调整后,监控 Wordfence 实时流量视图 24–48 小时,以确认合法用户不再被封锁。

第七步:清除客户端缓存并诊断浏览器层面的问题

浏览器中缓存的 429 响应虽然罕见,但如果响应包含 Cache-Control: max-age 标头(服务器配置错误),则有可能发生。标准的 429 响应不应被缓存。

Chrome:

Settings > Privacy and Security > Clear browsing data

选择缓存的图片和文件以及Cookie 和其他网站数据,然后清除。

使用 curl 直接验证响应标头,以排除浏览器特定行为:

curl -I -X GET https://yourdomain.com/problematic-endpoint

在响应中查找 Cache-ControlRetry-AfterX-RateLimit-* 标头。这些标头准确揭示了哪个层面在执行限制以及客户端何时可以重试。

对比:按层面划分的速率限制方法

层面工具 / 方法粒度开销最适用于
CDN / 边缘Cloudflare Rate Limiting、AWS WAF按 IP、按路径、按标头极低(源服务器前)DDoS、爬虫缓解
反向代理Nginx `limit_req_zone`按 IP、按区域API 端点、登录页面
Web 服务器Apache `mod_evasive`按 IP、按页面低至中共享主机、旧版技术栈
应用层中间件限流、API 网关按用户、按令牌、按密钥多租户 SaaS、REST API
CMS 插件Wordfence、iThemes Security按 IP、按用户角色中至高WordPress 专项防护
客户端代码`tenacity`、`p-throttle`、退避按出站请求应用端第三方 API 消费

何时联系您的托管服务提供商

在以下情况下,请升级至您的托管服务提供商:

  • 429 错误来自您无法控制的上游负载均衡器或反向代理。
  • 日志分析显示您的应用程序请求量在预期范围内,但错误仍然持续。
  • 您需要在计划的流量峰值期间(产品发布、营销活动)临时豁免速率限制。
  • 错误仅出现在特定地理区域,表明存在 CDN 或任播路由问题。

在使用 VPS 主机 方案时,您可以直接访问服务器配置文件,并可以实施上述所有 Nginx 和 Apache 更改,无需提交支持工单。在 独立服务器 等托管基础设施上,您的提供商支持团队可以协助处理内核级连接跟踪和硬件防火墙规则,这些超出了应用层配置所能实现的范围。

如果您的技术栈包含控制面板,带 cPanel 的 VPS 通过图形界面提供 ModSecurity 和 Apache 速率限制设置,简化了没有深厚命令行专业知识的团队的配置工作。

使用 SSL 保护 API 端点

一个经常被忽视的因素:未加密的 HTTP 端点更容易受到重放攻击和凭据填充攻击,这些攻击会导致暴力破解引发的 429 错误。强制使用有效的 SSL 证书 的 HTTPS,可确保身份验证令牌和会话 Cookie 不会被自动化工具拦截和重放,从根源上减少一类触发速率限制的流量。

技术决策矩阵:选择正确的修复方案

使用此检查清单将您的诊断引导至正确的解决方案:

  • /wp-login.php/xmlrpc.php 上的 429 — 针对这些路径强化 Nginx limit_req,如果不需要则完全封锁 xmlrpc.php,启用 Wordfence 暴力破解防护。
  • 来自您自己应用代码的 API 端点上的 429 — 实现带有 Retry-After 标头解析的指数退避;添加瞬态/Redis 缓存以降低出站调用频率。
  • 共享主机上间歇性影响所有用户的 429 — 迁移到 VPS 以获得独立资源和可配置的速率限制。
  • 来自您的应用消费的第三方 API 的 429 — 审计调用频率,实现请求队列,积极缓存响应,并联系 API 提供商讨论配额增加。
  • 由特定机器人或爬虫引起的 429 — 在 WAF 或 Nginx map 层面通过用户代理封锁;在封锁前通过反向 DNS 验证合法爬虫(Googlebot)。
  • 仅在浏览器中出现而非在 curl 中出现的 429 — 清除浏览器缓存;检查是否有 Service Worker 缓存了错误响应;检查 429 响应上的 Cache-Control 标头。
  • 日志中没有可识别规律的 429 — 检查上游 CDN 或负载均衡器日志;限制可能在应用日志不可见的基础设施层面执行。

常见问题

429 错误和 503 错误有什么区别?

429 是当客户端超过定义的请求速率时发出的有意为之的、基于策略的拒绝。503 表示服务器由于过载或维护而暂时无法处理请求。429 的修复方案是调整速率限制或纠正客户端行为;503 的修复方案是扩展容量或恢复服务。

看到 429 时是否应该总是提高速率限制?

不。只有在合法流量被封锁时,提高限制才是合适的。如果 429 是由机器人、暴力破解尝试或配置错误的脚本引起的,提高限制会使问题更严重,因为它允许更多恶意流量通过。始终先识别来源。

Retry-After 标头有什么作用,是否应该始终包含它?

Retry-After 标头告诉客户端在重试前需要等待多少秒。RFC 6585 建议在每个 429 响应中包含它。行为良好的 HTTP 客户端和 API 消费者将遵守它,从而减少加剧原始速率限制问题的重试风暴。

429 错误会影响我的 SEO 吗?

会的,如果 Googlebot 持续收到 429 响应,它将降低对您网站的爬取频率,这可能会延迟新内容或更新内容的索引。Wordfence 和类似插件应配置为豁免已验证的 Google 爬虫的速率限制。监控 Google Search Console 的爬取统计报告,查看服务器错误的峰值。

如何防止我自己的应用程序在外部 API 上触发 429 错误?

实施以下组合:主动限流(将出站请求速率限制在 API 文档阈值以下)、积极的响应缓存(使用适当 TTL 将 API 结果存储在 Redis 或 Memcached 中),以及被动指数退避(解析 Retry-After 标头并相应退避)。切勿在每次页面加载时同步调用 API,而不在其前面设置缓存层。

15%

全场主机优惠15%

测试技能,享折扣

使用代码:

Skills
开始使用