Minimal dark themed search homepage with a search box on a laptop screen
Concept visualization: the SearXNG search interface on a laptop. Generated illustration, not a photo of the actual setup.
2024 Infrastructure Live Demo ↗

Overview

This project is a publicly accessible SearXNG instance running at search.tyfsadik.org. SearXNG is a privacy-respecting meta-search engine that aggregates results from Google, Bing, DuckDuckGo, Wikipedia, GitHub, and other backends in parallel, then deduplicates and ranks them, without exposing any user identity or search history to those engines. No account is required. Two honest limits apply: SearXNG may set preference cookies, and queries and client addresses can appear in application, container, or reverse-proxy logs unless logging is explicitly controlled. This instance is configured to minimize both.

The instance is configured with a custom dark theme and deployed behind Nginx with rate limiting to prevent abuse from automated scrapers or bots. Redis backs SearXNG's application-aware rate limiter and related runtime state. The deployment uses the official searxng-docker repository as a base, with custom settings.yml modifications to enable preferred engines and tune the result ranking.

Architecture

graph TD subgraph Users["User Layer"] BR["Browser Query"] end subgraph Proxy["Proxy Layer"] NG["Nginx\nrate limiting + SSL\n:443"] end subgraph SearXNG["SearXNG Container"] AGG["Result Aggregator"] RNK["Result Ranker\n+ Deduplicator"] end subgraph Cache["State Layer"] RD[("Redis\nlimiter state")] end subgraph Backends["Search Backends (parallel)"] G["Google"] B["Bing"] DDG["DuckDuckGo"] WK["Wikipedia"] GH["GitHub"] end BR -->|"HTTPS query"| NG NG -->|"limit_req_zone\nproxy_pass"| AGG AGG <-->|"limiter state"| RD AGG -->|"parallel fetch"| G AGG -->|"parallel fetch"| B AGG -->|"parallel fetch"| DDG AGG -->|"parallel fetch"| WK AGG -->|"parallel fetch"| GH G -->|"results"| RNK B -->|"results"| RNK DDG -->|"results"| RNK WK -->|"results"| RNK GH -->|"results"| RNK RNK -->|"merged results"| NG NG -->|"response"| BR style BR fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style NG fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style AGG fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style RNK fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style RD fill:#181818,stroke:#1e1e1e,color:#888 style G fill:#181818,stroke:#1e1e1e,color:#888 style B fill:#181818,stroke:#1e1e1e,color:#888 style DDG fill:#181818,stroke:#1e1e1e,color:#888 style WK fill:#181818,stroke:#1e1e1e,color:#888 style GH fill:#181818,stroke:#1e1e1e,color:#888

Tech Stack

  • SearXNG, open-source meta-search engine with pluggable backends
  • Docker & Docker Compose, containerized deployment via searxng-docker
  • Nginx, reverse proxy with limit_req_zone rate limiting and SSL
  • Redis, rate limiter state and runtime state for SearXNG
  • Let's Encrypt / Certbot, TLS certificate for search.tyfsadik.org
  • Linux (Debian), host operating system

Build Process

1

Clone searxng-docker and Configure settings.yml

The official searxng-docker repository provides a pre-built compose file with Caddy, SearXNG, and Redis (recent revisions run the cache service on the Valkey image). This deployment uses only the SearXNG and cache services from that file; the bundled Caddy reverse proxy is replaced by the host Nginx configured in Step 4. The settings.yml file is edited before first launch to set the instance name, generate a secret key, and configure engine preferences.

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

# Generate a unique secret key
SECRET_KEY=$(openssl rand -hex 32)

# Edit searxng/settings.yml
# Set: general.instance_name, server.secret_key
sed -i "s/ultrasecretkey/$SECRET_KEY/g" searxng/settings.yml

cat searxng/settings.yml | grep secret_key
2

Configure Engines and Limiter

