The short version
If you used searxng-docker
That repository is archived. The compose file is now in the main searxng/searxng repository. It runs SearXNG and Valkey only, without Caddy. You can reuse your old settings.yml after you change the Valkey hostname.
If an AI app will call it
JSON output is off by default, so API requests return 403. When the limiter is on, each IP can make only four JSON requests an hour, and Firecrawl's requests are rejected every time. To fix this, put the app on its own Docker network and add that network to the limiter's pass list.
We ran every command below with Docker Compose v5.6 and SearXNG 2026.10.19-62366b05c. The config files are the ones we tested, not copies from the documentation.
What SearXNG is, and what it isn't
SearXNG is a free, open-source metasearch engine, licensed under AGPL-3.0. With about 38,600 GitHub stars, it is the most widely used project of its kind. It has no index of its own. It sends each query to other search engines, then merges and ranks their results. The results page has no ads or trackers. The search engines see your server's IP address, but not your users' cookies or browser details.
This design makes SearXNG popular with AI search projects. Vane includes it, self-hosted Firecrawl can use it for /search, and any program that makes HTTP requests can get free JSON results from it. The same design also makes it unreliable. SearXNG depends on search engines run by other companies, and those engines try to block automated traffic. The section on keeping it working covers this.
One change in 2026 surprises returning users: Google and Bing are off by default. In the current settings.yml, general web results come from Brave, DuckDuckGo, Google CSE (Google's custom search service), Wikipedia and Wikidata. The google and bing engines are set to disabled: true. You can turn them back on. The trade-off is explained below.
searxng-docker is archived: what replaced it
For years, the standard install used the separate searxng/searxng-docker repository. You cloned it, edited a .env file, and got SearXNG, Redis and a Caddy reverse proxy with automatic HTTPS. In March 2026, the project reworked its container setup. The old repository is now archived, and its README says it is superseded. Older guides may still work for now, but they follow a layout that is no longer maintained.
| Official compose (current) | searxng-docker (archived) | |
|---|---|---|
| Where it lives | container/ in searxng/searxng | Separate repo, read-only |
| Services | SearXNG (core) and Valkey | SearXNG, Redis/Valkey and Caddy |
| HTTPS | Bring your own reverse proxy | Caddy with Let's Encrypt |
| Config folder | ./core-config/ → /etc/searxng/ | ./searxng/ |
| Port | 8080 on all interfaces unless you set SEARXNG_HOST | 127.0.0.1:8080, with Caddy on 80/443 |
| Redis/Valkey service | valkey (container searxng-valkey) | redis, running a Valkey image |
The most important change is the removal of Caddy. The old setup bound SearXNG to localhost and set up HTTPS automatically. The new setup serves plain HTTP on every network interface, and you choose what to put in front of it. For a search backend that only your own app uses, this is simpler, because there are fewer services to run.
Migrating an existing install
Migration takes four steps. Set up the new layout in a new folder (see Step 1), stop it, move your old config into it, and change one hostname:
$ docker compose down # in the new ./searxng folder $ sudo mv ./searxng-docker/searxng/* ./searxng/core-config/ # in core-config/settings.yml, point Valkey at the new service name: # valkey: # url: valkey://valkey:6379/0 # copy any variables you set in the old .env into the new one, then: $ docker compose up -d
Keep the old folder until the new instance returns search results. If your old instance was public, you now need your own reverse proxy, because the new setup does not include Caddy.
Step 1: install SearXNG with Docker Compose
You need Docker Engine with the Compose plugin, or Podman. The compose file uses :Z volume labels, so it works on systems with SELinux enforcing. We ran it on Fedora without changes. Download the two template files and start the services:
$ 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 $ echo 'SEARXNG_HOST=127.0.0.1' >> .env # listen on localhost only (see below) $ docker compose up -d $ docker compose ps NAME IMAGE STATUS PORTS searxng-core docker.io/searxng/searxng:latest Up 127.0.0.1:8080->8080/tcp searxng-valkey docker.io/valkey/valkey:9-alpine Up 6379/tcp
Open http://localhost:8080 and run a search. On first start, the container creates a minimal settings.yml in core-config/ and replaces the placeholder secret_key with a random value. You no longer need to generate a key with openssl.
Three things the template doesn't tell you
The port is open to the internet by default. Without SEARXNG_HOST, Compose publishes port 8080 on every network interface. Docker's documentation states that published ports bypass ufw, because Docker routes the traffic before the firewall rules apply. Even if ufw blocks all incoming traffic on your VPS, anyone can still reach your instance. Set SEARXNG_HOST=127.0.0.1 unless you want the instance to be public.
Your normal user can't edit the config. On start, the container makes its searxng user (UID 977) the owner of core-config/. As a result, nano core-config/settings.yml fails with “permission denied”. Use sudo, or edit the file inside the container with docker compose exec --user root core vi /etc/searxng/settings.yml.
Docker Hub limits anonymous downloads. If docker compose pull fails with a rate-limit error, change the image: line to the copy at ghcr.io/searxng/searxng.
Step 2: turn on the JSON API
AI apps need results in JSON, not HTML. A new instance does not provide JSON:
$ curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:8080/search?q=test&format=json' 403
The default settings allow only html. According to the API documentation, a request for any format that is not enabled returns 403. The generated file starts with use_default_settings: true, so you only need to add the settings you change. This is the complete file we used:
use_default_settings: true server: secret_key: "keep-the-one-the-container-generated" image_proxy: true limiter: true # only if people or bots you don't control can reach it — Step 3 search: formats: - html - json valkey: url: valkey://valkey:6379/0 # the compose service name, not localhost
$ docker compose restart core $ curl -s 'http://127.0.0.1:8080/search?q=searxng+docker&format=json' -H 'Accept-Language: en' | jq '.results | length' 20
The response has seven top-level keys: query, results, answers, corrections, infoboxes, suggestions and unresponsive_engines. Each result includes url, title, content (a short snippet), the engines that returned it, and a score. SearXNG returns snippets, not full pages. If your model needs more than a few sentences of text, another tool must fetch the pages. In a fully local Fireplexity, Firecrawl does this.
Other useful parameters: categories (for example general, news, images), engines to pick engines explicitly, language, time_range (day, month, year), pageno and safesearch (0–2).
Step 3: the limiter, and why it blocks AI apps
The limiter is SearXNG's bot protection. It requires Valkey, which is why the compose file includes Valkey. It is off by default, and it turns on automatically when you set public_instance: true. If your instance is reachable from the internet, you should turn it on. Without it, bots that send many requests to your instance can get your server's IP blocked by Google and DuckDuckGo.
The problem is that the limiter treats most API clients as bots. We read the source code and then tested it. For every /search request, the limiter returns 429 in these cases:
- The request has no
Accept-Languageheader, itsAcceptheader does not allow HTML, or it does not accept gzip or deflate compression. - The user agent matches a block list that includes
curl,python-requests,Python,Go-http-client,Javaandokhttp. - The request asks for a format other than HTML, and the same IP has already made four such requests in the past hour (
API_MAX = 4over a 3,600-second window). This limit is fixed in the code. You cannot change it in the settings.
These are the results from our test instance with the limiter on:
| Client | Result | Why |
|---|---|---|
Plain curl | 429 on every request | User agent on the bot list, no Accept-Language |
| Firecrawl-style axios request | 429 on every request | Axios sends no Accept-Language |
Same, with Accept-Language: en | 200 × 4, then 429 | The four-an-hour API limit |
Any client on a pass_ip network | 200 × 8 of 8 | Pass list skips every check |
Firecrawl hides this problem. Its SearXNG client catches errors and returns an empty result list, so a blocked search looks the same as a search with no results. An answer engine built on it then answers without sources, or invents an answer.
The fix: decide who will use the instance
Private backend — only your app
- Leave
limiter: false. - Don't publish the port publicly:
SEARXNG_HOST=127.0.0.1, or no port at all and a shared Docker network. - No outside client can reach it, so it needs no rate limit.
Public instance — people and your app
- Turn on
limiter: trueand put a reverse proxy in front. - Add the proxy to
trusted_proxiesso the limiter sees real client IPs. - Put your app on its own network and list it in
pass_ip.
For a public instance, give the app a network with a fixed subnet. Do not rely on Docker's default addresses. On our machine, the compose network used 192.168.80.0/20, not the 172.x range that most examples assume. As a result, a pass_ip entry for 172.16.0.0/12 had no effect, and no error was shown. Create this file next to docker-compose.yml:
services:
core:
networks: [default, ai]
# ports: !reset [] # uncomment to stop publishing the port at all (Compose 2.24+)
networks:
ai:
name: searxng-ai # fixed name, so other compose projects can join it
ipam:
config:
- subnet: 10.89.0.0/24
Next, add the network to the limiter's configuration. Create limiter.toml next to settings.yml. It only needs the settings you change:
[botdetection] trusted_proxies = [ '127.0.0.0/8', '::1', '192.168.80.0/20', # the network your reverse proxy container is on — check yours ] [botdetection.ip_lists] pass_ip = [ '10.89.0.0/24', # searxng-ai: your AI app, no limits ]
The limiter reads the client IP from the X-Forwarded-For header, but only when the request comes from a proxy listed in trusted_proxies. By default, only localhost is trusted. Behind a proxy container, every visitor would appear to have the same IP and would share one limit of four API requests an hour. To find your network ranges, run docker network inspect searxng_default.
We tested this setup with both files in place:
- Browsers that connected through the proxy loaded search pages normally.
- A client that requested JSON through the proxy was blocked after four requests.
- The app on the
searxng-ainetwork was never blocked.
Step 4: connect your AI app
Once the searxng-ai network exists, any container on it can reach SearXNG at http://searxng-core:8080. You don't need to expose the port outside the machine.
Firecrawl (and Fireplexity on self-hosted Firecrawl)
Self-hosted Firecrawl uses SearXNG for /search when SEARXNG_ENDPOINT is set. Search runs in the api service, so connect that service to the network:
# .env SEARXNG_ENDPOINT=http://searxng-core:8080 SEARXNG_CATEGORIES=general # optional; SEARXNG_ENGINES=brave,duckduckgo also works # docker-compose.override.yaml services: api: networks: [backend, searxng-ai] networks: searxng-ai: external: true
We tested this setup with a container that sent the same kind of request as Firecrawl: an axios-style client, format=json, and no Accept-Language header. With the limiter on, all six requests succeeded, because the network is on the pass list. Firecrawl requests 20 results per page and uses the url, title and content fields. It does not get news or image results this way, so Fireplexity's news and image panels stay empty on self-hosted Firecrawl. The self-hosting guide covers the rest of the setup, including how to switch to a local model.
Vane
Vane's full image includes its own SearXNG. To use your instance instead, run the slim image and set the URL:
$ docker run -d --name vane -p 127.0.0.1:3000:3000 --network searxng-ai \
-e SEARXNG_API_URL=http://searxng-core:8080 \
-v vane-data:/home/vane/data itzcrazykns1337/vane:slim-latest
Vane's README also requires the Wolfram Alpha engine, which is disabled by default. Enable it in settings.yml:
engines:
- name: wolframalpha
disabled: false
Your own code
Any HTTP client works. Send GET /search?q=...&format=json, read results, and check unresponsive_engines in every response. This list is the only sign that an engine has been blocked. Logging it is a simple, low-cost way to monitor your instance.
Keeping a private instance working
The steps above fix the limits on your own instance. They cannot stop the search engines from limiting you. Every query comes from one server IP, and the large search engines treat steady automated traffic from one IP as scraping. Our test instance ran on a home internet connection. After a few minutes of light testing, DuckDuckGo returned a CAPTCHA, and Google (enabled with engines=google) returned “access denied”. Bing and Google CSE kept working. Data-centre IPs are usually blocked sooner.
When an engine returns an error, SearXNG stops using it for a set time instead of retrying immediately. These are the current default times:
| Error from the engine | Suspended for | Setting |
|---|---|---|
| Access denied, HTTP 402/403 | 3 minutes | SearxEngineAccessDenied: 180 |
| Too many requests, HTTP 429 | 3 minutes | SearxEngineTooManyRequests: 180 |
| CAPTCHA | 1 hour | SearxEngineCaptcha: 3600 |
| Cloudflare access denied | 1 day | cf_SearxEngineAccessDenied: 86400 |
| Google reCAPTCHA | 7 days | recaptcha_SearxEngineCaptcha: 604800 |
| Cloudflare CAPTCHA | 15 days | cf_SearxEngineCaptcha: 1296000 |
Until early 2026, a CAPTCHA suspended an engine for a full day, and older guides still give that figure. As engines are suspended, a self-hosted answer engine returns fewer and worse results, without any warning. These steps help:
- Monitor errors. The
/stats/errorspage on your instance lists each failing engine and the reason. Also logunresponsive_enginesfrom your app. - Use several engines. With several engines in
general, a suspended engine costs you some results, not all of them. If you choose a single engine withengines=, your app is only as reliable as that engine. - Enable Google with care. Add
- name: googlewithdisabled: falseto turn it back on. On a busy instance, Google is usually the first engine to block you. - Use proxies for high volume. The
outgoing.proxiessetting insettings.ymltakes a list of proxies to rotate between. On a server with several IP addresses,outgoing.source_ipsrotates between them. - Cache results in your app. AI apps often repeat the same searches. Even a short cache reduces the number of queries sent to the engines.
If you need reliable search without this maintenance, use a paid search API, where the provider keeps search working. Our comparison of Firecrawl alternatives lists Tavily, Exa and Brave-based options with per-query prices.
Hardware, updates and pinning
SearXNG needs few resources. On our test machine, the core container used about 127 MB of memory and Valkey used 4 MB, with almost no CPU use when idle. The SearXNG image takes 383 MB of disk space and the Valkey image 67 MB. A JSON query took about 1.8 seconds, almost all of it spent waiting for the search engines. The smallest VPS plan is enough. The server's IP address matters more than its size.
$ docker compose down $ docker compose pull $ docker compose up -d # to stay on a known build, pin it in .env: # SEARXNG_VERSION=2026.10.19-62366b05c
Images are tagged by date and commit, and new images are released most days. Engine fixes are released quickly, so an old image is a common reason an engine stops working. latest is fine for a personal instance. If an app depends on the instance, pin a version. Before you download a new docker-compose.yml, check what changed, as the documentation advises.
Common questions
What is SearXNG?
A free, open-source metasearch engine under AGPL-3.0. It has no index of its own: it sends your query to engines such as Brave, DuckDuckGo, Google and Wikipedia, merges what comes back, and strips cookies and identifying data from the requests. You can use a public instance or run your own, usually with Docker.
Is searxng-docker deprecated?
Yes. The repository was archived in 2026 and its README says it is superseded. New installs use docker-compose.yml and .env.example from the container/ folder of searxng/searxng. The new setup runs SearXNG and Valkey only, with no bundled Caddy.
Why does SearXNG return 403 for format=json?
The JSON format is off by default. Add json to search.formats in settings.yml and restart the container.
Why does my app get 429 Too Many Requests?
The limiter is on. It rejects requests without browser-like headers, including Accept-Language, and allows each IP four non-HTML requests an hour. Keep the limiter off on a private instance only your app can reach, or put the app's network in pass_ip in limiter.toml.
Is SearXNG safe to use?
The software is open source and widely used, and it removes cookies and identifying data before forwarding a query. The question is who runs the instance. A public instance's operator can log your searches. On your own instance, the search engines see your server's IP. Your own instance on a server you control is the most private option, as long as you haven't left its port open to the internet.
Can I use a public SearXNG instance for my AI app?
Not reliably. Many public instances disable JSON, and those that allow it usually run the limiter, which caps API use at four requests per IP per hour. Their operators try to block automated traffic like this. Run your own instance instead. It needs about 130 MB of memory.
Sources
- SearXNG: container installation — compose instancing, management commands, GHCR mirror, Valkey hostname, migrating from searxng-docker
- searxng/searxng
container/—docker-compose.yml,.env.example,entrypoint.sh(secret key generation, ownership) andDockerfile(Granian, UID 977), read at commit 62366b0; compose rework in PR #5906 - searxng/searxng-docker — archived repository and superseded notice
- SearXNG default
settings.yml— output formats, limiter and Valkey defaults, suspension times, engine defaults, outgoing proxies; Google default changed in PR #6546 - SearXNG limiter — Valkey requirement,
limiter.toml,trusted_proxies,pass_ip; header checks and the API limit from the botdetection source - SearXNG search API — parameters, and 403 for formats that are not enabled
- SearXNG: why use a private instance — privacy properties of public and private instances
- Docker: packet filtering and firewalls — published ports bypass ufw
- firecrawl/firecrawl —
apps/api/src/search/v2/searxng.tsanddocker-compose.yaml, read at commit dcbc947 for the request shape, environment variables and error handling - Vane on GitHub — slim image,
SEARXNG_API_URL, JSON and Wolfram Alpha requirements
Tested on 19 October 2026 with SearXNG 2026.10.19-62366b05c, Valkey 9 and Docker Compose v5.6.0 on Fedora, from a residential connection. Memory, latency and blocking figures are from that one machine and will differ on yours, especially the blocking. This site is an independent community page and is not affiliated with SearXNG, Firecrawl or the Vane project.