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, no cookies are set, and no queries are logged.

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 provides result caching to reduce redundant outbound queries and improve response times for popular searches. The deployment uses the official searxng-docker repository as a base, with custom settings.yml modifications to enable preferred engines, disable tracking, 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["Cache Layer"] RD[("Redis\nresult cache")] 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 <-->|"cache check"| 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 — result caching and rate limit 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 Nginx, SearXNG, and Redis. 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 Disable Tracking

The settings.yml file is edited to enable the preferred engine set, disable SearXNG's built-in rate limiter (since Nginx handles it), disable usage statistics collection, 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: false          # Nginx handles rate limiting
  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 full stack (SearXNG + Redis + Nginx) is started with Docker Compose. The pre-built docker-compose.yaml from the searxng-docker repo already includes all three services with correct inter-service networking.

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: check cache for query hash alt Cache hit RD-->>SX: cached results SX-->>NG: results (fast) NG-->>U: merged HTML/JSON else Cache miss SX->>BE: parallel requests to all enabled engines BE-->>SX: raw results per engine SX->>SX: deduplicate + rank by score SX->>RD: cache results (TTL 10 min) SX-->>NG: merged ranked results NG-->>U: response page end

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 adjusting the server.workers setting in settings.yml from the default of 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 result caching strategies and TTL selection for search result freshness
  • 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
SearXNG Docker Nginx Privacy Search Open Source Self-Hosted