The settings.yml file is edited to enable the preferred engine set, keep SearXNG's built-in application-aware limiter enabled as a second layer behind Nginx's rate limiting (a public instance should not rely on a single control), and turn off the image proxy (which adds latency). Engine timeouts are set conservatively to prevent slow backends from blocking the response.

# searxng/settings.yml key settings:
general:
  instance_name: "search.tyfsadik.org"
  debug: false

server:
  secret_key: "..."
  limiter: true           # application-aware limiter stays on; Nginx adds a second layer
  image_proxy: false

search:
  safe_search: 0
  autocomplete: "google"

# Enable specific engines (excerpt):
engines:
  - name: google
    engine: google
    timeout: 3.0
    weight: 2
  - name: bing
    engine: bing
    timeout: 3.0
  - name: duckduckgo
    engine: duckduckgo
    timeout: 3.0
  - name: wikipedia
    engine: wikipedia
    timeout: 2.0
  - name: github
    engine: github
    timeout: 3.0
3

Start the Stack

The SearXNG + cache services are started with Docker Compose. The pre-built docker-compose.yaml from the searxng-docker repo already includes correct inter-service networking for those services; Nginx runs on the host, not in Compose.

docker compose up -d

# Check all containers are up
docker compose ps

# Test SearXNG is responding locally
curl "http://localhost:8080/search?q=test&format=json" | jq '.results | length'

# Check Redis is connected
docker compose logs searxng | grep -i redis
4

Nginx Config with Rate Limiting

The Nginx configuration uses limit_req_zone to restrict each IP to 10 requests per minute for the search endpoint. A separate zone with a higher limit is applied to static assets. This prevents bot scraping while keeping the service usable for legitimate users.

# /etc/nginx/nginx.conf (http block)
limit_req_zone $binary_remote_addr zone=searxng_search:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=searxng_static:10m rate=60r/m;

