SSD Nodes Learn 🎉 VPS from $5.50/mo
How to do am Matt ConnorBy Matt Connor

How to Fix SearXNG 429 Errors and Rate Limits

SearXNG 429 errors fit come from your own limiter, while blocked engines show missing results. Check the log first to know the correct fix.

Why SearXNG dey return 429 errors

Self-hosted SearXNG instance dey return 429 errors for two different reasons, and the rate limit wey you need fix usually no be the one wey you dey assume. The first reason dey local: SearXNG own limiter decide say request come from bot and answer Too Many Requests with status 429. The second one dey upstream: search engine refuse your server IP address, and this one go reach your users as results page wey some things dey miss, not as 429.

The two cases no get the same fix. The limiter na your own, so you fit change am. Upstream blocking dey for Google's side, so nothing inside your settings.yml go remove am. Log go tell you which one affect you within about one minute, so start there.

This guide assume say you use the container install wey dem describe for self-hosted SearXNG instance for your own VPS. Every setting name below come from current upstream documentation and source, wey dem check for August 2026.

Read log before you change any setting

Reproduce the problem while log window dey open.

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

Limiter messages dey come from logger wey dem name searx.limiter, and dem dey show IP address. Blocklist hit dey read BLOCK 203.0.113.10: matched BLOCKLIST, while allowlist hit dey read PASS 203.0.113.10: matched PASSLIST. If limiter no fit reach its counter store, log go talk The limiter requires Valkey, please consult the documentation. This mean say nothing dey counted at all.

Log dey record each bot check for debug level, so you no go see am by default. Turn debug on for one test inside settings.yml:

general:
  debug: true

After that, log go add lines wey get shape like NOT OK (http_accept_language) beside client network, and e go show the check wey fail. Turn debug off again afterwards, because upstream warn say make you no run deployed instance with debug on.

Engine failures no resemble that one at all. Dem dey name engine instead of IP, and timeout na the most common one:

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

Page dey for this matter too. If enable_metrics remain for its default value of true, your instance go record engine errors for /stats/errors, while /preferences go list the engines wey dey answer currently. If /stats/errors full and log no get any searx.limiter lines, limiter no be the problem.

Fix the version before you debug anything

The upstream container setup na two files.

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

The compose file dey pull docker.io/searxng/searxng:${SEARXNG_VERSION:-latest}. If variable no set, e mean latest, and latest mean the instance go change under you for the next docker compose pull. So, setting wey work last week fit stop to match the code wey dey read am. SearXNG tags get date and commit. The example tag for the upstream .env.example as of August 2026 na 2026.3.25-541c6c3cb, so set real one for .env:

SEARXNG_VERSION=2026.3.25-541c6c3cb

Check the tags wey dem publish and pin the release wey you don test, then debug against fixed target. The same .env file hold your secret key, so read how env files and secrets dey work for Docker Compose before you commit that directory anywhere.

Limiter need Valkey, otherwise e no go run

Limiter dey count requests from each client, and worker processes need share those counts. Valkey na the store for this work. Na maintained fork of Redis. Older SearXNG guides dey call this setting redis:. Current releases dey read valkey:, so copy the key name from current documentation, no be from old post.

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

The upstream compose file don already run one searxng-valkey service for docker.io/valkey/valkey:9-alpine image, so that host name go resolve inside compose network. You fit set the same value with SEARXNG_VALKEY_URL environment variable. Unix socket URL (unix:///path/to/socket.sock?db=0) too dey work when SearXNG and Valkey dey share one host.

Wetin go happen when the store no dey depend on one other key. With public_instance: false, limiter go log the Valkey error and stop, so the instance go continue to serve without any rate limiting. With public_instance: true, the process go call sys.exit(1) instead, because open instance with broken bot protection fit collect CAPTCHAs (completely automated public turing test to tell computers and humans apart) from every engine within one day. If container dey restart in loop immediately after you set public_instance: true, na this be the cause. The last line before each exit go name Valkey.

Wetin the limiter dey actually count

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"
  }
]

