GICT knowledge base
How to Self-Host SearXNG with Docker: Step by Step Guide
Published
SearXNG puts results from several search services behind one interface. You choose the engines and the presentation, while the VPS sends the search requests. It is a metasearch application, not a crawler with its own independent web index.
Before you start
Use a fresh Ubuntu 24.04 VPS, an SSH account with sudo access, and an SSH client on your computer. Allow outbound DNS and HTTPS for image downloads and searches. Keep your existing SSH access; you do not need to open port 8080 in the provider firewall. This setup does not require a domain or a public web certificate.
Directly using a search engine is simpler if you only want occasional searches without maintaining a server. Self-hosting gives you control over this interface, but upstream engines still receive your queries and the VPS address. They can return different results, rate-limit requests, or refuse traffic from a data-center address. Clicking a result also connects your browser to that website normally.
The access boundary here is SSH plus a loopback-only port. SearXNG does not ask visitors to log in. Anyone who can reach its web listener can use it, including other users on the same VPS. The public_instance setting is not a password or an access-control rule. Do not turn this configuration into a public service just by changing the port binding.
Install Docker and create the project
On a fresh VPS, install Ubuntu’s Docker and Compose packages:
$ sudo apt-get update
$ sudo apt-get install -y docker.io docker-compose-v2 curl
$ sudo systemctl enable --now docker
$ sudo docker version
$ sudo docker compose version
Both version commands should succeed. If Docker is already installed and working, keep that installation rather than mixing package sources. This guide uses sudo docker, so adding the SSH user to the Docker group is unnecessary.
Create a new project directory:
$ mkdir -p ~/searxng/config
$ cd ~/searxng
Create ~/searxng/compose.yaml with the following contents. Use a text editor; this is a new file, not an addition to another application’s Compose project.
services:
searxng:
image: docker.io/searxng/searxng:2026.9.8-3fdc6d753
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
SEARXNG_BASE_URL: http://localhost:18096/
SEARXNG_LIMITER: "false"
SEARXNG_PUBLIC_INSTANCE: "false"
volumes:
- ./config:/etc/searxng
- searxng-data:/var/cache/searxng
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
volumes:
searxng-data:
The image is pinned to a dated build. The host binding, 127.0.0.1:8080, keeps the web port off the VPS’s public interfaces. localhost in the base URL matches the address your browser will use through the tunnel.
The config directory stores settings, while the named volume stores cache data. The private configuration disables the request limiter, so it does not need a Valkey service. That choice is for this restricted deployment, not a recommendation for public instances.
Start SearXNG and check the response
From ~/searxng, validate and start the project:
$ sudo docker compose config --quiet
$ sudo docker compose pull
$ sudo docker compose up -d
$ sudo docker compose ps
A valid configuration produces no output from the first command. The final command should show the service running with 127.0.0.1:8080->8080/tcp, not 0.0.0.0:8080.
On the first start, this image creates config/settings.yml from its template and generates a random secret key. Keep that generated key private. Do not replace the file with an online example containing ultrasecretkey.
Check the local HTTP response:
$ curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/
The working instance should return:
200
HTTP 200 confirms a successful response. A connection error immediately after startup can mean the application is still loading. Check its logs and repeat the request:
$ sudo docker compose logs --tail=60 searxng
HTTP 200 proves the interface responds.
Open the private search page
On your computer, open a second terminal and run:
$ ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:18096:127.0.0.1:8080 YOUR_SSH_USER@YOUR_VPS_IP
Use the same key and SSH port options you normally need for this VPS. Verify a new host’s fingerprint through your provider before accepting it. Leave this terminal open; no prompt after a successful connection is normal because -N runs no remote command.
Open http://localhost:18096/ in your browser. The plain HTTP connection stays on loopback at each end, and SSH encrypts the traffic between your computer and the VPS. The tunnel does not expose a shareable public website.
If local port 18096 is occupied, stop the conflicting local application or choose another local port and update SEARXNG_BASE_URL to match it. Changing the base URL requires sudo docker compose up -d on the VPS. Do not change the VPS binding to a public address to fix a local port conflict.
Verify a search and choose engines
Enter a public, non-sensitive test query such as Ubuntu Linux. Confirm that the results have titles, destination links, and engine labels. Try a second distinct query so you are not only checking the home page or one lucky response.
In the test, Ubuntu Linux returned 35 result cards in 6.47 seconds, including results attributed to DuckDuckGo, Brave, and Google CSE. Docker Compose also returned 35 cards. These are observations from one run, not guaranteed counts or response times.
Open Preferences > Engines to review the available engines. Disable engines you do not want, save the preferences, and repeat a query. Preferences are associated with the browser, not a server login; another browser or cleared cookies can have different selections. A search page with partial results and an engine error is not the same as a failed Docker installation.
You can also test a specific engine using a search shortcut such as !ddg Ubuntu. This helps separate an engine-specific problem from a general failure. Compare the attribution with your chosen engine and check whether the result actually matches the query. Result counts and ordering will change over time.
For an instance-wide label, edit the generated ~/searxng/config/settings.yml on the VPS with elevated privileges. Preserve the existing use_default_settings and server sections, including the secret. Add this section, or merge the setting into general if that section already exists:
general:
instance_name: "Private Search"
Restart the application after saving:
$ sudo docker compose restart searxng
Reload the home page and confirm the new instance name. Browser engine preferences and server-wide settings are separate; do not assume changing one rewrites the other.
If the container cannot reach the internet
Skip this section when searches already work. If every engine times out, compare an outbound request on the VPS with one inside the container:
$ curl -I --max-time 10 https://www.ubuntu.com/
$ sudo docker compose exec -T searxng wget -q -t 1 -T 10 -O /dev/null https://www.ubuntu.com/
The second command is silent on success. Our test VM had private and public addresses on one interface: the host request worked, but container traffic needed an explicit public source address. This is a provider-network condition, not a SearXNG requirement. DNS, firewall, and routing failures can produce similar symptoms; do not apply this fix without confirming the source-address problem.
Inspect the internet route:
$ ip -4 route get 1.1.1.1
For that specific layout, confirm with your provider that the route’s src is the assigned address containers should use. Create ~/searxng/.env with the following line, replacing the example with that working address. If the file exists, merge only this setting:
EGRESS_IP=203.0.113.10
Append this top-level section to ~/searxng/compose.yaml, aligned with services: and volumes:. Merge it if networks already exists; retain the other sections:
networks:
default:
driver_opts:
com.docker.network.host_ipv4: "${EGRESS_IP}"
Recreate this project’s network and container. This briefly stops SearXNG but preserves its configuration and named volume:
$ sudo docker compose config --quiet
$ sudo docker compose down
$ sudo docker compose up -d
$ sudo docker compose exec -T searxng wget -q -t 1 -T 10 -O /dev/null https://www.ubuntu.com/
Continue only when the outbound check succeeds. If the assigned address later changes, update EGRESS_IP and recreate the network again. Do not open port 8080 or disable the firewall to solve an outbound source-address problem.
If something does not work
- The interface works but one engine fails. Check the reported engine error. Timeouts, HTTP 429, CAPTCHA challenges, and blocked VPS addresses can come from the upstream provider. Disable that engine or wait before trying again. Repeated automated retries will not repair a rate limit.
- No engine returns results. Check the container logs and the VPS’s outbound DNS/HTTPS connectivity. Confirm that the engines are enabled in Preferences. A successful home-page request only tests the local application.
- The browser cannot connect. Confirm the tunnel is still open and the local port is free. Run the HTTP check on the VPS to separate an application failure from a tunnel problem. Pressing Ctrl+C in the tunnel terminal closes browser access without stopping the container.
- A settings edit prevents startup. Check YAML indentation and the logs. Merge your change into the existing file rather than adding duplicate
serverorgeneralsections. Preserve the generated secret. The container may change ownership of its configuration directory; usesudowith your editor instead of making it world-writable. - An older guide has different services or flags. The former
searxng-dockerrepository is superseded. Use the current container documentation for this image generation, and do not mix its configuration with old uWSGI examples.
You now have a private SearXNG interface with a persistent configuration and a tested route to external search results; the example query returned 35 result cards. The tunnel controls who can reach it; your selected engines determine which searches it can answer.
Related guides
How to Self-Host DocuSeal for Document Signing
Self-host DocuSeal with private administrator setup, PostgreSQL and HTTPS. Test signing, understand email data flows and create consistent backups.
Self HostedHow to Self-Host Docmost with Docker on Ubuntu 24.04
Install Docmost with Docker, PostgreSQL and Redis. Set up a private workspace and verify editing, live updates and attachment retrieval.
Self HostedHow to Remove Image Backgrounds Locally with rembg
Remove image backgrounds locally with rembg. Preserve originals, check PNG transparency and inspect cutout quality on both light and dark backgrounds.
Self HostedHow to Translate Text Offline with Argos Translate
Translate plain text locally with Argos Translate. Install a language pair, prepare caches, verify offline operation and review translation quality.
