Every search query you send to a commercial engine feeds a profile you never agreed to. A self-hosted metasearch engine like SearXNG breaks that link—it proxies queries, strips tracking data before they leave your server, and gives you a private search endpoint with no telemetry, no ads, and no third-party cookies. The stack is minimal: a container that relays your terms to dozens of engines, and a reverse proxy to wrap it in TLS.
Why SearXNG Over a Direct Search API?
A direct search API—whether from Google’s Programmable Search Engine or Bing’s Web Search API—still sends queries from your network. The provider ties each request to an API key, an IP block, and a usage pattern. You’ve merely shifted the logging to a different entity and added an account that can be subpoenaed.
SearXNG acts as an agent. It forwards your terms to upstream engines (Google, Bing, DuckDuckGo, Wikipedia, torrent trackers, and dozens of others) but strips out referrer headers, cookies, and client-side fingerprinting vectors before the request leaves your server. The upstream engines see a request from a single IP with a generic user-agent—your identity never leaves the host. Running your own crawler is theoretically more private, but crawling the web at scale is impractical; SearXNG is a reasonable middle ground.
Configuration puts you in control. You can disable Google entirely and rely on Mojeek or marginalia-search, add a self-hosted Whoogle instance, or prioritize engines that match your threat model. The engine list and weighting live in a single YAML file. SearXNG also supports JSON output (format=json), so you can pipe results into dashboards, Alfred/Raycast workflows, or scripts without scraping HTML.
Prerequisites and Architecture Decisions
You’ll need a Docker host with Docker Compose (v2+) and, ideally, a domain name for a proper TLS certificate. LAN-only access with a self-signed certificate works too. The minimal stack consists of two containers: SearXNG itself and a Redis instance.
The project’s default config uses SQLite for rate-limiting and session data, but Redis offers better concurrency and avoids filesystem locking. The container already runs as a non-root user and Redis is internal, so this adds no meaningful attack surface. The stack expects about 200 MB of RAM and negligible CPU under typical household query loads. A bind-mounted directory stores settings.yml and any custom templates—that’s the only persistent state you need.
Security posture: all outbound traffic from SearXNG is proxied—your browser never talks directly to search engines. The container sits behind a reverse proxy (Caddy, Traefik, or Nginx) that terminates TLS. Without TLS, queries travel in cleartext past your router’s WAN interface, defeating the purpose.
Configuration File Deep-Dive
Everything worth changing lives inside settings.yml under /etc/searxng. Bind-mount a host directory to that path and populate it with your customized file.
Engine Selection and Tuning
The engines list controls which upstreams are queried. A minimal entry for Google looks like this:
engines:
- name: google
engine: google
use_mobile_ui: true
# Without this, Google frequently challenges with a CAPTCHA.
timeout: 5.0
weight: 1
Set disabled: true to disable an engine without removing its block—that keeps it out of the UI dropdown. Removing the block entirely is cleaner. To add a custom engine, define a name, point it at an endpoint that speaks the SearXNG JSON format, and set engine: json_engine:
- name: my-whoogle
engine: json_engine
search_url: https://whoogle.example.com/search?q={query}&format=json
shortcut: wgl
timeout: 10.0
Some engines require specific parameters to avoid blocking. Google’s use_mobile_ui: true sends queries as if from a mobile browser, reducing CAPTCHAs. Bing benefits from use_mobile_theme: true, and regional engines may need country or language settings. Consult the official SearXNG documentation for the current list of engine quirks.
Privacy and Branding Tweaks
Start with the server block. The secret_key is used for session signing and must be a long random string:
server:
secret_key: "openssl rand -base64 42" # actually paste the output
bind_address: "0.0.0.0"
port: 8080
Set instance_name and contact_url to brand the page and provide a contact point. These appear in the footer.
The search dict holds safe_search: 0 (off), giving you raw, unfiltered results. Leave it there—you want the pipe as uncensored as the upstream engines allow. In the ui section, default_theme: simple loads faster and avoids JavaScript bloat. The outgoing block lets you append a short suffix to SearXNG’s user-agent:
outgoing:
useragent_suffix: "my-instance/1.0"
That identifies your instance to upstream engines without leaking version details.
Docker Compose: The Production-Ready Stack
A well-annotated Compose file turns these pieces into a running service.
The Compose File, Annotated
version: "3.8"
services:
redis:
image: redis:7-alpine
restart: unless-stopped
networks:
- searxng-net
searxng:
image: searxng/searxng:latest
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
volumes:
- ./searxng-config:/etc/searxng:rw
environment:
- SEARXNG_REDIS_URL=redis://redis:6379/0
cap_drop:
- ALL
networks:
- searxng-net
networks:
searxng-net:
driver: bridge
SearXNG binds port 8080 only to localhost, keeping the container off the network until the reverse proxy connects. cap_drop: ALL strips every Linux capability, and the image already runs as a non-root user—a solid hardening baseline. Redis sits on an internal bridge network with no external ports; only SearXNG can reach it.
Place a settings.yml inside ./searxng-config before starting the stack. On first run, SearXNG writes a default uwsgi.ini and other runtime files, but your config takes precedence.
Network Placement and TLS
Expose SearXNG through a reverse proxy that handles TLS. Caddy is the simplest option:
search.example.com {
reverse_proxy localhost:8080
}
That’s the entire Caddyfile—it provisions a Let’s Encrypt certificate automatically and proxies traffic. If you prefer Nginx Proxy Manager, it handles the same job with a GUI; our guide on Nginx Proxy Manager: Free SSL for Self-Hosted Services walks through the setup. For Traefik, add labels that define a router and middleware; for plain Nginx, a proxy_pass block with appropriate headers.
Never expose SearXNG directly on a public port without TLS. Search queries in cleartext defeat the privacy objective, and some ISPs tamper with unencrypted HTTP. On a LAN without a domain, a self-signed certificate combined with DNS rebinding protection on your router is sufficient—your browser will warn about the cert, but encryption remains.
Hardening and Operational Notes
Enable rate limiting to prevent abuse if the instance is internet-facing:
limiter:
enabled: true
# Per-IP and per-token limits are configured here.
SearXNG’s bot_detection setting can block simple scrapers by checking user-agent strings and header consistency.
By default, SearXNG logs every query—search term, client IP, timestamp—to stdout. To disable query logging entirely, set search.query_in_title: false in settings.yml and add these lines to a custom uwsgi.ini bind-mounted alongside your config:
[uwsgi]
disable-logging = true
After that, only startup messages appear. The reverse proxy will still log access, so consider anonymizing or aggressively rotating those logs.
For external-facing instances, pair fail2ban with your reverse proxy to rate-limit requests to the /search endpoint—similar to SSH bruteforce protection. Watch for rapid strings of POST /search and apply temporary iptables bans.
Container updates: SearXNG releases bugfixes and engine adjustments regularly. A watchtower container can auto-update, but config drift is a risk—new versions occasionally change setting keys. Subscribe to the GitHub releases and test updates manually to avoid surprises.
Using Your Metasearch Engine Daily
Set SearXNG as the default search engine in every browser. Firefox detects OpenSearch endpoints automatically after you visit the instance’s home page; you’ll see an option to add it. Alternatively, manually add https://search.example.com/opensearch.xml.
For Chrome and Edge, create a custom search engine with the URL pattern https://search.example.com/search?q=%s and assign a keyword like sx. Typing sx <query> in the address bar sends the search. Safari on iOS supports the same pattern through Settings > Safari > Search Engine > Quick Website Search.
Alfred and Raycast can use a custom web search with the URL https://search.example.com/search?q={query}. Set a shortcut and launch queries from the keyboard without opening a browser.
Preferences—engines, theme, language—are stored in a cookie on your browser. No account, no login. They’re per-device and easy to clear, but you control exactly what the engine sees. The JSON API (?format=json) makes SearXNG scriptable: curl -s 'https://search.example.com/search?q=test&format=json' | jq '.results' drops top links into a terminal, ready for piping into a dashboard widget.
Conclusion
Self-hosting a metasearch engine is one of the highest-leverage privacy moves you can make in a homelab. It decouples your search behavior from your identity at the network layer—no amount of browser tweaks or private windows accomplishes the same thing. The stack is minimal: SearXNG, Redis, a reverse proxy, and a single config file you control. When an engine changes its CAPTCHA rules or returns junk, you adjust the YAML and restart. The official docs cover engine-specific quirks; the community-maintained list of public instances offers a safe way to test the service before you deploy your own. Then you get the quiet satisfaction of a search bar that answers only to you.