Route SearXNG engine requests via a proxy
Send SearXNG outgoing engine requests through a proxy or Tor: the outgoing settings.yml block, per-engine networks, timeouts, and when it makes results worse.
What routing SearXNG engine requests through a proxy changes
Routing SearXNG engine requests through a proxy changes one thing: the IP address an engine sees when your instance queries it. Your users still reach your VPS directly. Your VPS still builds the results page. Only the outgoing hop moves, from your server's own address to whatever address the proxy exits from.
That is the right fix when engines are refusing your server. It is the wrong fix when the problem is traffic arriving at your instance. The two problems look different in the logs and they live in different config files, so the first job is telling them apart.
Which direction is your problem, inbound or outbound?
Your instance sits between two flows. Inbound is a browser asking your instance for a search page. Outbound is your instance asking Google, Brave or Startpage for results. A proxy in the outgoing: block touches the second flow only.
Outbound trouble looks like this: a search returns, but one engine is missing and the page shows an error for it. Visit /stats/errors and you see rows naming SearxEngineCaptchaException or SearxEngineTooManyRequestsException. The engine then vanishes from results for hours. That is an engine refusing your server's IP, and it is what this guide is about. If those errors are your symptom, read why individual engines return captcha errors first, because a proxy is one answer there and often not the cheapest one.
Inbound trouble looks different: your own users get 429 Too Many Requests from your instance, or the search box returns nothing at all for everybody at once. That is the limiter, and no proxy setting will change it. Go to the inbound rate limit and 429 post instead, or straight to limiter.toml and the Valkey connection it needs if you already know the limiter is the cause.
Where the outgoing block lives in settings.yml
Everything below goes in your admin settings file, /etc/searxng/settings.yml. With the official container image that path is a bind mount, ./core-config/ on the host in the upstream compose file. The file starts with use_default_settings: true, which means SearXNG loads its own defaults and your file only overrides parts of them.
The upstream default block is short:
outgoing:
# default timeout in seconds, can be override by engine
request_timeout: 3.0
# the maximum timeout in seconds
# max_request_timeout: 10.0
useragent_suffix: ""
# The maximum number of concurrent connections that may be established.
pool_connections: 100
# Enables the use of HTTP2
enable_http2: trueThe keys you can add, with their defaults: proxies (unset), using_tor_proxy (false), extra_proxy_timeout (0), retries (0), max_redirects (30), source_ips (unset), verify (true), and networks (empty). Older guides also mention pool_maxsize and keepalive_expiry. Those are gone. SearXNG's HTTP client is now curl_cffi (pinned at 0.16.3 as of September 2026), and keys that are not in the current schema simply do nothing.
One useful side effect of that client change: SOCKS proxy support comes from libcurl and is already installed. You do not need to add httpx[socks] or any other Python package, whatever a 2023 blog post tells you.
Route every SearXNG engine request through one proxy
Test the proxy from the machine SearXNG runs on before you touch any config.
curl -sS --max-time 20 --proxy http://10.10.0.5:3128 https://check.torproject.org/api/ipThat endpoint answers with JSON holding two fields, IsTor and IP. The IP value is the address engines will see once SearXNG uses this proxy. If curl cannot reach the proxy, SearXNG will not either, and you have saved yourself an hour of reading engine errors.
Now the config:
use_default_settings: true
outgoing:
request_timeout: 3.0
extra_proxy_timeout: 10.0
proxies:
all://:
- http://10.10.0.5:3128The key under proxies is a URL scheme pattern. all:// covers every outgoing request. You can narrow it with http:// and https:// keys if you want them treated differently, which is rare. The value is a list, and more than one entry is distributed round robin across requests.
Proxy URLs may use http, https, socks4, socks5 or socks5h. The h in socks5h means the proxy resolves the hostname. Without it, SearXNG resolves the name locally and sends the proxy an IP address, so your DNS (domain name system) queries still leave from your VPS and still name every engine you talk to.
Restart SearXNG so it re-reads the file. With the upstream compose file the service is named core:
docker compose ps
docker compose restart coreOlder compose files name that service searxng, which is why the ps line comes first. On a source install, restart the uwsgi service that loads SearXNG. Then run one search and open /stats/errors. An empty page is what a healthy instance looks like.
Send only some engines through the proxy
A global proxy is a blunt instrument. It slows down the engines that were working fine. Named networks are the better tool: define one under outgoing.networks, then point individual engines at it by name.
use_default_settings: true
outgoing:
request_timeout: 3.0
networks:
viaproxy:
proxies:
all://:
- http://10.10.0.5:3128
extra_proxy_timeout: 10.0
retries: 1
engines:
- name: brave
network: viaproxy
- name: startpage
network: viaproxyThis works because of the merge rule. With use_default_settings: true, entries in your engines: list are merged into the shipped engine definitions by name, so the two blocks above change two engines and leave the other two hundred alone. Get the name wrong and you have quietly defined a new engine that does nothing.
The network: value can also be the name of another engine, which makes that engine share the same client and connection pool. Two presets exist, ipv4 and ipv6, which force the address family without any proxy at all. That is worth trying before a proxy: some engines treat a v6 address from a hosting range worse than the v4 address on the same box, and network: ipv4 costs nothing to test.
One sharp edge: a network: name that does not exist is not caught by a friendly settings check. Copy the name from the networks: block rather than typing it twice.
Use Tor as the outgoing path
SearXNG has direct support for Tor, and it verifies the setup rather than trusting you. Install the daemon first:
sudo apt update && sudo apt install -y tor
sudo systemctl enable --now tor
ss -lnt | grep 9050ss should print a listener on 127.0.0.1:9050. Confirm it is really Tor before SearXNG asks the same question:
curl -sS --max-time 30 -x socks5h://127.0.0.1:9050 https://check.torproject.org/api/ipA healthy answer contains "IsTor":true. Then configure it:
use_default_settings: true
outgoing:
using_tor_proxy: true
extra_proxy_timeout: 10.0
proxies:
all://:
- socks5h://127.0.0.1:9050When using_tor_proxy is true, SearXNG requests https://check.torproject.org/api/ip through your proxy and reads the IsTor field. If that comes back false, every outgoing request fails with:
Network configuration problem: not using TorThere is a second way to trigger that message, and it surprises people. The check first tests the proxy URLs as text and returns false unless every entry starts with socks5h://. Write socks5://127.0.0.1:9050 and SearXNG refuses to proceed, even though a real Tor daemon is listening on the other end. The scheme string is part of the contract.
In Docker there is a third trap. 127.0.0.1 inside the container is the container itself, not your VPS, so a Tor daemon on the host is unreachable and every engine fails at once with connection errors instead of captchas. Run Tor as a second compose service and use socks5h://tor:9050, or bind its SocksPort to an address the container can reach. The general setup is covered in sending a VPS's outbound traffic over Tor, including the part where the Tor daemon itself must be able to bootstrap.
Be clear about what Tor buys you here. Exit node addresses are published in a list that anyone can download, so the large engines filter them first and hardest. Expect more captchas on mainstream engines, not fewer. Tor is worth configuring when you want the onion engines, which cannot be reached any other way, or when hiding your instance's address from engines matters more than result coverage.
Timeouts and retries when a proxy is in the path
A proxy adds a hop, so a request that used to take 400 ms may now take 1.5 seconds. The default request_timeout is 3.0 seconds, which leaves very little room. extra_proxy_timeout adds seconds to the budget whenever proxies are configured, and 10.0 is the value the upstream documentation suggests.
Per-engine timeout: still wins over the global value, so a slow engine can get its own number without slowing everything down. max_request_timeout caps what a user may request through preferences.
retries defaults to 0. Set it to 1 and a failed request is attempted again over a different proxy or source IP from your lists, which is the only reason to list more than one proxy. Do not set it higher. A search waits for its slowest engine, so a retry budget of 3 with a 13 second effective timeout is 39 seconds that a human is sitting through.
Remember that the outgoing network carries more than engine queries. The image proxy and favicon fetches use it too, so a slow proxy shows up as thumbnails that appear late on a page that otherwise rendered fine.
How to tell whether the proxy actually helped
Do not trust a feeling that results look better. Use your instance's own error counters, and change one thing at a time.
The counters behind /stats/errors live in the SearXNG process, not in Valkey, and they need enable_metrics: true under general:, which is the default. Because they are in-process, restarting SearXNG resets them to zero. That is what makes a clean before-and-after possible.
- Restart SearXNG.
- Run the same fixed list of at least twenty queries, through the same engines, in one sitting.
- Open
/stats/errorsand write down the exception class name and the percentage for each engine. - Change exactly one thing in
outgoing:, restart, and run the identical query list again. - Compare the same engine's rows against its own earlier rows.
The restart matters more than it looks. A captcha suspends the engine for 86400 seconds by default, and a too-many-requests error suspends it for 3660 seconds. Both are tunable under search.suspended_times. So the first captcha in a run removes that engine from every later query in the run, and your error percentage ends up measuring one failure rather than twenty. A restart clears the suspension along with the counters.
Ordinary failures behave differently again. They start at ban_time_on_fail: 5 seconds and grow up to max_ban_time_on_fail: 120, so a flaky engine recovers on its own during a test run and a captcha-blocked one does not.
One detail about the numbers on that page: the percentage is rounded into five point steps against the number of searches sent to that engine. With six queries it tells you almost nothing. With twenty or more it becomes readable. And compare an engine only against itself, never against a different engine, because their captcha thresholds have nothing to do with each other.
What gets worse when you add a proxy
This change has real costs, and for some instances it is a downgrade.
- Shared proxy pools carry other people's history. A commercial datacentre proxy has been seen by every engine thousands of times already, while your plain VPS address may be comparatively unknown. Moving can raise your captcha rate.
- Trust moves rather than disappearing. The proxy operator sees which engines you query and when. TLS (transport layer security) keeps the query text private, but with
socks5hthe proxy also performs your DNS lookups, so the destination list is fully visible to it. - Every outbound fetch pays the latency, including image proxy requests that a user is waiting on.
- Free proxy lists are not an option for this. An untrusted middle box that terminates or rewrites traffic is a worse position than the one you started in.
- Rotating residential proxies usually breach the terms of the engines you are querying, and sometimes those of the address owners too. Decide that deliberately.
Before paying for any of this, prune. Half of a busy /stats/errors page is usually engines that were never going to work from a datacentre address, and deciding which engines to keep enabled removes the noise for free. A hardened instance also gets scraped less, which reduces the outbound volume that triggers blocks in the first place, so closing a public instance down to real users is worth doing first.
Failure modes, with the strings you will see
Every engine fails immediately after you add proxies. Not captchas, connection errors, and they arrive faster than a normal search. SearXNG cannot reach the proxy address. In a container, 127.0.0.1 is the container itself. Test with curl --proxy from inside the same context SearXNG runs in.
Network configuration problem: not using Tor. One of three causes. A proxy URL in the list does not start with socks5h://. The proxy is not Tor. Or the check request to check.torproject.org could not complete, which is itself a sign the Tor path is broken.
An engine that used to work now times out. The proxy hop pushed it past request_timeout. Add extra_proxy_timeout: 10.0, or give that engine its own timeout:. If the same engine was timing out before the proxy existed, the proxy is not your problem and engine timeouts and KeyError failures covers what is.
The captcha rate is unchanged. The engine is identifying your instance by something other than the address: request headers, TLS fingerprint, or query patterns that do not look like a browser. Changing the exit IP cannot fix any of those, and more proxies will not either.
Nothing changed at all after editing the file. Either SearXNG was not restarted, or your engines: entry has a name that does not match a shipped engine. Check /config, which shows the settings the running process actually loaded.
FAQ
Will a proxy stop SearXNG captcha errors?
Sometimes, and sometimes it makes them worse. A captcha means the engine decided your requests are automated, and the exit IP is only one of the signals behind that decision. Commercial proxy and Tor exit ranges are published, so engines that filter on address reputation filter those ranges before they filter an ordinary hosting address. Measure it on your own instance with /stats/errors before and after, using the same queries and a restart between runs, rather than assuming a direction.
What is the difference between socks5:// and socks5h:// in SearXNG?
With socks5h:// the proxy resolves hostnames. With socks5:// SearXNG resolves them locally and sends the proxy an IP, so your DNS queries still leave from your server and still reveal every engine you contact. There is a second reason to prefer the h form: when using_tor_proxy: true is set, SearXNG checks the proxy URLs as text and refuses to run unless every one of them starts with socks5h://, which produces the error Network configuration problem: not using Tor even against a working Tor daemon.
Can I route only one engine through the proxy?
Yes. Define a named network under outgoing.networks, then add an entry to engines: with that engine's exact name and a network: key pointing at it. With use_default_settings: true, engine entries are merged into the defaults by name, so only the engines you list are affected. The network: value can also name another engine, which makes both share one client, and the presets ipv4 and ipv6 force an address family with no proxy involved.
My users get 429 from my own instance. Does a proxy help?
No. A 429 served by your instance to your own users comes from the limiter, which controls requests arriving at SearXNG. The outgoing: block controls requests leaving SearXNG toward engines, so the two never touch. Fix the limiter configuration and its Valkey connection instead, and come back to this page only when the errors you see are engine errors on the results page.