# /etc/nginx/sites-available/search.tyfsadik.org
server {
    listen 443 ssl;
    server_name search.tyfsadik.org;

    ssl_certificate /etc/letsencrypt/live/search.tyfsadik.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/search.tyfsadik.org/privkey.pem;

    location /search {
        limit_req zone=searxng_search burst=5 nodelay;
        limit_req_status 429;
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location / {
        limit_req zone=searxng_static burst=20;
        proxy_pass http://localhost:8080;
    }
}
5

Apply Custom Dark Theme

SearXNG supports custom CSS via a theme file. A dark theme based on the existing simple theme is created by overriding CSS variables in a custom stylesheet file that is bind-mounted into the container's theme directory.

# searxng/static/themes/simple/css/searxng.min.css (custom overrides)
# Create custom.css and add to bind mount in compose file

# In searxng/settings.yml:
ui:
  default_theme: simple
  theme_args:
    simple_style: dark

# Or override via a custom CSS file:
# services.searxng.volumes in docker-compose.yaml:
# - ./searxng/custom.css:/usr/local/searxng/searx/static/themes/simple/css/custom.css

# Verify theme is applied
curl https://search.tyfsadik.org | grep "dark"
6

Provision SSL Certificate

Certbot with the Nginx plugin provisions the Let's Encrypt certificate and automatically modifies the Nginx server block to add SSL directives and the HTTP-to-HTTPS redirect.

apt install certbot python3-certbot-nginx -y

certbot --nginx -d search.tyfsadik.org \
  --agree-tos --email [email protected]

# Verify HTTPS is working
curl -I https://search.tyfsadik.org
# Expected: HTTP/2 200

# Check certificate expiry
echo | openssl s_client -connect search.tyfsadik.org:443 2>/dev/null \
  | openssl x509 -noout -dates

Request Flow

sequenceDiagram participant U as User Browser participant NG as Nginx participant SX as SearXNG participant RD as Redis Cache participant BE as Search Backends U-->>NG: GET /search?q=query HTTPS NG-->>NG: rate limit check (10r/m per IP) NG-->>SX: proxy_pass query SX-->>RD: application limiter check (Redis state) SX-->>BE: parallel requests to all enabled engines BE-->>SX: raw results per engine SX-->>SX: deduplicate + rank by score SX-->>NG: merged ranked results NG-->>U: response page

Challenges & Solutions

  • Some engines rate-limiting the SearXNG IP: Google and Bing occasionally returned CAPTCHA challenges or empty results after frequent requests from the same server IP. Resolved by setting conservative engine timeouts (3 seconds), marking engines as disabled when they return persistent errors, and relying on DuckDuckGo and Brave Search as fallback engines that are less aggressive with bot detection.
  • High resource usage from concurrent searches: Each search spawns multiple outbound HTTP requests in parallel, and with several users searching simultaneously the Python worker count became a bottleneck. Tuned by setting the UWSGI_WORKERS environment variable (the official image's documented knob for worker processes, default 4) to 2 on the constrained server, reducing memory usage while maintaining acceptable response times.
  • Bot traffic overwhelming the instance: Shortly after the instance was indexed by search engines, automated scrapers began sending hundreds of requests per minute. The Nginx limit_req_zone rate limiter reduced this to manageable levels, and returning HTTP 429 to violators causes well-behaved bots to back off.
  • Redis connection refused on first start: SearXNG started before Redis was ready, causing connection errors in the logs. Fixed by adding depends_on: redis with a condition: service_healthy health check in the compose file.

What I Learned

  • Nginx limit_req_zone rate limiting: zones, burst allowance, and nodelay semantics
  • SearXNG engine configuration: timeout tuning, weight scoring, and engine disabling
  • Redis-backed limiter state and its role in SearXNG
  • Meta-search architecture: parallel fan-out requests, result deduplication by URL hash
  • Bot mitigation strategies: rate limiting, HTTP 429 responses, and Nginx access logging analysis
  • Docker Compose service health checks and dependency ordering

How I built it

The goal was a search page I could set as my default without feeding a profile to anyone: no account, minimized cookies, minimized logging, with results aggregated from the engines I already trusted.

  1. Phase 1, deploy the upstream stack: Cloned the official searxng-docker repository, which bundles SearXNG, Nginx, and Redis in one compose file.
  2. Phase 2, configure identity and engines: Edited settings.yml to set the instance name, generated a unique secret key, enabled the preferred engine set, kept the built-in limiter enabled, and turned off the image proxy.
  3. Phase 3, harden the front door: Configured Nginx rate limiting to blunt scrapers and bots, with Redis backing SearXNG's limiter state.
  4. Phase 4, publish with TLS: Issued a Let's Encrypt certificate and published the instance at search.tyfsadik.org.
  5. Phase 5, theme and tune: Applied the custom dark theme and tuned engine timeouts for a balance of completeness and speed.

What you need

CategoryItemNotes
HardwareAny small VPS or homelab VMModest CPU and RAM; caching keeps it light
SoftwareDocker and Docker ComposeRuns the searxng-docker stack
SoftwareSearXNG, Nginx, RedisEngine, rate-limited front door, and cache
AccountsA domain with DNS controlFor the TLS certificate
SkillsYAML config editing and basic Nginxsettings.yml and limit_req_zone tuning

Challenges and lessons learned

  • Upstream engine pushback: Google and Bing rate-limit or block datacenter IPs hitting them at volume. Lesson: diversify engines and tune timeouts and weights so a single blocked backend never empties the results.
  • Abuse from bots: a public instance attracts scrapers fast. Lesson: Nginx rate limiting plus Redis-backed throttling keeps the instance usable without accounts.
  • Result quality tuning: defaults favor breadth over relevance. Lesson: curate the engine list per category instead of enabling everything.

What is next

  • Curate per-category engine sets tuned for code, academic, and news queries.
  • Add uptime and result-quality monitoring with alerts.
  • Enable bang-style shortcuts for power users.
  • Configure fallback engines that activate when a primary is blocked.
SearXNG Docker Nginx Privacy Search Open Source Self-Hosted