LinuxaiTested on real hardware

Private Web Search for Open WebUI (SearXNG)

Deploy SearXNG natively in an LXC with the JSON API enabled, then wire Open WebUI to it for cited, private web search — no cloud, no API keys.

DistrosDebian 13, Debian 12
Shellbash
Updated
Script
bash
# Deploy SearXNG (private metasearch) natively in a Debian LXC and expose the
# JSON API that Open WebUI needs. Run as root inside the container unless noted.
# Full walkthrough + the Open WebUI wiring traps: /blog/searxng-owui-private-web-search

set -euo pipefail

# 1. System packages (Python venv toolchain + build deps + git).
apt-get update
apt-get install -y python3-dev python3-venv python3-pip \
  git build-essential libxslt-dev zlib1g-dev libffi-dev libssl-dev

# 2. Dedicated service user + source checkout.
useradd --system --home-dir /usr/local/searxng --shell /bin/bash searxng || true
mkdir -p /usr/local/searxng && chown searxng:searxng /usr/local/searxng
sudo -H -u searxng git clone https://github.com/searxng/searxng \
  /usr/local/searxng/searxng-src

# 3. Virtualenv + editable install (as the searxng user).
sudo -H -u searxng python3 -m venv /usr/local/searxng/searx-pyenv
sudo -H -u searxng /usr/local/searxng/searx-pyenv/bin/pip install -U pip setuptools wheel
sudo -H -u searxng /usr/local/searxng/searx-pyenv/bin/pip install \
  --use-pep517 --no-build-isolation -e /usr/local/searxng/searxng-src
# granian is SearXNG's current recommended app server (replaces uWSGI).
sudo -H -u searxng /usr/local/searxng/searx-pyenv/bin/pip install granian

# 4. Settings. JSON format is REQUIRED for Open WebUI or it gets 403/empty.
mkdir -p /etc/searxng
SECRET=$(openssl rand -hex 32)
cat > /etc/searxng/settings.yml <<EOF
use_default_settings: true
server:
  secret_key: "$SECRET"
  limiter: false          # LAN-only instance: skip the Redis/Valkey rate limiter
  public_instance: false
search:
  formats:
    - html
    - json                # REQUIRED: without this, Open WebUI web search 403s
EOF
chown -R searxng:searxng /etc/searxng
chmod 640 /etc/searxng/settings.yml

# 5. systemd unit. --access-log is deliberate: it lets you SEE queries land.
cat > /etc/systemd/system/searxng.service <<'EOF'
[Unit]
Description=SearXNG metasearch (granian)
After=network.target

[Service]
User=searxng
WorkingDirectory=/usr/local/searxng/searxng-src
Environment=SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml
ExecStart=/usr/local/searxng/searx-pyenv/bin/granian --interface wsgi \
  --host 0.0.0.0 --port 8888 --access-log searx.webapp:app
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now searxng

# 6. Acceptance: the JSON API must return 200.
sleep 3
curl -s -o /dev/null -w 'searxng json api: %{http_code}\n' \
  "http://127.0.0.1:8888/search?q=proxmox&format=json"

# --- Open WebUI wiring (do this in the OWUI admin panel / model editor) ---
# a. Admin Panel > Settings > Web Search:
#      Enable Web Search = on
#      Web Search Engine = searxng
#      SearXNG Query URL  = http://10.0.0.20:8888/search?q=<query>   (keep <query> literal)
# b. In recent OWUI, the DB config beats env vars — set it in the panel, not just env.
# c. Each chat model needs function-calling mode = legacy, or forced search silently
#    no-ops (a freshly pulled model has no entry at all). Set it in the model editor.
# d. Leave "search query generation" OFF: small local models emit bad queries and
#    OWUI then silently skips the search.

What this does

This deploys SearXNG — a private, self-hosted metasearch engine — natively in a Debian LXC container, with the JSON search API turned on so Open WebUI can use it for web search. The result: your local Ollama model can answer current-events questions with citations, and no query ever goes to a commercial search API. The script above handles the SearXNG install; the Open WebUI wiring is a short set of admin-panel steps listed in the script comments and explained in full in the companion post.

The full narrative, including why each of the three Open WebUI wiring traps is so easy to miss, is in Give Your Local AI Private Web Search With SearXNG.

Prerequisites

  • A Debian LXC container (or VM) with internet access — SearXNG is the thing doing the searching, so it must reach the web and resolve DNS. 2 CPU / 1 GB RAM / 10 GB disk is enough.
  • An existing Open WebUI + Ollama install on your network, reachable from the SearXNG host.
  • openssl (for the secret key) and git, both installed by the script.

Notes

  • Make these values your own before you rely on the result: replace 10.0.0.20 in the Open WebUI query URL with your SearXNG host’s real IP, the port 8888 if you changed it, and generate your own secret_key (the script does this with openssl rand -hex 32 inside the container — never reuse the example). If a value looks specific to one machine, it’s a placeholder to change, not a literal to copy.
  • JSON format is the number-one gotcha. SearXNG serves only HTML by default; without json in search.formats, Open WebUI’s requests come back 403 Forbidden. This is documented in both the SearXNG search API docs and the Open WebUI SearXNG guide.
  • limiter: false is safe only on a private instance. It disables SearXNG’s abuse protection (and the Redis/Valkey dependency it needs). Fine for a LAN-only box only you can reach; never on anything internet-facing.
  • Don’t increase granian’s worker count. SearXNG’s docs advise against it — more workers can trip public engines’ bot detection. The defaults are correct.
  • --access-log is intentional. granian is quiet by default; the flag lets you confirm searches actually arrive with journalctl -u searxng | grep "GET /search".
  • The model function-calling mode is the silent killer. In the Open WebUI version tested here, forced web search runs only for models set to legacy function calling, and a freshly pulled model has no such entry — so search is skipped for everyone with no error. Set it per model in the model editor.
  • Known-normal noise: on a fresh IP, some engines briefly CAPTCHA-suspend themselves and SearXNG serves from the rest of the pool; a botdetection: X-Forwarded-For warning on direct connections is cosmetic. Neither is a fault.