SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-09-05

SearXNG 429 错误与速率限制排查修复

SearXNG 出现 429 可能是本地限制器拦截,也可能是上游搜索引擎封锁服务器 IP。查看日志中的 limiter、IP 和引擎名称,约 1 分钟内确定原因并采取对应修复。

SearXNG 返回 429 错误的原因

自托管 SearXNG 实例返回 429 错误通常有两个互不相关的原因,而需要修复的速率限制通常不是您以为的那一个。第一个原因发生在本地:SearXNG 自己的限制器判断请求来自机器人,并以状态码 429 返回 Too Many Requests。第二个原因发生在上游:搜索引擎拒绝了您服务器的 IP 地址,用户看到的通常是缺少部分内容的结果页面,而不是 429 错误。

这两种情况没有共同的修复方法。限制器由您控制,因此可以修改。上游封锁发生在 Google 一侧,因此修改您的 settings.yml 无法解除封锁。日志通常能在约 1 分钟内告诉您属于哪种情况,因此应从日志开始排查。

本指南假定您使用的是 在自己的 VPS 上运行自托管 SearXNG 实例 中介绍的容器安装方式。以下所有设置名称均来自截至 2026 年 8 月核对过的当前上游文档和源代码。

更改设置前先查看日志

打开日志窗口,重现问题。

cd ./searxng/
docker compose logs -f searxng-core

限制器消息来自名为 searx.limiter 的日志记录器,并包含 IP 地址。命中阻止列表时,日志内容为 BLOCK 203.0.113.10: matched BLOCKLIST;命中允许列表时,日志内容为 PASS 203.0.113.10: matched PASSLIST。如果限制器无法访问计数器存储,日志会显示 The limiter requires Valkey, please consult the documentation,这表示根本没有进行计数。

每次单独的机器人检查都会以 debug 级别记录,因此默认情况下看不到这些日志。在 settings.yml 中开启 debug,执行一次测试:

general:
  debug: true

随后,日志会在客户端网络旁添加类似 NOT OK (http_accept_language) 的行,并指出失败的检查。测试完成后立即关闭 debug,因为上游明确要求已部署实例不得在启用 debug 的情况下运行。

引擎故障的表现完全不同。日志会记录引擎名称,而不是 IP 地址。最常见的故障是超时:

HTTP requests timeout (search duration : 3.1 s, timeout: 3.0 s)

此问题也有对应的页面。将 enable_metrics 保持为默认值 true 时,实例会在 /stats/errors 记录引擎错误;/preferences 会列出当前正在响应的引擎。如果 /stats/errors 已满,而日志中没有 searx.limiter 行,则问题不在限制器。

在开始排查任何问题前固定版本

上游容器配置由两个文件组成。

mkdir -p ./searxng/core-config/
cd ./searxng/

curl -fsSL \
    -O https://raw.githubusercontent.com/searxng/searxng/master/container/docker-compose.yml \
    -O https://raw.githubusercontent.com/searxng/searxng/master/container/.env.example

cp -i .env.example .env

Compose 文件会拉取 docker.io/searxng/searxng:${SEARXNG_VERSION:-latest}。未设置变量意味着 latest,而 latest 表示实例会在下一次 docker compose pull 时发生变化。因此,上周正常工作的设置可能不再匹配读取它的代码。SearXNG 标签包含日期和提交。以 2026 年 8 月为准,上游 .env.example 中的示例标签是 2026.3.25-541c6c3cb,因此请在 .env 中设置一个实际版本:

SEARXNG_VERSION=2026.3.25-541c6c3cb

检查已发布的标签,并固定到经过实际测试的版本,然后针对固定目标进行排查。同一个 .env 文件还包含您的密钥,因此在将该目录提交到任何位置前,请先阅读 Docker Compose 中环境文件和密钥的工作方式。

限流器需要 Valkey,否则无法运行

限流器按客户端统计请求次数,这些计数必须在多个工作进程之间共享。该存储使用 Valkey,它是 Redis 的维护分支。较旧的 SearXNG 指南将此设置称为 redis:。当前版本读取 valkey:,因此应从当前文档复制键名,不要从旧文章中复制。部分页面的内容更早,介绍的是 Searx,而不是 SearXNG;两者是不同的代码库,使用的限流器也不同。因此,在从页面复制配置块之前,先确认页面针对的是哪一个项目。

use_default_settings: true
server:
  secret_key: "change-this-value"
  limiter: true
  public_instance: false
valkey:
  url: valkey://searxng-valkey:6379/0

