Update SearXNG, Gluetun, and Valkey to describe the quadlet units instead of compose: searxng sets Network=gluetun.container, the units install to ~/.config/containers/systemd/, and scripts/deploy-searxng-quadlet.sh deploys them. Add a Boot Persistence section to the Stack doc. Correct the Gluetun tunnel check (the :8000 server 404s; verify tun0 instead) and note the pinned 10.89.2.0/24 subnet.
4.3 KiB
SearXNG
SearXNG is a privacy-respecting metasearch engine. It aggregates results from many upstream search engines into one result set and serves both an HTML UI and a JSON API for agents.
Host: Dankclaw — Hostname: search.dankclaw.local — Docs: https://docs.searxng.org
Facts (from research, primary source)
| Item | Value |
|---|---|
| Image | ghcr.io/searxng/searxng:latest (or DockerHub mirror) |
| Port | 8081 (host, on Gluetun) → 8080 (container) |
| Config | /etc/searxng/settings.yml (bind mount) |
| Cache | Valkey (replaces Redis) — valkey/valkey:9-alpine |
| Formats | html, json (agent API) |
VPN Routing (critical)
All SearXNG outbound traffic exits through the Gluetun VPN container. The searxng unit sets Network=gluetun.container, which makes it reuse gluetun's network namespace:
[Container]
ContainerName=searxng
Network=gluetun.container
Port publishing happens on the gluetun unit, not on searxng. See Gluetun and the knowledge base page VPN Routing for SearXNG (KB Pattern 2 is the one used here).
Boot Persistence (Quadlet)
The trio runs as quadlet units, not podman-compose, so it starts at boot. The unit files live in searxng/quadlet/ and install to ~/.config/containers/systemd/.
| Unit file | Service | Role |
|---|---|---|
searxng.network |
searxng-network.service |
DNS-enabled bridge, subnet pinned to 10.89.2.0/24 |
gluetun.container |
gluetun.service |
WireGuard tunnel; publishes 8081:8080 |
searxng-valkey.container |
searxng-valkey.service |
Valkey cache, alias valkey |
searxng.container |
searxng.service |
Reuses gluetun's netns; publishes no port |
gluetun and valkey join the searxng network. searxng reuses gluetun's netns and reaches valkey by name on that network. Each unit sets WantedBy=default.target. User linger is on, so the units start at boot.
Deploy with scripts/deploy-searxng-quadlet.sh dankclaw. It copies the units, daemon-reloads, enables them, and starts them in dependency order.
settings.yml Keys
use_default_settings: true
server:
secret_key: "${SEARXNG_SECRET}" # from .env
base_url: "http://search.dankclaw.local/"
limiter: true
method: "POST"
search:
default_lang: "en"
formats:
- html
- json
valkey:
url: "valkey://searxng-valkey:6379/0"
outgoing:
request_timeout: 3.0
max_request_timeout: 10.0
retries: 3
Volumes
| Container path | Host path (convention) |
|---|---|
/etc/searxng |
/opt/searxng/config |
/etc/searxng/valkey |
(valkey container data) |
Healthcheck
SearXNG responds on /healthz with 200 when ready.
Trusted Group (limiter.toml)
The trusted group lives in shared/searxng/limiter.toml, not in settings.yml. The settings.yml file only turns the limiter on. The file deploys to /opt/searxng/config/limiter.toml, mounted at /etc/searxng/limiter.toml.
Two lists control access:
pass_ip— an IP in this list gets unrestricted access and bypasses all bot detection.trusted_proxies— if the request IP is in this list, SearXNG reads the real client IP fromX-Forwarded-For(set by Caddy).
The whole homelab LAN 192.168.0.0/24 is in both lists. This lets local agents and browsers poll the JSON API without tripping the 4-per-hour limit. deploy.sh copies the file into place on each deploy.
Access SearXNG through Caddy (search.dankclaw.local). A direct request on the published port has no X-Forwarded-For header. The bot-detection cannot resolve the client and returns 429. The searxng network subnet 10.89.2.0/24 is in trusted_proxies so Caddy's request is trusted. The subnet is pinned in searxng.network, so the value stays stable across reboots.
Gotchas
- The JSON API is rate-limited hard by the limiter (4 per 3600s per IP for untrusted). Add the agent's IP to
pass_ipinlimiter.tomlif it polls. - Reach SearXNG through Caddy, not the raw published port. A direct port hit has no
X-Forwarded-Forheader and the bot-detection returns 429. base_urlmust match the public address or proxy rewrites break.- Keep the valkey cache container on the same podman network as searxng.
See Also
- Dankclaw Stack
- Gluetun — the VPN container
- Knowledge base: VPN Routing for SearXNG, SearXNG Tuning, Agentic Search Best Practices