Skip to Content

Production Guide: Deploy SearXNG with Docker Compose + Caddy + Redis on Ubuntu

Private metasearch on Ubuntu with Docker Compose, Caddy TLS, Redis limiter state, safe engine settings, backups, and practical troubleshooting.

A private metasearch service is useful when your team wants fast web discovery without routing every query through one commercial search account, leaking internal research patterns, or maintaining browser plugins on every workstation. SearXNG is a lightweight open-source metasearch engine that aggregates results from multiple providers, strips tracking parameters, and can be tuned for internal use. In this guide we will deploy SearXNG on Ubuntu with Docker Compose, Caddy for automatic HTTPS, and Redis for rate limiting and runtime state.

This is a production-oriented baseline for a small company, research group, or engineering team. It is not meant to create a public anonymous search engine for the entire internet. The safer operating model is a private instance behind a domain you control, with conservative engines, monitored logs, regular backups, and a clear policy for who can access it.

Architecture and flow overview

The request path is intentionally simple. Users browse to the HTTPS hostname. Caddy terminates TLS, adds security headers, and forwards the request to the SearXNG container on the private Docker network. SearXNG reads its configuration from a mounted settings file, queries enabled upstream engines, normalizes the result page, and stores limiter state in Redis. Redis is not exposed publicly. The only internet-facing container is Caddy on ports 80 and 443.

  • Caddy handles certificates, HTTP to HTTPS redirects, compression, and reverse proxying.
  • SearXNG provides the web UI, JSON endpoint, engine configuration, image proxy, and privacy controls.
  • Redis supports limiter state and improves resilience during bursts of searches.
  • Backups cover the settings directory, Caddyfile, and environment file. Search history is not intentionally persisted.

Prerequisites

  • An Ubuntu 22.04 or 24.04 server with at least 1 vCPU and 1 GB RAM.
  • A DNS record such as search.example.com pointing to the server.
  • Docker Engine and Docker Compose v2 installed.
  • Ports 80 and 443 open to the internet for Caddy certificate issuance.
  • A decision about access: private VPN, firewall allow list, SSO proxy, or a low-profile internal hostname.
docker --version
docker compose version
getent hosts search.example.com
sudo ss -tlnp | grep -E ':80|:443' || true

Manual copy fallback: select the command text in the block above if the copy button is unavailable.

Step-by-step deployment

Create a dedicated directory under /opt, generate a secret, and stage the Compose file. Keep the settings file writable during the initial configuration phase, then treat it as infrastructure code once the instance is working.

mkdir -p /opt/searxng/{caddy,redis,searxng}
cd /opt/searxng
umask 077
openssl rand -hex 32 > searxng/secret_key.txt
cat > .env <<'ENV'
SEARXNG_HOSTNAME=search.example.com
SEARXNG_BASE_URL=https://search.example.com/
SEARXNG_INSTANCE_NAME=Private Search
ENV
cat > docker-compose.yml <<'YAML'
services:
  searxng:
    image: searxng/searxng:latest
    restart: unless-stopped
    depends_on: [redis]
    environment:
      - SEARXNG_BASE_URL=${SEARXNG_BASE_URL}
      - SEARXNG_INSTANCE_NAME=${SEARXNG_INSTANCE_NAME}
    volumes:
      - ./searxng:/etc/searxng:rw
    networks: [internal]
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --save 60 1000 --appendonly yes
    volumes:
      - redis-data:/data
    networks: [internal]
  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports: ["80:80", "443:443"]
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config
    networks: [internal]
volumes:
  redis-data:
  caddy-data:
  caddy-config:
networks:
  internal:
YAML

Manual copy fallback: select the command text in the block above if the copy button is unavailable.

Now create the SearXNG settings file. The important production choices are debug: false, a unique secret key, image_proxy: true, Redis-backed limiter support, and a short outbound timeout so one slow engine does not make the whole search page feel broken.