上游 compose 文件已经在 docker.io/valkey/valkey:9-alpine 镜像上运行 searxng-valkey 服务,因此该主机名可在 compose 网络内解析。同样的值也可以通过 SEARXNG_VALKEY_URL 环境变量设置。当 SearXNG 和 Valkey 位于同一台主机时,可以使用 Unix socket URL(unix:///path/to/socket.sock?db=0)。

存储服务缺失时的行为取决于另一个键。设置为 public_instance: false 时,限流器会记录 Valkey 错误并放弃运行,因此实例会继续提供服务,但完全不执行限流。设置为 public_instance: true 时,进程会改为调用 sys.exit(1),因为开放且机器人防护失效的实例会在一天内从每个搜索引擎收集 CAPTCHA(全自动区分计算机和人类的公开图灵测试)。如果设置 public_instance: true 后容器立即进入循环重启,这就是该情况;每次退出前的最后一行日志会指出 Valkey。

限流器实际统计的内容

ChartSearXNG limiter: requests allowed per client IP, defaults in ip_limit.py
The data behind this chart
[
  {
    "label": "Burst, normal client",
    "max_requests": 15,
    "window": "20 seconds"
  },
  {
    "label": "Burst, flagged client",
    "max_requests": 2,
    "window": "20 seconds"
  },
  {
    "label": "Sustained, normal client",
    "max_requests": 150,
    "window": "10 minutes"
  },
  {
    "label": "Sustained, flagged client",
    "max_requests": 10,
    "window": "10 minutes"
  },
  {
    "label": "Any non-HTML format",
    "max_requests": 4,
    "window": "1 hour"
  },
  {
    "label": "Flagged requests before block",
    "max_requests": 3,
    "window": "30 days"
  }
]

普通客户端在 20 秒突发窗口内可发送 15 个请求,在 10 分钟窗口内可发送 150 个请求。一旦某个请求被标记为可疑,同一客户端在每个突发窗口内只能发送 2 个请求。最后一行的限制最严格:如果某个地址在 30 天窗口内出现 3 个被标记的请求,该地址会被重定向到起始页,而不是执行搜索;日志会记录 BLOCK: too many request from ... in SUSPICIOUS_IP_WINDOW (redirect to /)。

这些数字是 searx/botdetection/ip_limit.py 中的常量。它们不是配置项,limiter.toml 也不会公开这些值,因此要修改它们必须编辑源代码。/etc/searxng/limiter.toml 实际控制的是用于对客户端分组的地址前缀、受信任代理列表、可选的链接令牌检查,以及允许列表和阻止列表。

请求会根据请求头检查结果被标记为可疑,每项检查都有一个名称,您会在调试日志中看到这些名称:

  • http_accept:Accept 请求头不包含 text/html。
  • http_accept_encoding:Accept-Encoding 请求头既未列出 gzip,也未列出 deflate。
  • http_accept_language:不存在 Accept-Language 请求头。
  • http_connection:Connection 请求头被设置为 close。
  • http_user_agent:User-Agent 缺失,或匹配已知的机器人模式。
  • http_sec_fetch:Sec-Fetch-Mode 或 Sec-Fetch-Dest 请求头的内容不是浏览器发送的内容。

浏览器会发送上述所有请求头。普通的 curl 调用几乎不会发送其中任何一个,因此手动编写的测试请求第一次尝试就会被标记,而相同的搜索在浏览器标签页中却可以正常工作。这就是“在我的浏览器中可以运行,但我的脚本收到 429”成为正常结果的原因,而不是什么难以解释的问题。

反向代理后限流器会同时阻断所有用户

这是导致正常运行的实例失效的最常见原因。SearXNG 会从 X-Forwarded-For 中第一个不受信任的 IP 获取客户端地址;如果没有,则回退到 X-Real-IP;如果仍没有,则使用建立连接的地址。是否信任这些请求头,由 limiter.toml 中的 trusted_proxies 决定。

如果代理地址不在该列表中,系统会忽略这些请求头,每个访客都会以代理地址访问。这样所有访客共用一个计数器;当总数在 10 分钟内超过 150 次请求后,整个站点会一起被阻断。一个用户反复刷新几次结果页,就可能导致所有人都无法访问。

过度信任的风险更大。如果列表中包含公网地址段,任何访客都可以发送自己的 X-Forwarded-For 请求头,并为每个请求选择新的身份。这样一来,知道该方法的用户就可以绕过限流。这里只应列出您自己的代理连接所使用的地址。在 Docker 中,这通常是 172.16.0.0/12 内的桥接网络,而该配置行默认处于注释状态。

[botdetection]
ipv4_prefix = 32
ipv6_prefix = 48

trusted_proxies = [
  '127.0.0.0/8',
  '::1',
  '172.16.0.0/12',
]

代理也必须发送这些请求头。Nginx 不会自动添加其中任何一个:

location / {
    proxy_pass http://127.0.0.1:8080;

    proxy_set_header Host              $host;
    proxy_set_header Connection        $http_connection;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
}

Caddy 和 Traefik 会自动设置转发请求头,因此使用它们时,您只需完成 trusted_proxies 这一部分。相关取舍请参阅为自托管服务选择反向代理。要验证任一配置,请启用 debug,使用手机移动数据搜索一次,并确认日志行中的网络地址是手机地址,而不是代理地址。

您的代理每小时获得 4 次 API 请求

JSON 输出默认处于禁用状态,因此需要为代理启用该选项:

search:
  formats:
    - html
    - json

现在再次查看图表中的这一行。任何请求非 HTML 格式的请求都会在独立窗口中计数:每个地址在每个 1 hour 内最多 4 次请求。研究代理在一个任务中就会耗尽该额度,之后的每次调用都会返回 429。无法通过提高限制解决,因为该数值写在源代码中。

正确的解决方法是告知限制器:此客户端不是未知客户端。将其地址添加到 limiter.toml 中的放行列表:

[botdetection.ip_lists]
block_ip = []

pass_ip = [
  '10.8.0.0/24',
]

pass_searxng_org = true

pass_ip 的优先级高于其他所有方法,因此加入放行列表的客户端也会跳过请求头检查,直接调用 curl 即可正常工作。将范围限制在尽可能小的范围内,并优先使用 VPN 子网或容器网络,不要使用任何可路由的网络。另一个正确的解决方法是让代理完全避开公网路径:将其指向内部网络中的容器地址,这样代理及其限制器就不会看到这些流量。为 AI 代理配置 SearXNG 搜索技能介绍了具体配置方法。

应避免将代理指向他人运行的公共实例。这样最容易导致志愿者的 IP 地址被上游搜索引擎封禁;这也是 JSON 格式默认禁用的根本原因。

当引擎阻止您时

ChartHow long SearXNG suspends an engine, search.suspended_times defaults
The data behind this chart
[
  {
    "label": "SearxEngineTooManyRequests",
    "suspended_seconds": 3600,
    "roughly": "1 hour"
  },
  {
    "label": "SearxEngineAccessDenied",
    "suspended_seconds": 86400,
    "roughly": "1 day"
  },
  {
    "label": "SearxEngineCaptcha",
    "suspended_seconds": 86400,
    "roughly": "1 day"
  },
  {
    "label": "recaptcha_SearxEngineCaptcha",
    "suspended_seconds": 604800,
    "roughly": "7 days"
  },
  {
    "label": "cf_SearxEngineCaptcha",
    "suspended_seconds": 1296000,
    "roughly": "15 days"
  }
]

当引擎返回自身的 429 响应或 CAPTCHA 页面时,SearXNG 会抛出命名异常,并在一段时间内停止向该引擎发送请求。429(请求过多)响应会将其暂停 3600 秒。普通 CAPTCHA 或访问被拒绝的响应会将其暂停 1 day。通过 Cloudflare 提供的 CAPTCHA 会将其暂停 15 days,这是列表中默认时长最长的一项,因为这表示拦截发生在边缘层,重试不会有帮助。您命中的 3 个 CAPTCHA 行中的哪一行,会决定接下来哪些尝试值得进行;确定实例记录的是哪种异常后,CAPTCHA 错误有各自的修复方法。

普通故障使用不同的设置。超时或解析错误会根据 search.ban_time_on_fail 计算出较短的暂停时间。该值默认为 5 秒,并由 search.max_ban_time_on_fail 限制为最多 120 秒。因此,响应缓慢的引擎会在几分钟内自动恢复,而被阻止的引擎会停用数小时。这解释了一个常被认为是随机的问题:开始时结果正常,随后某个引擎的结果在整个下午都消失。

在归咎于其他原因之前,应先处理超时问题。默认的 request_timeout 为 2.0 秒。对于位于远离引擎最近边缘服务器的小型 VPS,这个值可能过短。

outgoing:
  request_timeout: 3.0
  max_request_timeout: 10.0
engines:
  - name: bing
    timeout: 5.0

request_timeout 是所有引擎的默认值,max_request_timeout 是上限,单个引擎也可以设置自己的 timeout。提高这些值可以减少故障,但会增加页面延迟。因此,应以半秒为步长逐步调整,并监控 /stats/errors,不要直接跳到 10。

如果某个引擎确实阻止了您的地址,请将其移除。每次搜索都要等待最慢的引擎,因此保留一个永久暂停的引擎只会增加延迟,且不会返回结果。

use_default_settings:
  engines:
    remove:
      - google

使用 docker compose restart searxng-core 应用更改,然后执行几次搜索并重新加载 /stats/errors。实际使用 5 分钟后页面为空,表示更改已生效。

数据中心 IP 会被视为机器人

您的 VPS 地址属于托管服务地址段,大型搜索引擎通常会将这些地址段判定为自动化流量。无论请求头设置得多规范、请求频率多低,其中一些引擎都会对来自此类地址的每个请求显示 CAPTCHA。settings.yml 中的任何设置都无法改变这一判定。搜索引擎看到的是您的服务器,而不是输入查询的用户;这也是您选择自行托管后需要承担的全部隐私权衡。在假定 SearXNG 能提供更多隐私保护之前,建议先阅读SearXNG 实际隐藏了多少信息。

您可以改变要请求哪些引擎,以及实例是否公开列出。供一个家庭使用的私有实例很少会触发限制。运行在托管服务 IP 上的公共实例会被最严格的引擎陆续暂停访问。这是软件的正常运行状态,不是配置故障。SearXNG 可以通过 outgoing.proxies 或 outgoing.using_tor_proxy 将引擎请求经由代理转发,从而让流量使用其他地址。出口节点和廉价代理池的评分通常比托管服务地址段更差,因此这种做法可能导致搜索结果质量下降。

先监控实例,及时发现问题

即使所有引擎都处于暂停状态,SearXNG 仍会在其端口上响应。因此,只监控状态码的运行时间检查会保持正常,但实例实际上不会返回任何内容。应检查响应内容:发起一次真实搜索,并匹配响应正文中预期出现的词语。Uptime Kuma 关键词监控无需额外工具即可完成此操作。每次版本升级后也要检查 /stats/errors,因为引擎会更改 HTML,解析器可能因此失效,而且这与速率限制无关。

FAQ

为什么将 SearXNG 放在反向代理后面后,所有访问者都会收到 429?

因为限流器将代理视为客户端。只有当连接地址列在 /etc/searxng/limiter.toml 的 trusted_proxies 中时,SearXNG 才会读取 X-Forwarded-For。如果未列出该地址,所有访问者共用一个计数器,并会一起超过每 10 分钟 150 个请求的限制。添加代理连接所使用的地址。在 Docker 中,这通常是网桥范围 172.16.0.0/12。同时确保代理发送 X-Real-IP 和 X-Forwarded-For。不要列出不受您控制的网络范围,因为受信任网络中的任何访问者都可以设置该请求头,并为每个请求选择新的身份。

SearXNG 限流器每小时允许多少个 API 请求?

每个 IP 地址每小时 4 个。请求 HTML 以外格式的请求会单独计入一个小时窗口。该限制在 searx/botdetection/ip_limit.py 中设置,而不是在 limiter.toml 中设置,因此无法通过配置提高。代理或脚本在一个任务中就会达到该限制。将客户端地址添加到 limiter.toml 中的 pass_ip,或者通过内部网络访问该实例,这样限流器就不会看到该请求。

为什么搜索结果为空,却没有 429 错误?

拒绝您服务器的是搜索引擎,而不是用户。在您自己的实例上打开 /stats/errors。其中会列出失败的每个搜索引擎及失败原因。出现 CAPTCHA 或拒绝访问记录,表示该搜索引擎阻止了您服务器的 IP 地址。随后,SearXNG 会暂停该搜索引擎:收到请求过多的响应后暂停 1 小时,收到 CAPTCHA 后暂停 1 天。没有本地设置可以解除上游阻止。因此,请移除阻止您地址的搜索引擎,只保留能够正常响应的搜索引擎。

私有实例是否应启用限流器?

如果除了您之外没有其他访问者,请保留 limiter: false。它会增加 Valkey 依赖,也会阻止您自己的脚本,并且无法防御您不会收到的流量。实例获得公网地址后,应立即启用它,同时启用 public_instance: true。这两个设置必须同时使用:启用 public_instance: true 但没有正常工作的 Valkey 时,进程会以状态码 1 退出,而不是在未受保护的情况下运行。