# searxng Privacy-respecting metasearch. **Runs on nh3-docker** (`10.100.50.40:9996`, `searxng.nh3.internal`), and is exposed to every Claude Code session on nh3-dev as the `web_search` MCP tool. ## ⚠ Why NH3 and not the colo Measured 2026-09-03: ana-docker egress 38.120.12.42 datacenter -> DuckDuckGo/Startpage CAPTCHA nh3-docker egress 70.230.226.88 residential -> no gate on that account Search engines gate datacenter ranges. Running the aggregator from the residential-egress site removes the problem at the source rather than proxying around it — the same reason the fleet keeps a residential SOCKS5 proxy on nh3-dev for yt-dlp. If NH3's egress ever changes, `outgoing.proxies` in `conf/searxng-settings.yml` has the fallback commented in place. ⚠ It is **not** a complete fix: `brave`, `duckduckgo` and `startpage` still CAPTCHA from here. `google cse` carries general search at ~20 results/query, and `yandex`, `wiby`, `github`, `stackoverflow` and `marginalia` all work. General search is therefore effectively single-engine — if `google cse` breaks, the instance goes quiet, which is exactly the failure below. ## ⚠ /healthz cannot tell you whether search works On 2026-09-03 the old ana-docker instance was found returning **zero results for every query**, for an unknown number of weeks, while: - the container reported `healthy` for 7 straight days, 0 restarts; - the Homepage card was green; - `/healthz` returned 200 every 30 seconds. It was running **2026.4.17 while current was 2026.9.3** — 4.5 months of engine scrapers rotting against sites that had changed their markup. `:latest` means "latest at pull time", and nothing re-pulls on its own. Proven by experiment before touching anything: the new image, same settings file, same host, same query returned **20 results where the running one returned 0**. **`scripts/searxng-health.sh` asserts the property, not the mechanism** — three unrelated queries must each return results > 0. Run it after any change, and periodically; it is the only thing that catches rot. ## Update ssh infra-ops@nh3-docker 'cd /opt/docker/compose/searxng && \ sudo docker compose pull && sudo docker compose up -d' scripts/searxng-health.sh ## The MCP tool `services/searxng-mcp/` — installed with `uv tool install` and registered at **user scope** (`claude mcp add --scope user searxng searxng-mcp`), so every Claude Code session gets `web_search` with no per-project setup. ⚠ **Zero results raise an error rather than returning an empty list.** An empty list is indistinguishable from a broken aggregator, and that ambiguity is what let the old instance fail silently for weeks. Same reasoning as althing's "an unreachable post office is an OUTAGE, never an empty inbox". ⚠ Written against **mcp 2.x**, where `FastMCP` became `MCPServer` and the v1 `@app.list_tools()` decorator API is gone. v1 examples fail at import with `'Server' object has no attribute 'list_tools'`. ⚠ `uv tool install --force` alone served a **cached build** and silently reinstalled the old code — the installed file still had the v1 API after the source no longer did. `--reinstall --no-cache` was required, and the check that caught it was `md5sum` of source vs installed. ## Secret `SEARXNG_SECRET` lives in `/opt/docker/compose/searxng/.env` (0600, root) on the host and is vaulted at `nh3-docker/searxng-secret`. Never in git.