cat > /opt/searxng/searxng/settings.yml <<'YAML'
use_default_settings: true
general:
  instance_name: "Private Search"
  debug: false
  privacypolicy_url: false
  donation_url: false
server:
  bind_address: "0.0.0.0"
  port: 8080
  secret_key: "CHANGE_ME_FROM_SECRET_FILE"
  base_url: "https://search.example.com/"
  image_proxy: true
  limiter: true
  public_instance: false
redis:
  url: redis://redis:6379/0
search:
  safe_search: 1
  autocomplete: "duckduckgo"
  default_lang: "en"
outgoing:
  request_timeout: 3.0
  max_request_timeout: 10.0
  pool_connections: 100
  pool_maxsize: 20
engines:
  - name: google
    disabled: true
  - name: bing
    disabled: false
  - name: duckduckgo
    disabled: false
YAML
SECRET=$(cat /opt/searxng/searxng/secret_key.txt)
python3 - <<PY
from pathlib import Path
p=Path('/opt/searxng/searxng/settings.yml')
s=p.read_text().replace('CHANGE_ME_FROM_SECRET_FILE', '$SECRET')
p.write_text(s)
PY

Manual copy fallback: select the command text in the block above if the copy button is unavailable.

Create the Caddy reverse proxy and launch the stack. Caddy will request and renew certificates automatically as long as DNS is correct and ports 80 and 443 are reachable.

cat > /opt/searxng/caddy/Caddyfile <<'CADDY'
{$SEARXNG_HOSTNAME} {
  encode zstd gzip
  header {
    Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    X-Content-Type-Options "nosniff"
    Referrer-Policy "no-referrer"
    Permissions-Policy "geolocation=(), microphone=(), camera=()"
  }
  reverse_proxy searxng:8080
}
CADDY
cd /opt/searxng
docker compose up -d
docker compose ps

Manual copy fallback: select the command text in the block above if the copy button is unavailable.

Configuration and secrets handling best practices

SearXNG is simple, but the operational choices matter. Do not reuse a public demo configuration. Generate a unique secret per environment and keep it out of screenshots, chat transcripts, and repositories. The secret protects signed cookies and internal state; rotating it will invalidate sessions, which is acceptable but should be planned.

Keep the instance private unless you have a strong reason to operate it publicly. Public instances attract automated traffic, captcha pressure from upstream engines, and abuse complaints. For team usage, put it behind a VPN, an identity-aware proxy, or at minimum an IP allow list at the firewall. If you need a JSON endpoint for internal tools, issue those tools a dedicated network path rather than exposing search to the entire web.

Engine selection should match your risk tolerance. Disable engines that regularly trigger captchas from your server region. Start with a conservative set, test query quality, then add more providers gradually. For privacy, avoid enabling accounts, telemetry, or anything that writes user-level search history outside the container.

Verification checklist

Verification should test the container health, TLS path, web UI, JSON API, and Redis connectivity. Run these checks after the first deployment and after each upgrade.

cd /opt/searxng
docker compose logs --tail=80 searxng
curl -I https://search.example.com/
curl -s 'https://search.example.com/search?q=sysbrix&format=json' | jq '.query, (.results | length)'
docker compose exec redis redis-cli ping

Manual copy fallback: select the command text in the block above if the copy button is unavailable.

  • The homepage should return HTTP 200 over HTTPS.
  • A normal search should render results without engine timeout errors dominating the page.
  • The JSON endpoint should return a result list for automation consumers if you intend to use it.
  • Redis should return PONG, confirming limiter state is available.
  • Caddy logs should show certificate issuance, not repeated ACME failures.

Backups and routine operations

Most of the value is in configuration, not in application data. Back up the settings, Caddyfile, and environment file. Redis state can usually be rebuilt, but keeping its volume is harmless. Store backups somewhere outside the host if this is a shared service.

