Private AI chat with web search: Open WebUI and SearXNG

Open WebUI with a local Ollama model, and SearXNG as its web search engine, on one server behind Caddy. Searches go from your server to the search engines, not to a third-party search API.

Reviewed 14 October 2026

Why these apps together

A small team or a household wants an AI chat whose answers can cite current web pages, without sending prompts to a hosted model or queries to a paid search API.

The app pages size and deploy each app alone. Together they need two things neither page covers: Open WebUI must be pointed at SearXNG over the network Caddy shares with both, and SearXNG must be told to answer in JSON, which it refuses by default (403 Forbidden). This page sizes the pair as one server and gives the exact address and setting.

Apps in this stack

AppRole in the stackLoad levelCompose support
Open WebUIThe chat interface, with Ollama running the model on the CPU.LightRun-tested
SearXNGThe metasearch engine Open WebUI sends its web searches to.LightSupported

How the apps connect

Open WebUI to SearXNG: http://searxng:8080/search
Open WebUI sends web searches to SearXNG's /search endpoint over Caddy's edge network, by service name. The request never leaves the server's Docker networks.

Resources for the whole server

Computed by the Stack Sizer's engine for these apps and load levels: what the apps need, the system baseline and the safety headroom. The same input in the Stack Sizer gives the same numbers.

PartvCPURAM (GiB)Disk (GiB)
Open WebUI35.7518.472
SearXNG0.50.50.6
System baseline0.11.258.3
Safety headroom0.721.54.106
Recommended total4.32931.478

Caddy is not included in the resource calculation of the Stack Sizer or the Deployment Spec.

Open these apps in the Stack Sizer

Files from the Compose Generator

docker-compose.yml
# Generated by the ANAROQ Compose Generator: https://anaroq.com/tools/compose-generator
# Engine @anaroq/compose 1.0.0, catalog 2026.10.10, exposure caddy.
# Apps: open-webui 1.2.0, searxng 1.0.0 (supported, not run-tested).
# No secrets in this file: copy .env.example to .env and fill it in before `docker compose up -d`.

services:
  open-webui:
    image: "ghcr.io/open-webui/open-webui:v0.11.4"
    restart: "unless-stopped"
    environment:
      OLLAMA_BASE_URL: "http://open-webui-ollama:11434"
      WEBUI_SECRET_KEY: "${OPEN_WEBUI_SECRET_KEY:?set OPEN_WEBUI_SECRET_KEY in .env}"
    volumes:
      - "open-webui-data:/app/backend/data"
    networks:
      - "open-webui-internal"
      - "edge"
    depends_on:
      open-webui-ollama:
        condition: "service_started"
    healthcheck:
      start_period: "120s"
  open-webui-ollama:
    image: "ollama/ollama:0.40.2"
    restart: "unless-stopped"
    volumes:
      - "open-webui-ollama:/root/.ollama"
    networks:
      - "open-webui-internal"
  searxng:
    image: "searxng/searxng:2026.10.13-22c64b8d3"
    restart: "always"
    environment:
      SEARXNG_SECRET: "${SEARXNG_SECRET:?set SEARXNG_SECRET in .env}"
      SEARXNG_BASE_URL: "https://${SEARXNG_DOMAIN:?set SEARXNG_DOMAIN in .env}/"
    volumes:
      - "searxng-config:/etc/searxng"
      - "searxng-data:/var/cache/searxng"
    networks:
      - "searxng-internal"
      - "edge"
    healthcheck:
      test:
        - "CMD"
        - "wget"
        - "-q"
        - "--spider"
        - "http://127.0.0.1:8080/healthz"
      interval: "30s"
      timeout: "5s"
      retries: 3
      start_period: "60s"
  caddy:
    image: "caddy:2.11.7-alpine"
    restart: "unless-stopped"
    environment:
      OPEN_WEBUI_DOMAIN: "${OPEN_WEBUI_DOMAIN:?set OPEN_WEBUI_DOMAIN in .env}"
      SEARXNG_DOMAIN: "${SEARXNG_DOMAIN:?set SEARXNG_DOMAIN in .env}"
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - "./Caddyfile:/etc/caddy/Caddyfile:ro"
      - "caddy-data:/data"
      - "caddy-config:/config"
    networks:
      - "edge"
