Setting up a personal SearXNG instance on a small VPS: privacy-respecting metasearch without the cloud lock-in

Google still has the best results for some queries. DuckDuckGo is fine for most of them. Kagi is excellent if you want to pay $10/month. But there’s a fourth option that gets oddly little airtime: SearXNG — a self-hosted metasearch engine that aggregates results from Google, Bing, DuckDuckGo, Brave, Wikipedia, GitHub, and ~70 other sources, anonymizes the requests so none of them see who you are, and returns merged results in one page.

It’s not magic. It’s slower than any single source. The result quality depends entirely on which engines you enable. But it solves the “I want clean search results that aren’t tied to my Google account, with no SaaS subscription” case nicely. Here’s the install I run, the configuration that gets results that don’t suck, and the gotchas I hit.

What SearXNG actually does

You type a query. SearXNG fans the query out to ~10 backend search engines in parallel (configurable per query), strips out any tracking the engines tried to attach, scores and merges the results, then renders a single results page. From the engines’ perspective, the search came from your VPS — not from your home IP, not your browser, not tied to your account.

  • No data persistence. SearXNG doesn’t log queries by default.
  • Bangs. Type !yt cats and SearXNG redirects to YouTube’s cats search. !gh react goes to GitHub. ~200 of these built in.
  • Categorized results. General, Images, Videos, News, Music, Files, IT, Maps, Science. Each tab uses different upstream engines.
  • API. JSON endpoint at /search?q=&format=json — useful for scripting.

Install on a $5 VPS

Docker is by far the easiest. The official searxng/searxng-docker repo ships docker-compose, configs, and Caddy out of the box.

cd /opt
git clone https://github.com/searxng/searxng-docker.git
cd searxng-docker

# Edit Caddyfile to use your real hostname:
sed -i 's|search.example.com|search.yourdomain.com|g' Caddyfile

# Generate a strong secret_key (used to sign URL params)
sed -i "s|ultrasecretkey|$(openssl rand -hex 32)|g" searxng/settings.yml

docker compose up -d

Caddy will obtain a Let’s Encrypt cert automatically. Visit https://search.yourdomain.com/ and the search box is up. Total setup: ~5 minutes.

The settings.yml that produces good results

The default settings.yml works but is overly conservative — many high-quality engines are disabled because they require API keys, or are rate-limited, or fail too often. After a few weeks of tweaking, the configuration that gives me Google-quality results most of the time:

# searxng/settings.yml — engines section, key parts
engines:
  - name: google
    disabled: false           # enabled — best general results
    timeout: 4.0
  - name: brave
    disabled: false           # enabled — surprisingly good in 2026
  - name: duckduckgo
    disabled: false
  - name: bing
    disabled: false
  - name: qwant
    disabled: true            # disable — high failure rate
  - name: startpage
    disabled: false
  - name: wikipedia
    disabled: false
  - name: github
    disabled: false
  - name: stackoverflow
    disabled: false
  - name: arxiv
    disabled: false           # research papers — niche but high-quality
  - name: hacker news
    disabled: false           # for tech queries

# global settings
server:
  bind_address: "0.0.0.0"
  secret_key: "..."           # already set above
  limiter: true               # rate-limit per IP

search:
  safe_search: 0              # off — set to 1 if shared with kids
  default_lang: "en"
  formats:
    - html
    - json

Restart with docker compose restart. From the SearXNG preferences page (gear icon) you can also enable/disable engines per-user without editing the YAML.

Make it your browser’s default

SearXNG exposes an OpenSearch descriptor at /opensearch.xml, which most browsers auto-discover. In Firefox, visit your SearXNG URL once and right-click the address bar → Add. In Chrome, Settings → Search engine → Manage search engines → add https://search.yourdomain.com/?q=%s, set as default.

Now every URL bar query goes through your SearXNG. From the public web’s perspective, your browsing pattern is invisible — the queries originate from your VPS, with no cookies, with no device fingerprint.

The gotchas

  • Some engines block VPS IP ranges. Cloud-provider IP ranges are well-known to Google / Bing / Brave, and they sometimes serve CAPTCHA pages or block the request. SearXNG handles this by rotating which engines it queries and falling back. If you see “no results,” that’s why; try again, or move SearXNG to a residential-IP server (a home Pi behind Tailscale).
  • It’s slower than direct search. A SearXNG query takes 1.5–3 seconds for the slowest enabled engine to respond. Compare to ~300ms for direct Google. The trade is privacy and aggregation, not speed.
  • Image search quality is uneven. Image-search engines vary a lot in what they return. SearXNG’s image tab is OK but Google Images directly is still better. !gi cats bang takes you straight there.
  • The rate limiter kicks in if you bulk-query via the JSON API. Set limiter: false in settings.yml if you’re the only user, or whitelist your IP under the limiter config.
  • Don’t run a public SearXNG. If you put one up at a public URL with no auth, scrapers will find it and drain your VPS quota. Either keep it private (Tailscale-only) or put it behind HTTP basic-auth via Caddy.

Putting it behind Tailscale (the version I actually run)

Best privacy posture: don’t expose SearXNG to the internet at all. Run it on a tiny Linux box at home (Pi 5, mini-PC, NUC) reachable only over Tailscale. The 100.x Tailscale IP is your access path. Now:

  • Search queries leave your home network on a residential IP — engines treat them like normal browser traffic, no CAPTCHAs.
  • The SearXNG instance is reachable from any of your devices on the tailnet, but invisible to the public internet.
  • No public TLS cert needed (Tailscale handles encryption end-to-end).
  • Setup is the same as above, except instead of Caddy with a public hostname, you bind Caddy to 100.x.y.z:443 and use tls internal.

End-state: a small box at home that takes your search queries, fans them out to 8–10 engines under a residential IP, returns merged results, and never tells anyone what you searched for. No subscription, no account, no logs. After a year I haven’t gone back to typing into Google’s search bar directly — the merged-results experience has its own appeal independent of the privacy story.

Photo: Laptop displaying a search results page by cottonbro on Pexels.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.