A normal client fit make 15 requests inside 20 second burst window and 150 inside 10 minute window. Once dem flag request as suspicious, the same client go drop to 2 for each burst window. The last row na the harshest one: after 3 flagged requests inside 30 day window, dem go redirect that address go start page instead of allowing search, and the log go talk say BLOCK: too many request from ... in SUSPICIOUS_IP_WINDOW (redirect to /).

These numbers na constants for searx/botdetection/ip_limit.py. Dem no be settings, and limiter.toml no expose dem, so to change dem means say you must edit the source. Wetin /etc/searxng/limiter.toml dey control na the address prefixes wey e use group clients, the list of trusted proxies, the optional link token check, and the pass and block lists.

Header checks dey flag request as suspicious, and each check get name wey you go see for debug log:

  • http_accept: the Accept header no contain text/html.
  • http_accept_encoding: the Accept-Encoding header no name either gzip or deflate.
  • http_accept_language: no Accept-Language header dey.
  • http_connection: the Connection header set to close.
  • http_user_agent: User-Agent dey missing or e match known bot pattern.
  • http_sec_fetch: the Sec-Fetch-Mode or Sec-Fetch-Dest header no be wetin browser dey send.

Browser dey send all these ones. Plain curl call dey send almost none of dem, so hand-written test request go get flagged on the first try, while the same search go work for browser tab. Na why “e dey work for my browser, but my script dey get 429” na the normal result, no be mystery.

Behind reverse proxy limiter dey block everybody at once

This na the most common way to spoil instance wey dey work. SearXNG dey take client address from the first untrusted IP for X-Forwarded-For, e fall back to X-Real-IP, then e fall back again to the address wey open the connection. trusted_proxies for limiter.toml dey decide whether SearXNG go trust those headers at all.

If proxy address no dey that list, SearXNG go ignore the headers and every visitor go arrive with proxy address. Dem go then share one counter, so the whole site go block together once total request pass 150 within 10 minutes. One user wey reload results page some times fit bring everybody down with am.

To trust too much worse pass. If you list public range, any visitor fit send their own X-Forwarded-For header and choose new identity for every request. This go turn limiter off for anybody wey know how to try am. List only the address wey your own proxy dey connect from. For Docker, na usually bridge network inside 172.16.0.0/12, and that line ship commented out.

[botdetection]
ipv4_prefix = 32
ipv6_prefix = 48

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

Proxy sef gats send the headers. Nginx no dey add any of dem by itself:

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 and Traefik dey set forwarded headers for you, so with dem you only need handle trusted_proxies side of the work. You fit see the trade-offs for how to choose reverse proxy for self-hosted service. To verify either setup, turn debug on, search once with your phone on mobile data, and confirm say network for the log line na your phone address, no be proxy address.

Your agent get four API requests per hour

JSON output dey disabled by default, so agent need make e add am:

search:
  formats:
    - html
    - json

Now read the chart row again. Any request wey ask for format wey no be HTML dey counted for its own window: 4 requests per 1 hour, per address. Research agent fit use all of that for one task, and every call after that go return 429. You no fit raise the limit, because the number dey inside the source.

The clean fix na to tell the limiter say this client no be stranger. Add its address to the pass list for limiter.toml:

[botdetection.ip_lists]
block_ip = []

pass_ip = [
  '10.8.0.0/24',
]

pass_searxng_org = true

pass_ip get priority pass every other method, so allowlisted client go skip the header checks too and bare curl call go work. Keep the range as small as you fit, and prefer VPN subnet or container network instead of anything wey routable. The other clean fix na to keep the agent completely off the public path: point am to the container address for the internal network, where proxy and its limiter no go see the traffic. How to give AI agent SearXNG search skill cover how to wire this up.

The option wey you suppose avoid na pointing agent to public instance wey another person dey run. Na the fastest way to make upstream engines block volunteer's IP address, and na why JSON format dey disabled by default for the first place.

When engines dey block you instead

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"
  }
]

When engine answer with e own 429 or CAPTCHA page, SearXNG go raise named exception and stop to ask that engine for some time. A too-many-requests answer go suspend am for 3600 seconds. Plain CAPTCHA or access-denied answer go suspend am for 1 day. CAPTCHA wey Cloudflare serve go suspend am for 15 days, wey be the longest default for the list, because that answer mean say the block dey for the edge and retry no go help.