networks:
  open-webui-internal: {}
  searxng-internal: {}
  edge: {}
volumes:
  open-webui-data: {}
  open-webui-ollama: {}
  searxng-config: {}
  searxng-data: {}
  caddy-data: {}
  caddy-config: {}
.env.example
# Generated by the ANAROQ Compose Generator: https://anaroq.com/tools/compose-generator
# Engine @anaroq/compose 1.0.0, catalog 2026.10.10, exposure caddy.
# Copy this file to .env and fill in every value. Never commit .env.
# ANAROQ does not create passwords or keys: generate them on your own machine.

# --- open-webui ---
# Key Open WebUI signs sessions with (WEBUI_SECRET_KEY). Changing it signs everyone out.
# Secret, required. For example: openssl rand -hex 32
OPEN_WEBUI_SECRET_KEY=
# Public host name of Open WebUI behind the reverse proxy, for example chat.example.com.
# Required.
OPEN_WEBUI_DOMAIN=

# --- searxng ---
# Server secret_key SearXNG uses for cryptography (SEARXNG_SECRET overrides the value in settings.yml). SearXNG refuses to start with the placeholder ultrasecretkey.
# Secret, required. For example: openssl rand -hex 32
SEARXNG_SECRET=
# Public host name of SearXNG behind the reverse proxy, for example search.example.com. Used for SEARXNG_BASE_URL.
# Required.
SEARXNG_DOMAIN=
Caddyfile
# Generated by the ANAROQ Compose Generator: https://anaroq.com/tools/compose-generator
# Engine @anaroq/compose 1.0.0, catalog 2026.10.10, exposure caddy.
# Each site address comes from .env through the Caddy container's environment.

{$OPEN_WEBUI_DOMAIN} {
	reverse_proxy open-webui:8080
}

{$SEARXNG_DOMAIN} {
	reverse_proxy searxng:8080
}

Generated by the Compose Generator's engine for these apps behind Caddy. The files hold no secrets: copy .env.example to .env and fill it in.

Compose Analyzer result

Error0
High0
Medium0
Low10
Info0

10 findings: 0 error, 0 high, 0 medium, 10 low, 0 info.

Low10

  1. LowAQ-HLTH-005Line 8, column 5services.open-webui

    open-webui does not limit the size of its logs (logging.options.max-size).

    Fix: Limit the log size so logs cannot fill the disk.

  2. LowAQ-SEC-005Line 11, column 7services.open-webui.environment

    open-webui passes secrets through environment (WEBUI_SECRET_KEY).

    Fix: Where the image supports it, use secrets: or a _FILE variable instead.

  3. LowAQ-HLTH-002Line 24, column 5services.open-webui-ollama

    open-webui-ollama has no healthcheck.

    Fix: Add a healthcheck, so Compose and you can tell when the service is ready.

  4. LowAQ-HLTH-005Line 24, column 5services.open-webui-ollama

    open-webui-ollama does not limit the size of its logs (logging.options.max-size).

    Fix: Limit the log size so logs cannot fill the disk.

  5. LowAQ-HLTH-005Line 31, column 5services.searxng

    searxng does not limit the size of its logs (logging.options.max-size).

    Fix: Limit the log size so logs cannot fill the disk.

  6. LowAQ-SEC-005Line 34, column 7services.searxng.environment

    searxng passes secrets through environment (SEARXNG_SECRET).

    Fix: Where the image supports it, use secrets: or a _FILE variable instead.

  7. LowAQ-HLTH-002Line 54, column 5services.caddy

    caddy has no healthcheck.

    Fix: Add a healthcheck, so Compose and you can tell when the service is ready.

  8. LowAQ-HLTH-005Line 54, column 5services.caddy

    caddy does not limit the size of its logs (logging.options.max-size).

    Fix: Limit the log size so logs cannot fill the disk.

  9. LowAQ-PORT-005Line 60, column 9services.caddy.ports.0

    This file publishes 3 port mappings. Docker publishes ports past ufw rules.

    Fix: Filter them in the DOCKER-USER chain or at the provider's firewall, or do not publish them.

  10. LowAQ-VOL-005Line 64, column 9services.caddy.volumes.0

    caddy bind-mounts the relative path ./Caddyfile.

    Fix: That works, but the data lives in the directory you run Compose from: back it up.

