How to Self-Host a Private Search Engine with SearXNG (Google's Results, Nobody's Tracking)

Search from your own server: SearXNG asks Google, Bing and DuckDuckGo for you and shows the results with no ads, no profile, no tracking.

Every search you type builds a profile somewhere. SearXNG is a metasearch engine: a page on your server that forwards your query to Google, Bing, DuckDuckGo, Wikipedia, GitHub, Stack Overflow and seventy other sources, merges the results, and shows them without adverts, tracking cookies or a logged-in identity. The engines see a request from your server's IP with no history attached; you see clean results. Set it as the default search in every browser in the house.

Quick reference

TaskWhat
Folder~/docker/searxng/compose.yaml + settings.yml
Webhttp://server-ip:8083 (pick a free port)
Needsa secret_key in settings, Redis for rate limiting
EnginesPreferences → Engines or engines: in settings.yml
Browser defaulthttps://search.example.com/search?q=%s
Keep privateLAN/VPN only, or behind the proxy with the limiter on

1. The compose file

mkdir -p ~/docker/searxng/config && cd ~/docker/searxng
nano compose.yaml

services:
searxng:
image: searxng/searxng:latest
container_name: searxng
ports:
- "127.0.0.1:8083:8080"
environment:
SEARXNG_BASE_URL: https://search.example.com/
SEARXNG_SECRET: ${SEARXNG_SECRET}
volumes:
- ./config:/etc/searxng
depends_on: [redis]
cap_drop: [ALL]
cap_add: [CHOWN, SETGID, SETUID]
restart: unless-stopped

redis:
image: redis:alpine
container_name: searxng-redis
command: redis-server --save 30 1 --loglevel warning
cap_drop: [ALL]
cap_add: [SETGID, SETUID, DAC_OVERRIDE]
restart: unless-stopped

.env with SEARXNG_SECRET (openssl rand -hex 32). On first start it writes a default config/settings.yml.

docker compose up -d

2. settings.yml

nano config/settings.yml

The lines worth setting:

use_default_settings: true
server:
base_url: https://search.example.com/
limiter: true
image_proxy: true
redis:
url: redis://searxng-redis:6379/0
search:
safe_search: 0
autocomplete: "duckduckgo"
default_lang: "en-GB"
formats: [html, json]
ui:
default_theme: simple
infinite_scroll: true
engines:
- name: google
disabled: false
- name: bing
disabled: false
- name: duckduckgo
disabled: false
- name: brave
disabled: false

image_proxy fetches thumbnails through the server so the image hosts don't see you; formats: json lets Homepage (94) and scripts query it. Restart after editing.

3. Reverse proxy

search.example.com → 127.0.0.1:8083 (28) with HTTPS, and pass the real IP (X-Forwarded-For, X-Real-IP) so the limiter can tell devices apart. Behind the VPN is the private default; if you make it public, keep limiter: true on or bots will use your instance to scrape Google and get your IP blocked.

4. Make it the default search

! bangs work (!w linux searches Wikipedia, !gh GitHub, !yt YouTube — routed to Invidious if you set it, 181). Categories (images, videos, news, map, files, science) are tabs.

5. Tune it

Preferences (cog) per browser: engines on/off, results per page, dark theme, open in new tab, the language. Preferences are stored in a cookie or a URL you can bookmark — nothing on the server. Disable engines that keep failing (the stats page shows per-engine response times).

Why it's private — and what it isn't

Your searches leave the server with no cookie and no account, from one IP for the whole household, mixed together. Google can't profile you; it sees a server asking varied questions. It isn't anonymous — your server's IP is still the requester, and a public instance would make that IP look like a bot. For true anonymity you'd route SearXNG's outbound through Tor or a VPN container (136); for a household it's already a large step up.

Troubleshooting

"Too many requests" / engines failing. Google rate-limits the server's IP; disable Google for a while, or route via Gluetun (136). Keep limiter: true if the instance is public.

Images don't load. image_proxy needs outbound access; or disable it.

Results in the wrong language. default_lang and the browser's preference.

Secret key error on start. SEARXNG_SECRET missing; settings.yml's secret_key must not be the default.

Wrapping up

Two containers, a secret, a settings file with the engines you want, HTTPS, default search in every browser. The results you're used to, with the tracking removed at the source — and !w, !gh and the rest still at your fingertips.

Call us