SearXNG – Your Very Own Search Engine
Google Knows What You Searched Last Summer: How to Self-Host Your Way Out
Go to myactivity.google.com. Right now. I’ll wait.
Scroll through that list. Every search you’ve made. Every question you were too embarrassed to ask a friend. Every 2 AM medical symptom panic. Every job listing you clicked while still employed. Every weird hobby you explored for a week and abandoned.
That’s not a search history. That’s a psychological profile. And Google has been building it since the day you created your account.
The uncomfortable truth: Your search history reveals more about you than your medical records, your browser history, or your text messages. It’s the unfiltered stream of what you want to know — and you hand it over dozens of times a day.
If you’ve been following along with this site, you already know I take a layered approach to privacy. AdGuard Home handles DNS-level ad and tracker blocking — that was Layer 1. But DNS filtering can’t help you when you voluntarily type your thoughts into a search box owned by an advertising company. That’s Layer 2. And that’s what we’re fixing today.
Already running AdGuard or Pi-hole? Good — DNS filtering is half the battle. This article is the other half. If you haven’t set up DNS-level blocking yet, start with my AdGuard article first.
jdelay@thedelay:~/privacy$ ./deploy_searxng.sh
Quick Deploy: 6 Steps, 15 Minutes
Prerequisites: A Linux box with Docker and Docker Compose installed. That’s it.
Step 1: Create the directory structure
sudo mkdir -p /opt/searxng
cd /opt/searxng
Step 2: Generate a secret key
openssl rand -hex 32
# Save this output -- you'll need it in Step 3
Step 3: Create settings.yml
Paste the production-ready config from the Settings section below. Replace the secret_key with your value from Step 2.
Step 4: Create docker-compose.yml
Paste the compose file from the Stack section below.
Step 5: Launch
docker compose up -d
Step 6: Verify
Open http://your-server:8080 (or your Caddy domain). Search for something. Check that results come back from multiple engines.
That’s it. Your searches are now yours again. Read the rest for the hardening, the daily-driver tips, and the war stories.
$ cat /var/log/google/your_entire_life.log
The Problem: What Google Actually Knows About You
Let’s be specific about what we’re dealing with. When you type a query into Google, here’s what gets logged:
What a Search Engine Knows About You
- Your IP address (and therefore your approximate location)
- The exact search query
- Which results you clicked (and how long you stayed)
- Your device fingerprint (browser, OS, screen resolution, installed fonts)
- Your Google account identity (if signed in — and you probably are)
- The search you made before this one, and the one before that
- The time of day, day of week, and frequency patterns
Google doesn’t just know what you searched. They know when you searched, where you searched from, what you clicked, and how your search patterns change over time. That last one is the kicker — your search history is a timeline of your evolving interests, concerns, and life events.
Here’s how the major search options compare:
| Feature | Bing | DuckDuckGo | SearXNG | |
|---|---|---|---|---|
| Logs your IP | Yes | Yes | No (claimed) | No (self-hosted) |
| Tracks search history | Yes | Yes | No (claimed) | No |
| Personalizes results | Yes | Yes | No | No |
| Sells data to advertisers | Yes | Yes | No | No |
| You control the server | No | No | No | Yes |
| Open source | No | No | No | Yes |
| Aggregates multiple engines | No | No | No | Yes |
Notice the pattern? Even DuckDuckGo — the “privacy” search engine — is a company running servers you don’t control. They say they don’t log. You trust that they don’t. But you can’t verify it.
$ man searxng
What SearXNG Actually Does (And What It Doesn’t)
SearXNG is a free, open-source metasearch engine. It doesn’t have its own index — it queries other search engines on your behalf and aggregates the results. The critical difference: the upstream engines see the SearXNG server’s IP, not yours. Your identity stays behind your own infrastructure.
Here’s the request flow:
You (browser)
|
| [search query over HTTPS]
|
v
Your SearXNG Instance (your server, your network)
|
| [same query, but from SearXNG's IP]
|
+-----> Google ------+
+-----> Bing --------+----> Results aggregated,
+-----> Brave -------+ deduplicated, and
+-----> Wikipedia ---+ returned to you
+-----> DuckDuckGo --+
|
v
You (clean results, no tracking)
Google sees a query from your server’s IP. It doesn’t see your browser fingerprint, your cookies, your account, or your click behavior. As far as Google is concerned, some random server asked a question. It doesn’t know (or care) who’s behind it.
Engine categories — what SearXNG can search
SearXNG organizes its 70+ supported engines into categories:
| Category | Example Engines | What It Searches |
|---|---|---|
| General | Google, Bing, DuckDuckGo, Brave | Web pages |
| Images | Google Images, Bing Images, Flickr | Image search |
| Videos | YouTube, PeerTube, Dailymotion | Video search |
| News | Google News, Bing News, Yahoo News | News articles |
| Maps | OpenStreetMap, Photon | Location/maps |
| Music | Bandcamp, SoundCloud | Music search |
| IT | Stack Overflow, GitHub, MDN | Developer resources |
| Science | Google Scholar, Semantic Scholar, PubMed | Academic papers |
| Files | 1337x, piratebay, nyaa | Torrents/files |
| Social Media | Reddit, Mastodon | Social content |
You enable and disable engines individually. Don’t use YouTube? Turn it off. Want only privacy-respecting engines? Disable Google entirely and rely on Brave + DuckDuckGo + Mojeek. Your instance, your rules.
What SearXNG Is NOT
Let me save you some disappointment:
- It’s not a VPN. It doesn’t encrypt your traffic to the SearXNG server. Use HTTPS (Caddy handles this) or access it over your LAN/VPN.
- It’s not Tor. It doesn’t provide network-level anonymity. Your ISP can see you connecting to your SearXNG instance (which is fine — it’s your server).
- It’s not magic. If you sign into Google and search from there, SearXNG can’t help you. It only protects searches routed through it.
- It doesn’t have its own index. If every upstream engine blocks you, you get no results. (More on that in Lessons Learned.)
$ docker compose config –services
The Stack: Three Services, One Privacy Layer
The deployment is three containers:
| Service | Purpose | Port | External Exposure |
|---|---|---|---|
| SearXNG | The search engine | 8080 | Yes (behind reverse proxy) |
| Caddy | Reverse proxy + auto TLS | 443 | Yes (HTTPS frontend) |
| Valkey | Result caching (Redis fork) | 6379 | No (internal only) |
Here’s how they fit together:
Internet / LAN
|
v
[Caddy :443] <-- HTTPS termination, auto TLS
|
v
[SearXNG :8080] <-- Search engine, queries upstream
|
v
[Valkey :6379] <-- Caches results, reduces upstream requests
$ docker compose up -d
Deployment: The Full Walkthrough
Here’s the full deployment, step by step. I’m assuming a Linux server with Docker and Docker Compose already installed.
sudo mkdir -p /opt/searxng
cd /opt/searxng
// All config files, compose file, and volumes in one place
openssl rand -hex 32
Save this output. You’ll paste it into settings.yml in the next step. This key is used by SearXNG internally for session management and CSRF protection.
// Don’t skip this. Running without a secret key means default values, which means predictable tokens.
This is the heart of SearXNG. Create /opt/searxng/settings.yml:
Full annotated settings.yml (production-ready)
# SearXNG Settings - Production Configuration
# Docs: https://docs.searxng.org/admin/settings/
use_default_settings: true
general:
instance_name: "Search" # Shows in browser tab
privacypolicy_url: false # No external privacy policy link
donation_url: false # No donation nag
enable_metrics: false # Don't track internal usage stats
server:
secret_key: "YOUR_SECRET_KEY_FROM_STEP_2"
bind_address: "0.0.0.0"
port: 8080
limiter: true # Rate limiting (requires Valkey)
public_instance: false # Not a public instance
image_proxy: true # Proxy images through SearXNG
ui:
static_use_hash: true # Cache-busting for static files
default_theme: simple # Clean, fast theme
default_locale: en # Interface language
query_in_title: false # Don't put search queries in browser tab title
infinite_scroll: true # Load more results on scroll
center_alignment: true # Center the search bar
search:
safe_search: 0 # 0=off, 1=moderate, 2=strict
autocomplete: "duckduckgo" # Autocomplete suggestions source
default_lang: "auto" # Auto-detect language
ban_time_on_fail: 5 # Ban failing engines for 5 seconds
max_ban_time_on_fail: 120 # Max ban time: 2 minutes
engines:
# Disable engines that are redundant or unreliable
- name: bing
disabled: false
- name: brave
disabled: false
- name: duckduckgo
disabled: false
- name: google
disabled: false
- name: mojeek
disabled: false
- name: startpage
disabled: false
- name: qwant
disabled: false
- name: wikipedia
disabled: false
- name: wikidata
disabled: true # Rarely useful for general search
- name: currency
disabled: false # Handy for quick conversions
outgoing:
request_timeout: 3.0 # Don't wait forever for slow engines
max_request_timeout: 10.0 # Absolute max
useragent_suffix: "" # Don't advertise SearXNG in user-agent
pool_connections: 100
pool_maxsize: 20
The important bits: limiter: true enables rate limiting (requires Valkey), public_instance: false tells SearXNG this isn’t public-facing, and image_proxy: true routes images through your server so upstream engines can’t track your browser via image loads.
// If you only change one thing from the defaults, make it the secret_key
Full annotated docker-compose.yml
version: "3.9"
services:
searxng:
image: searxng/searxng:latest
container_name: searxng
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./settings.yml:/etc/searxng/settings.yml:ro
environment:
- SEARXNG_BASE_URL=https://search.yourdomain.com/
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
networks:
- searxng
valkey:
image: valkey/valkey:8-alpine
container_name: valkey
restart: unless-stopped
command: valkey-server --save 30 1 --loglevel warning
volumes:
- valkey-data:/data
cap_drop:
- ALL
cap_add:
- SETGID
- SETUID
- DAC_OVERRIDE
networks:
- searxng
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- "443:443"
- "80:80"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
networks:
- searxng
networks:
searxng:
driver: bridge
volumes:
valkey-data:
caddy-data:
caddy-config:
The cap_drop: ALL Gotcha
cap_drop: ALLstrips every Linux capability from the container. This is a security best practice — containers shouldn’t have privileges they don’t need.- But SearXNG needs CHOWN, SETGID, and SETUID to start properly (it changes its internal user on boot).
- If you see
Permission deniedor the container exits immediately, check thatcap_addincludes these three capabilities. - Valkey needs SETGID, SETUID, and DAC_OVERRIDE for its data directory.
If you’re using Caddy as the reverse proxy, create /opt/searxng/Caddyfile:
search.yourdomain.com {
reverse_proxy searxng:8080
}
That’s the entire Caddyfile. Caddy handles HTTPS certificates automatically.
If you already have Nginx, Traefik, or Cloudflare Tunnel as your reverse proxy, skip Caddy entirely. Just point your existing proxy at http://your-server:8080. Remove the Caddy service from docker-compose.yml.
// If you’re running this behind Cloudflare Tunnel like I do for other services, you don’t need Caddy at all
cd /opt/searxng
docker compose up -d
Check that all containers are healthy:
docker compose ps
You should see three services running (or two, if you skipped Caddy). Open your browser, navigate to your SearXNG URL, and search for something. Results should come back from multiple engines — you’ll see small engine icons next to each result indicating the source.
If you get no results, check the SearXNG logs:
docker compose logs searxng
// Common first-run issues: incorrect YAML syntax (whitespace-sensitive), missing secret key, or Valkey not ready yet (give it 10 seconds and retry)
$ chmod 600 /etc/searxng/settings.yml
Hardening: Because Running It Isn’t Enough
A default SearXNG installation works fine for personal use on a LAN. But if it’s exposed to the internet — even behind authentication — you should lock it down. An unsecured SearXNG instance is an open proxy that anyone can use to search the web through your server.
The rate limiter prevents abuse (and protects you from accidentally DDoS-ing upstream engines). It requires Valkey.
In settings.yml, make sure these are set:
server:
limiter: true
redis:
url: "redis://valkey:6379/0"
// If limiter: true and Valkey isn’t running, SearXNG will crash on start. The error message isn’t great.
If this is a personal instance, lock it to your LAN:
# UFW example: allow SearXNG only from local network
sudo ufw allow from 192.168.0.0/16 to any port 8080 proto tcp
sudo ufw deny 8080
Or better: don’t expose port 8080 at all. Access SearXNG through your VPN (WireGuard, Tailscale) or Cloudflare Tunnel with access controls.
Your settings.yml contains the secret key. Lock it down:
sudo chown root:root /opt/searxng/settings.yml
sudo chmod 600 /opt/searxng/settings.yml
// Docker reads the file as root, so root ownership is fine
SearXNG releases frequently. The Docker image tagged latest tracks stable releases.
cd /opt/searxng
docker compose pull
docker compose up -d
That’s it. Valkey data persists in a Docker volume, and your settings.yml is mounted from the host. Updates are non-destructive. Set a reminder to update monthly, or set up Watchtower for automatic updates.
Hardening Checklist
- Rate limiter enabled (requires Valkey)
- Network access restricted (UFW, VPN, or tunnel)
- settings.yml permissions: 600
- cap_drop: ALL in docker-compose.yml
- Not running as root inside the container
- Regular updates (monthly minimum)
- Logs rotated (Docker handles this by default with json-file driver)
$ alias s=’searxng-search’
Living With SearXNG: Daily Driver Tips
SearXNG is only useful if you actually use it. Here’s how to make it your daily driver without feeling like you downgraded.
Set It As Your Default Search Engine
Browser configuration for default search
Firefox:
- Navigate to your SearXNG instance
- Right-click the URL bar → “Add Search Engine”
- Go to Settings → Search → Default Search Engine → select SearXNG
Chrome/Chromium:
- Go to Settings → Search Engine → Manage Search Engines
- Add new: Name = SearXNG, Keyword = s, URL =
https://your-searxng/search?q=%s - Set as default
Brave:
Same as Chrome — Settings → Search Engine.
Safari:
Safari doesn’t support custom default search engines natively. Use a browser extension like xSearch or just bookmark your SearXNG instance.
Mobile (Android/iOS):
Firefox for Android supports custom search engines (same process as desktop). On iOS, use Firefox or Brave — Safari on iOS does not support custom search engines.
Bang Commands — The Power User’s Shortcut
SearXNG supports bang commands, just like DuckDuckGo. Type a prefix to search a specific engine directly:
| Bang | Engine | Privacy |
|---|---|---|
!g | Protected (routed through SearXNG) | |
!b | Bing | Protected |
!ddg | DuckDuckGo | Protected |
!w | Wikipedia | Protected |
!gh | GitHub | Protected |
!so | Stack Overflow | Protected |
!!g | Google (direct) | LOST (redirects your browser directly) |
!!b | Bing (direct) | LOST |
The Double-Bang Trap
- Single bang (
!g) routes through SearXNG. Your privacy is preserved. The upstream engine sees SearXNG’s IP. - Double bang (
!!g) redirects your browser directly to the engine. Your privacy is gone. Google sees your real IP, your browser fingerprint, everything. - Double bang exists for cases where you need engine-specific features (Google Maps integration, YouTube login, etc.). Use it consciously, not accidentally.
- If you’re not sure, always use single bang.
The JSON API
SearXNG has a JSON API that returns search results as structured data:
curl "https://your-searxng/search?q=homelab+backup&format=json" | jq '.results[:3]'
This is gold for automation. You could build an n8n workflow that searches for a topic daily and sends you a digest. Or a script that checks if your site appears in results. Or a monitoring tool that alerts you when a specific term trends. The API turns search into a building block.
$ diff –color google.conf searxng.conf
SearXNG vs. The Field
Every privacy-focused search option makes trade-offs. Here’s how they stack up:
| Feature | SearXNG | DuckDuckGo | Startpage | Brave Search | Kagi |
|---|---|---|---|---|---|
| Self-hosted | Yes | No | No | No | No |
| Open source | Yes | Partially | No | No | No |
| Own index | No | Hybrid | No (Google proxy) | Yes | Yes |
| Multi-engine | Yes (70+) | No | No | No | No |
| Cost | Free | Free | Free | Free | $5-10/mo |
| Privacy model | You verify | Trust them | Trust them | Trust them | Trust them |
| Result quality | Good (depends on engines) | Good | Good (Google-based) | Good | Excellent |
| Maintenance | You maintain | None | None | None | None |
$ cat /var/log/mistakes.log
Lessons Learned
Every deployment teaches you something. Here’s what this one taught me.
Stripping Capabilities Means Stripping Convenience
cap_drop: ALL in docker-compose.yml is the right security move. But when I first launched without the corresponding cap_add entries, SearXNG silently failed to start. No helpful error message — just an exit code and a vague permission error in the logs.
The fix: add back the minimum capabilities the container actually needs (CHOWN, SETGID, SETUID for SearXNG; add DAC_OVERRIDE for Valkey). Test after every capability change.
The lesson: Security hardening that breaks functionality isn’t hardening — it’s sabotage. Strip capabilities, but test the result.
Engines Will Fight Back
Google doesn’t love being queried by automated tools. After a few hundred queries, you’ll start seeing CAPTCHAs or empty results from Google. Bing is more tolerant. Brave barely notices.
The fix: diversify your engines. Don’t rely on any single source. I run 8-10 engines simultaneously. If Google blocks me for a few hours, I barely notice because Brave, DuckDuckGo, and Mojeek pick up the slack.
The lesson: A metasearch engine is only as reliable as its weakest dependency. Spread the load.
Anonymity Has Layers
SearXNG is one layer. It anonymizes who is searching. But it doesn’t encrypt your connection to SearXNG itself (use HTTPS), it doesn’t hide that you’re running a search service (your ISP can see traffic to SearXNG’s upstream engines), and it doesn’t protect you if you click a result and browse without protection.
The real privacy stack is defense in depth: AdGuard Home for DNS filtering, SearXNG for search anonymity, a privacy-focused browser (Firefox + uBlock Origin), and a VPN for network-level protection.
The lesson: No single tool is a silver bullet. Layer your defenses.
Results Vary By Engine Mix
With 2-3 engines enabled, results are sparse and biased toward whichever engine responds first. With 20+ engines enabled, results are slow and full of duplicates. The sweet spot I’ve landed on is 8-12 general engines, plus category-specific engines for images, news, and IT.
The lesson: More engines isn’t always better. Tune your mix based on what you actually search for.
CAPTCHAs Are a Feature, Not a Bug
When Google starts throwing CAPTCHAs at your SearXNG instance, that’s not a problem — that’s validation. It means Google can’t distinguish your server from a human browsing the web. The anonymization is working. SearXNG automatically marks engines that return CAPTCHAs as temporarily unavailable and falls back to others.
The lesson: Don’t panic when you see CAPTCHA warnings in the SearXNG stats page. It means the system is working as designed.
Hosting Means Responsibility
If your instance is publicly accessible (no authentication, no VPN), bots will find it within days. They’ll use it as an open proxy, burning through your bandwidth and getting your IP flagged by upstream engines. I’ve seen reports of public instances getting hundreds of thousands of queries per day from automated abuse.
The lesson: If it’s personal, lock it down. If it’s public, be prepared to manage it like a service. There’s no middle ground.
$ tree ~/privacy-stack/
The Privacy Stack: Where SearXNG Fits
Privacy isn’t a product. It’s a stack. Each layer protects against a different threat, and no single layer is sufficient on its own.
Layer 4: Network [VPN / WireGuard]
| Encrypts all traffic, hides destination
v
Layer 3: Browser [Firefox + uBlock Origin]
| Blocks trackers, fingerprinting, scripts
v
Layer 2: Search [SearXNG] <-- You are here
| Anonymizes search queries from engines
v
Layer 1: DNS [AdGuard Home]
| Blocks ad/tracker domains at network level
v
Layer 0: Infrastructure [HA DNS]
Reliable DNS resolution
Each layer is independent. You can run any combination. But the more layers you stack, the smaller your attack surface becomes.
| Layer | Tool | What It Protects | TheDeLay.com Article |
|---|---|---|---|
| DNS (Layer 1) | AdGuard Home | Blocks tracker/ad domains network-wide | Step Aside, Pi-Hole |
| Search (Layer 2) | SearXNG | Anonymizes search queries | This article |
| Infrastructure (Layer 0) | HA DNS | Ensures DNS never goes down | The #1 DNS Mistake |
| Backups | Duplicati / n8n | Protects your configs (including SearXNG) | Backups Let You Sleep |
$ exit
Own Your Search
Go back to myactivity.google.com. Look at it one more time.
Now imagine that list is empty. Not because you deleted it — because it was never created. Because your searches never touched Google’s servers with your identity attached. Because the server that processes your queries is sitting in your closet, under your control, logging nothing.
Your search history is a map of your mind. Every question you ask reveals what you’re thinking, what you’re worried about, what you’re curious about. That map has been in Google’s hands for years.
Take it back.
SearXNG won’t make you invisible. It won’t protect you from every threat. But it removes one of the largest, most intimate data collection pipelines from your life. And it does it for free, with open source software, on hardware you already own.
Fifteen minutes of setup. A lifetime of searches that belong to you. Now go deploy it. Then go search for something you’d never Google.

2 Comments