The Compose Analyzer (ruleset 1.0.0) checked docker-compose.yml: 0 error, 0 high, 0 medium and 10 low findings. A recipe is published only without error and high findings.

Continue in the tools

The link opens the Compose Generator with these apps, options and exposure, and with the Deployment Spec of this sizing, so cloud-init and the Runbook start from the same state.

Open in the journey

Runbook

Before the first start

  1. Copy .env.example to .env next to docker-compose.yml and fill in every value. Compose stops with an error while a required value is missing.
  2. Create OPEN_WEBUI_SECRET_KEY on your own machine, for example with openssl rand -hex 32. ANAROQ never creates passwords or keys.
  3. Create SEARXNG_SECRET on your own machine, for example with openssl rand -hex 32. ANAROQ never creates passwords or keys.
  4. Keep Caddyfile next to docker-compose.yml; the Caddy container mounts it read-only.
  5. Ports 80 and 443 (TCP, and 443/UDP for HTTP/3) must be reachable from the internet for Caddy's automatic HTTPS.
  6. Before starting, point the A/AAAA records of the domain in OPEN_WEBUI_DOMAIN (open-webui) to this server; Caddy requests its certificate on first start.
  7. Before starting, point the A/AAAA records of the domain in SEARXNG_DOMAIN (searxng) to this server; Caddy requests its certificate on first start.
  8. Models are not downloaded automatically: docker compose exec open-webui-ollama ollama pull <model>. CPU only, no GPU.
  9. Start the stack with docker compose up -d and follow it with docker compose ps.

After the first start

  1. Allow JSON answers in SearXNG

    After the first start, add json to search.formats in /etc/searxng/settings.yml (the searxng-config volume), then restart SearXNG. Without it Open WebUI's searches fail with 403 Forbidden.

    search:
      formats:
        - html
        - json
  2. Point Open WebUI at SearXNG

    In Open WebUI, open Settings, then Admin, then Web Search. Turn web search on, choose searxng as the engine and enter the query URL below.

    http://searxng:8080/search
  3. Check the connection

    Start a chat, turn on web search for it and ask about something recent. The answer lists the pages SearXNG returned; a 403 means the JSON format is still off.

What the apps share

  • Both apps get a public host name through Caddy. SearXNG is not set up as a public instance (its limiter is off), so keep its host name to yourselves or restrict it in the Caddyfile; Open WebUI does not need SearXNG to be public.
  • Web search adds the fetched pages to the model's context. On the light level the model's context is small, so keep the number of search results low in Open WebUI's web search settings.
  • Back up Open WebUI's data volume and SearXNG's config volume (/etc/searxng), which holds the settings.yml you edit below. SearXNG's cache volume needs no backup.

Alternatives

  • Without web search, Open WebUI alone is enough: its own page sizes it and opens it in the Compose Generator. Open WebUI
  • Open WebUI can also use a hosted search API instead of SearXNG; that removes the second app but sends every query to that provider.

Basis

Catalog
2026.10.10
Sizing policy
2026.10.0
Compose engine
@anaroq/compose 1.0.0
Analyzer ruleset
1.0.0

Sources

How profiles, sizes and Compose files are built is described in the methodology. Read the methodology

All self-hosted apps