Normal failures dey use different settings. Timeout or parse error go suspend the engine for short time wey come from search.ban_time_on_fail. E default na 5 seconds, and search.max_ban_time_on_fail cap am at 120 seconds. So slow engine go recover by itself within some minutes, while blocked engine go disappear for hours. This difference explain one symptom wey people dey report as random: results dey okay, then results from one engine go disappear for the rest of the afternoon.

Timeouts dey worth fixing before you blame anybody. The default request_timeout na 2.0 seconds, and that one tight for small VPS wey dey far from the engine nearest edge server.

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

request_timeout na the default for every engine, max_request_timeout na the ceiling, and one engine fit get e own timeout. If you raise dem, page latency go increase but failures go reduce. So increase am by half seconds and monitor /stats/errors instead of jumping straight to 10.

For engine wey truly dey block your address, remove am. Every search dey wait for the slowest engine, so engine wey remain suspended permanently go add latency and return nothing.

use_default_settings:
  engines:
    remove:
      - google

Apply changes with docker compose restart searxng-core, then run some searches and reload /stats/errors. If page empty after five minutes of real use, that mean the change work.

Datacentre IP go dem treat like bot

Your VPS address dey belong to hosting range, and big search engines dey score those ranges as automation. Some of dem dey show CAPTCHA for every request from that kind address, no matter how correct the headers be or how slow the request pace be. No setting for settings.yml fit change that judgement.

Wetin you fit change na which engines you ask and whether your instance dey listed publicly. Private instance wey one household dey use rarely dey trigger anything. Public instance for hosting IP go collect suspensions for the strictest engines, and na the normal software behaviour be that, no be fault for your config. SearXNG fit route engine requests through proxy with outgoing.proxies or outgoing.using_tor_proxy, wey go move the traffic go another address. Exit nodes and cheap proxy pools dey get worse score pass hosting ranges, so expect say this move fit make results worse.

Watch the instance make you sabi problem first

SearXNG dey answer for its port even when all engines dey suspended, so uptime check wey only dey watch status code go still show green while the instance no dey return anything. Check the content instead: make request for real search and match word wey you expect inside response body. Uptime Kuma keyword monitoring dey do exactly that without any extra tool. Watch /stats/errors after every version bump too, because engines dey change their HTML and parser fit break even when no rate limit dey involved.

FAQ

Why SearXNG dey return 429 to every visitor after I put am behind reverse proxy?

Na because limiter dey count the proxy as the client. SearXNG only reads X-Forwarded-For when the connecting address dey listed for trusted_proxies inside /etc/searxng/limiter.toml. If e no dey listed, every visitor dey share one counter, and dem all go cross the 150 requests per 10 minutes limit together. Add the address wey your proxy dey connect from. For Docker, na usually the bridge range 172.16.0.0/12. Make sure say proxy dey send X-Real-IP and X-Forwarded-For. Never list range wey you no control, because trusted network go allow any visitor set that header and choose new identity for every request.

How many API requests per hour SearXNG limiter dey allow?

Four for each IP address per hour. Any request wey ask for format wey no be HTML dey count for separate one-hour window. That limit dey set for searx/botdetection/ip_limit.py, no be limiter.toml, so you no fit increase am from config. An agent or script fit pass the limit for one task. Add the client address to pass_ip for limiter.toml, or reach the instance through internal network where limiter no dey see the request.

Why my search results dey come back empty without 429 error?

Na the engines dey reject your server, no be your users. Open /stats/errors for your own instance. E name each engine wey fail and explain why. CAPTCHA or access-denied entry mean say that engine block your server IP address. SearXNG then suspend the engine for one hour after too-many-requests answer, and for one day after CAPTCHA. No local setting fit remove upstream block. Remove engines wey dey block your address, and keep the ones wey dey answer.

I suppose enable limiter for private instance?

If na only you dey reach the instance, leave limiter: false. E add Valkey dependency and e block your own scripts, while e dey protect against traffic wey you no get. Enable am immediately the instance get public address, together with public_instance: true. That pair na intentional: with public_instance: true and no working Valkey, the process go exit with status 1 instead of running without protection.