Fix SearXNG engine timeouts and KeyError
Read the SearXNG service log and /stats to tell a timeout from a suspended engine or a crash like KeyError, then fix each one in settings.yml.
What a SearXNG engine error means
A SearXNG engine error on the results page means one engine failed while the rest of the search worked. SearXNG queries every selected engine in parallel, keeps whatever comes back in time, and then reports the engines that did not answer. The page tells you which engine failed and the class of the failure. The service log tells you the cause.
Three different problems produce that red message, and they need opposite fixes. An engine can miss the deadline, which is a timeout. It can be suspended, which means SearXNG refused to call it at all because of earlier failures. Or the request can succeed and the parser can crash on the response, which is where KeyError comes from. Classify what you have before you change any setting, because raising a timeout does nothing for a crashed parser, and updating the image does nothing for an engine that is simply slow. The steps below assume a SearXNG instance you run yourself, because they all need the service log.
Read the message on the results page first
Under the results, SearXNG shows a collapsible section called Messages from the search engines. Open it. Each engine that answered is listed with its response time. Each engine that did not is listed with its name and a short error type. Beside each one there is a link, View error logs and submit a bug report, which opens that engine's own stats page.
That error type is generated from the exception SearXNG caught, not written by a human. SearXNG takes the exception class, prefixes it with its Python module, and drops the prefix when the module is builtins. So the shape of the text is your first classifier. A bare name with no dot, such as KeyError, is a Python builtin, which means code inside SearXNG raised it while handling the response. A httpx. prefix means the HTTP client gave up on the request. A searx.exceptions. prefix means SearXNG read the response and recognised it, for example as a CAPTCHA (completely automated public Turing test to tell computers and humans apart) or as a too-many-requests reply.
One error type is a hand-written string rather than an exception name: the lowercase word timeout, with no dot in it. That one means the engine's worker thread was still running when the whole search ran out of time, so SearXNG stopped waiting and moved on without it. It is not the same as a timeout class from httpx, which means the single HTTP request hit its own deadline first. Both are timeouts, but the first one points at the search deadline and the second points at the engine, and section 6 below treats them differently.
Copy the error type down exactly as your page prints it. Do not match it against an example from a guide, because the useful detail is which module the name came from, and that differs per instance and per engine version.
Where the engine error line appears in the log
The results page gives you a class. The log gives you the cause. Every engine logs under the logger name searx.engines.<engine name>, so that string is the filter that turns a noisy log into the few lines you need.
With the Compose file that SearXNG's container documentation ships, the service is named core:
docker compose logs --since 10m core | grep searx.engines
docker compose logs -f coreOlder Compose files, including the ones from the separate searxng-docker repository, name the service searxng. Run docker compose config --services if you are not sure which you have. For a container started by hand with docker run --name searxng, use docker logs --since 10m searxng. For a source install behind uWSGI, the same lines go to that service's journal:
sudo journalctl -u uwsgi --since "10 min ago" | grep searx.enginesOn Debian and Ubuntu the packaged uWSGI is driven through an init wrapper rather than a plain systemd unit, so if that journal is empty, look for the application log under /var/log/uwsgi/ instead.
Now read what you got. For a crash you are looking for a Python traceback. Its last line names the exception and the key or index that was missing, and the frames above it name the file under searx/engines/ that raised it. That file name is the engine's parser, and it is the most useful fact in the whole investigation, because it proves the failure happened while reading a response that did arrive. For a timeout there is no traceback. You get a single line naming the engine, the time the request spent, and the limit it was allowed. If you need more detail to reproduce something, general.debug: true in settings.yml raises the log level, but it is a development setting and should not stay on for an instance other people use.
What /stats tells you about failure rate and response time
The log explains one event. /stats on your own instance explains whether that event is a pattern. The engine table has five columns: Engine name, Scores, Result count, Response time, and Reliability.
Read Reliability first. It is 100 minus the summed percentage of recorded primary errors for that engine, so 100 means nothing has failed since the counters started and a low number means most calls are failing. You can sort on it directly with /stats?sort=reliability. An engine sitting at 100 that produced one red message is a single upstream hiccup and needs no config change. An engine near 0 is broken for your instance and will stay broken until you change something.
Response time is reported as a total plus the HTTP part, and the processing part is the total minus the HTTP part. That split is what separates a slow network path from slow parsing work. Compare the total against the timeout that engine is allowed. An engine whose median total already sits just under the deadline will time out on every query where the upstream is slightly slower than usual, which is why the failure looks random.
Click the engine name for its own page. It lists Errors and exceptions and Warnings, each with a percentage. That is the failure history rather than one event, so it answers the question the results page cannot: is this the first time, or the fiftieth.
Two limits on this page are worth knowing. The metrics live in process memory, so a restart resets every counter and a freshly restarted instance always looks healthy. And if general.enable_metrics is set to false, the page has nothing to report at all. If you run more than one worker process, the counters belong to the worker that served your request, so the numbers can move between reloads.
Classify the failure into one of three cases
A timeout. The error type is the word timeout, or a class from httpx. /stats shows that engine's response time at or near its allowed limit. The log line talks about elapsed time and no traceback appears.
A suspension. SearXNG never called the engine. It stores the reason from the earlier failure and reports that stored reason for as long as the suspension lasts. The signature is timing: the error appears immediately instead of after a wait, and the reason usually names something the upstream said, such as access denied or too many requests.
Upstream breakage. The error type is a Python builtin such as KeyError, IndexError or TypeError, and the log holds a traceback that ends inside searx/engines/. The engine answered. Its answer no longer matches what the parser expects.
Fix a SearXNG engine timeout by raising both timeouts
There are two timeout settings, and raising only one of them often changes nothing. outgoing.request_timeout is the default deadline for a single HTTP request to an engine, and a per-engine timeout overrides it for that engine. outgoing.max_request_timeout is the upper limit on the whole search.
Here is the part that catches people. The deadline for the whole search is the largest timeout among the engines selected for that search, and when max_request_timeout is set, the search deadline becomes the smaller of those two numbers. So a per-engine timeout: 10.0 on an instance with max_request_timeout: 5.0 gives that engine five seconds, not ten, because the worker thread is cut off when the search deadline passes. The engine then reports the plain timeout error type. Raise both values, or the larger one is ignored.
use_default_settings: true
outgoing:
request_timeout: 5.0
max_request_timeout: 15.0
engines:
- name: wikidata
timeout: 10.0With use_default_settings: true, entries under engines are merged into the shipped defaults by name, so those two lines change one engine and leave every other engine untouched. The name has to match the default name exactly, spaces included, or you have quietly defined a new engine that does nothing.
Know the cost before you raise anything. Because the search deadline is taken from the slowest engine in the search, a single engine allowed ten seconds makes every query that includes it able to take ten seconds, and users feel that on successful searches as well as failing ones. Prefer a per-engine timeout over a large global request_timeout. Also check outgoing.retries: each retry spends its time inside the same search deadline, so retries on a slow engine turn a sometimes-slow engine into a reliable timeout.
Then restart so the file is read again. A bind-mounted settings.yml needs a restart and nothing more, since the image did not change, which is the difference between restarting and rebuilding a Compose service.
docker compose restart core
docker compose logs --since 1m coreCheck that log before you reload the browser. Invalid YAML stops SearXNG from starting, and the parse error names the line number, so a container that keeps restarting after an edit is almost always an indentation mistake rather than a broken engine.
Fix a suspension by waiting it out or disabling the engine
A suspension is SearXNG protecting itself and your server's address. After a failure it stops calling that engine for a while. The short kind starts from search.ban_time_on_fail, 5 seconds by default, grows with the number of consecutive failures, and is capped by search.max_ban_time_on_fail, 120 seconds by default. A single successful search resets it. So a brief red message that clears itself within a minute or two needs no action.
Specific upstream responses get their own, much longer suspensions, listed under search.suspended_times. The shipped defaults are long deliberately: one hour after a too-many-requests response, and one day after an access-denied or a CAPTCHA response. A Cloudflare CAPTCHA suspends the engine for 1296000 seconds, which is fifteen days. Shortening those numbers to make the message disappear is the wrong move, because retrying an engine that just blocked you is what deepens the block. When the reason names a CAPTCHA, the cause is on the other side and it has its own guide: a CAPTCHA returned by an engine you are querying.
If an engine fails often enough that you would rather stop asking it, disable it instead of deleting it:
engines:
- name: qwant
disabled: truedisabled: true keeps the engine in the list but off by default, so a user can still switch it on in preferences, and you can switch it back with one line. To drop it from the instance completely, use the remove list under use_default_settings.engines. Which engines earn their place is a longer decision, covered in which SearXNG engines to enable and which to turn off.
Do not reach for display_error_messages: false. It hides the message for that engine and changes nothing else, so the engine keeps failing and you have thrown away the only signal you had.
Why does a SearXNG engine fail with KeyError?
A KeyError from an engine means the parser asked the response for a field that was not in it. Engine modules read the upstream answer, often JSON, and pull named fields out of it. When the upstream renames a field, stops sending one, or returns a different document altogether such as a consent or block page, that lookup fails and Python raises KeyError naming the field it wanted. IndexError is the same fault against a list that came back shorter than expected. TypeError usually means a field arrived as null, meaning no value, where the parser expected an object it could read into.
No setting in settings.yml fixes this, because the defect is in code that no longer matches the data. You have two honest options: update, or disable.
Update first. Engine parsers are repaired in SearXNG itself, usually quickly, so an instance that has not been pulled for months is the common reason a reader meets this error at all. Pin a tag instead of running latest, so you know which version you moved to and can move back to the one that worked. The Compose file reads the tag from SEARXNG_VERSION in .env:
SEARXNG_VERSION=2026.9.25-12f8b6515docker compose pull core
docker compose up -d core
docker compose logs --since 2m coreTags are published as a date plus a commit hash, and the one above was the newest on Docker Hub as of 25 September 2026. Read the current tag from the image's own tag list rather than copying that string, then bring up only that one service so the rest of the stack keeps running. Repeat the failing search afterwards and check the engine's Reliability on /stats again, since the counters were reset by the restart and now measure only the new version.
If the parser fix has not been released yet, search the SearXNG issue tracker at github.com/searxng/searxng/issues for the engine name. Someone has usually filed it with the same traceback. Disable the engine until the fix ships, then remove the disabled: true line.
An engine error is not a 429 from your own limiter
The boundary matters, because the two look similar in a browser and the fixes point in opposite directions.
Everything above is your instance telling you that an upstream engine failed. Your instance itself worked: it answered the request and rendered a results page, with one row reporting a failure. If instead your own instance replies with HTTP 429 Too Many Requests, the whole page or API call fails and there are no engine messages to read, because the search never ran. That is SearXNG's limiter, a separate subsystem with its own configuration file and its own Valkey dependency. Start with SearXNG answering your own requests with 429, and read how limiter.toml and Valkey fit together before changing limiter settings.
There is a second, easier confusion. A too-many-requests error type inside Messages from the search engines is the upstream engine rate limiting your server's outbound address. It points the other way, and no limiter setting on your instance affects it. The levers there are querying fewer engines per search, spreading the load, or presenting a different exit address.
A routine you can repeat
- Reproduce with one engine only, by prefixing the query with that engine's bang shortcut, for example
!ddgfollowed by your search terms. - Open Messages from the search engines and write down the error type exactly as printed.
- Filter the service log for
searx.enginesaround that timestamp and decide whether you have a traceback or a timing line. - Open
/stats, then the engine's own page, and read Reliability plus the response time split. - Apply the single fix that matches the class, restart, and go back to step 1 to confirm it.
The reason this order works is that each step narrows the cause before any setting changes. Editing settings.yml first is what turns a fifteen-minute fix into an afternoon, because a timeout raised on a crashed parser produces exactly the same red message, and now you have two changes to undo.
FAQ
Why does my SearXNG results page say an engine is unresponsive?
Because that engine did not return usable results before the search finished, while the other engines did. Open the Messages from the search engines section under the results to see the engine name and the error type. A bare exception name such as KeyError means SearXNG's own parser crashed on the response. A name starting with httpx. means the HTTP request failed or timed out. The lowercase word timeout means the whole search ran out of time while that engine was still working. Then read the service log filtered on searx.engines for the cause behind the class.
How do I raise the timeout for one SearXNG engine?
Add a per-engine timeout under engines in settings.yml, matching the engine's default name exactly, and keep use_default_settings: true so the entry merges into the defaults instead of replacing them. Check outgoing.max_request_timeout at the same time: the search deadline is the largest timeout among the selected engines, capped by max_request_timeout, so a per-engine value above that cap has no effect. Restart the service and watch the log to confirm the file parsed.
What does a KeyError from a SearXNG engine mean?
It means the engine answered and the parser then asked its response for a field that was absent, so Python raised KeyError with the missing name. The traceback in the log ends in a file under searx/engines/, which tells you the fault is in code rather than in your network or your configuration. The usual cause is an upstream API or HTML change that a newer SearXNG release already handles, so update to a pinned image tag and retest. Disable that engine while you wait if the fix is not out yet.
How long does SearXNG suspend an engine after it fails?
Ordinary failures start at search.ban_time_on_fail, 5 seconds by default, increase with each consecutive failure, and stop at search.max_ban_time_on_fail, 120 seconds by default. One successful search clears the state. Specific upstream refusals use the much longer values under search.suspended_times: one hour for a too-many-requests response, one day for access denied or a CAPTCHA, and fifteen days for a Cloudflare CAPTCHA. During a suspension SearXNG does not contact the engine and repeats the stored reason from the original failure.
Is an engine error the same as my instance returning 429?
No. An engine error is one row inside a results page that your instance successfully produced, and it describes an upstream engine failing. A 429 from your instance means the request never reached the search stage, because SearXNG's limiter rejected it, and that is configured in limiter.toml with Valkey behind it. If you see no results page and no engine messages at all, treat it as a limiter problem rather than an engine problem.