cat > /opt/searxng/backup.sh <<'SH'
#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date +%Y%m%d-%H%M%S)
DEST=/var/backups/searxng
mkdir -p "$DEST"
tar -czf "$DEST/searxng-config-$STAMP.tgz" -C /opt searxng/searxng searxng/caddy searxng/.env
find "$DEST" -type f -mtime +14 -delete
SH
chmod +x /opt/searxng/backup.sh
/opt/searxng/backup.sh
ls -lh /var/backups/searxng | tail

Manual copy fallback: select the command text in the block above if the copy button is unavailable.

For upgrades, back up first, pull images, restart, and verify. Avoid unattended upgrades for SearXNG if the instance is relied on by scripts; engine definitions and defaults can change, so a quick manual test is worth the few minutes.

cd /opt/searxng
./backup.sh
docker compose pull
docker compose up -d
docker compose logs --tail=60 searxng
curl -fsS https://search.example.com/ > /dev/null && echo ok

Manual copy fallback: select the command text in the block above if the copy button is unavailable.

Common issues and fixes

ACME certificate issuance fails. Confirm DNS points to this server, ports 80 and 443 are open, and no other service is bound to those ports. Caddy must be the public entry point for automatic certificates.

Searches are slow or empty. Check logs for engine timeouts and disable the worst offenders. Increase timeouts only after confirming the server has stable outbound connectivity. A short timeout with several reliable engines is better than a long timeout with many flaky engines.

Upstream engines show captcha or blocked errors. Reduce traffic, keep the service private, and disable engines that block your server network. Do not try to bypass provider protections; tune the instance for legitimate team use.

Redis connection errors appear. Confirm the Redis service name is exactly redis on the Compose network and that the settings file uses redis://redis:6379/0. Restart only SearXNG after settings changes.

cd /opt/searxng
# See which engines are timing out or blocked.
docker compose logs searxng | grep -iE 'timeout|captcha|blocked|error' | tail -40
# Confirm the app can reach Redis.
docker compose exec searxng python - <<'PY'
import redis
r=redis.Redis(host='redis', port=6379, db=0)
print(r.ping())
PY
# Validate Caddy configuration before restarting.
docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile

Manual copy fallback: select the command text in the block above if the copy button is unavailable.

FAQ

Should SearXNG be public or private?

For most organizations it should be private. A public instance needs abuse monitoring, stricter rate limits, and more operational attention. A private instance gives the privacy and workflow benefits without inviting internet-wide traffic.

Why use Redis if SearXNG can run without it?

Redis gives the limiter a shared state store and makes behavior more predictable under bursts. It also avoids keeping runtime state only inside the application process, which is helpful when containers restart.

Can I put SearXNG behind an SSO proxy?

Yes. Put the SSO proxy in front of Caddy or configure Caddy to forward through your identity layer. Keep SearXNG itself on the private Docker network and let the proxy enforce identity.

How many engines should I enable?

Start small. Enable a few reliable engines, measure result quality, then add more only when they improve coverage. Too many engines can slow searches and increase block or captcha errors.

Does SearXNG store user search history?

The baseline configuration does not intentionally persist user search history. However, proxy access logs and container logs may contain request paths. Review log retention if search privacy is a core requirement.

How do I monitor this deployment?

Monitor container restarts, HTTPS availability, response time for a sample query, disk usage under /opt/searxng, and Caddy certificate renewal logs. A simple uptime check plus periodic JSON query is enough for many teams.

Can internal apps use the JSON endpoint?

Yes, but treat it as an internal service. Use stable queries, set timeouts in client apps, and avoid high-frequency polling that could make upstream engines throttle your server.

Related guides

Talk to us

If you want this deployed with production hardening, monitoring, and backup automation tailored to your environment, our team can help.

Contact Us

Production Guide: Deploy Apache Guacamole with Docker Compose + Caddy + PostgreSQL on Ubuntu
Secure browser-based RDP, SSH, and VNC access without exposing remote desktop ports directly to the internet.