AILLMSelf-Hosting

Private Web Search for Local LLMs: SearXNG + Open WebUI with Docker (Linux, Windows, Mac)

Before you start

  • 17 min read

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#

SearXNG is a free metasearch engine you run yourself, and Open WebUI can use it as the web search behind your local model. Your model gets fresh pages to read. You need no paid search API and no key. The search engines see a query from your server, not a profile of you.

At a glance#

  • What you get: a chat where the web-search button works, powered by your own SearXNG.
  • Time needed: about 20 minutes (plus your model download, if you do not have one yet).
  • Difficulty: easy. You copy about six commands and two small files.
  • You need: Docker with Compose v2, a running model backend such as Ollama, and a free port 3000.
  • You do not need: an API key, an account at any search company, or a domain (the domain is only for the optional Traefik part).
Open WebUIv0.12.4, released 2026-10-25, image ghcr.io/open-webui/open-webui:v0.12.4 (official image)
SearXNGNo GitHub releases. Date tags on Docker Hub: docker.io/searxng/searxng:2026.10.25-54ca1d9cf (official image, newest tag on 2026-10-26)
Valkeydocker.io/valkey/valkey:9-alpine (the Redis-compatible store SearXNG's own compose file uses)
Ports3000 (Open WebUI) and 8081 (SearXNG screen), both bound to 127.0.0.1
Official docsSearXNG container install, Open WebUI SearXNG page

Which versions, and why (checked 2026-10-26). We found no repeated reports of failed installs or data loss for Open WebUI v0.12.4 or the newest SearXNG tag, so this guide uses both. The evidence is thin, because v0.12.4 is one day old.

  • One behaviour change in v0.12.4: tool connections that users saved in their own settings stop working until an admin turns on Direct Integrations. Nothing is deleted. Web search is not affected.
  • Rollback target: v0.12.3 (2026-10-16). If you roll back, restore your backup instead of only changing the tag.
  • One trap in other tutorials: Open WebUI's own SearXNG page still tells you to clone the searxng-docker repo. That repo was archived on 2026-03-28. SearXNG's current docs use a compose file from the main repo, and so do we.

Already have Open WebUI and Ollama running? This guide adds to it. For the basics, see our older Open WebUI with Ollama and Docker Compose guide.

How it fits together#

You ask, Open WebUI turns it into a search, SearXNG asks the engines, and the results flow back to your model.

One question flows top to bottom: you ask in Open WebUI, Open WebUI sends a query to SearXNG, SearXNG asks the outside search engines and merges the results as JSON, Open WebUI reads the top pages and your model writes the answer. Valkey sits beside SearXNG as the limiter memory.

Three containers do the work:

ContainerJobWho can reach it
open-webuiChat screen and the search callerYou, at 127.0.0.1:3000
searxngAsks the engines, returns JSONOpen WebUI, plus you at 127.0.0.1:8081 for testing
valkeyRemembers request counts for the limiterOnly SearXNG

Before you start#

LinuxWindowsMac
DockerDocker Engine + Compose v2Docker Desktop, WSL2 backendDocker Desktop
ShellbashPowerShellTerminal
MemoryPlan for about 2 GB free for the two apps (our estimate)Same, plus the WSL2 VMSame, plus the Docker VM
Good to knowAdd your user to the docker groupTurn on virtualization in the BIOSApple Silicon works with the multi-arch images (VERIFY on your chip)

Check your tools:

Run in terminalbash
docker --version
docker compose version

Checklist:

  • [ ] Docker runs (docker ps works)
  • [ ] Port 3000 and port 8081 are free
  • [ ] A model backend is running (for example Ollama) and has one model pulled
  • [ ] Any existing Open WebUI data folder is backed up

Your model backend is separate from this guide. Open WebUI reaches it through OLLAMA_BASE_URL. If Ollama runs on your host and Open WebUI runs in Docker, Ollama must accept connections from Docker (on Linux this usually means it listens on more than 127.0.0.1; VERIFY for your install).

Tested on 2026-10-26: Linux = not run, Windows = not run, Mac = not run. Every command here is checked against the official docs listed in Sources. We did not execute them.

Step 1: Create folders and secrets#

All data lives under docker_volumes/ in your home folder. The .env file holds two random secrets and the data path.

  1. Create the folders.
  2. Create .env with two random secrets.
  3. Keep it private.

Linux#

Run in terminalbash
mkdir -p "$HOME/docker_volumes/searxng/core-config" "$HOME/docker_volumes/open-webui" \
         "$HOME/docker_volumes/search-stack/backups"
cd "$HOME/docker_volumes/search-stack"
umask 077
printf 'DATA_ROOT=%s/docker_volumes\n' "$HOME" > .env
printf 'SEARXNG_SECRET=%s\n' "$(openssl rand -hex 32)" >> .env
printf 'WEBUI_SECRET_KEY=%s\n' "$(openssl rand -hex 32)" >> .env

Windows (PowerShell)#

Codepowershell
$Root = Join-Path $HOME "docker_volumes"
"searxng/core-config","open-webui","search-stack/backups" | ForEach-Object {
  New-Item -ItemType Directory -Force -Path (Join-Path $Root $_) | Out-Null }
Set-Location (Join-Path $Root "search-stack")
function New-HexSecret { $b = New-Object byte[] 32; [Security.Cryptography.RandomNumberGenerator]::Fill($b); -join ($b | ForEach-Object { $_.ToString("x2") }) }
@("DATA_ROOT=$($Root.Replace('\','/'))", "SEARXNG_SECRET=$(New-HexSecret)", "WEBUI_SECRET_KEY=$(New-HexSecret)") |
  Set-Content -Encoding ascii .env

Mac (Terminal)#

Same commands as Linux.

Check it worked#

Run in terminalbash
ls -la ~/docker_volumes

You should see searxng, open-webui and search-stack. On Windows run dir $Root.

Step 2: Write the SearXNG settings#

SearXNG reads two small files from core-config/. Save both in ~/docker_volumes/searxng/core-config/ (Windows: $HOME\docker_volumes\searxng\core-config\).

File 1: settings.yml

File contentyaml
use_default_settings: true

search:
  formats:
    - html
    - json

server:
  limiter: true
  image_proxy: true

valkey:
  url: valkey://valkey:6379/0

What each part does:

SettingWhy
use_default_settings: trueYou only list what you change. Everything else stays default.
formats: html, jsonOpen WebUI asks for JSON. SearXNG answers 403 Forbidden to any format not listed.
limiter: trueRate-limits requests and blocks bots. It needs Valkey.
valkey: urlThe host name valkey is the service name in the compose file below.
secret_keyNot in this file on purpose. The compose file sets it from SEARXNG_SECRET.

File 2: limiter.toml

Open WebUI's calls can look like a bot to the limiter. This file lets your private Docker network through (Docker's default networks live in these ranges; add your own if you changed them):

File contenttoml
[botdetection.ip_lists]
pass_ip = [
  '172.16.0.0/12',
  '10.0.0.0/8',
]

On a private setup that nobody else can reach, you may instead set limiter: false in settings.yml. The limiter helps mostly on public instances.

Linux#

Run in terminalbash
nano ~/docker_volumes/searxng/core-config/settings.yml
nano ~/docker_volumes/searxng/core-config/limiter.toml

Windows (PowerShell)#

Run in terminalpowershell
notepad $HOME\docker_volumes\searxng\core-config\settings.yml
notepad $HOME\docker_volumes\searxng\core-config\limiter.toml

Notepad may offer .txt on save. Choose "All files" so the name stays exact.

Mac (Terminal)#

Run in terminalbash
open -e ~/docker_volumes/searxng/core-config/settings.yml
open -e ~/docker_volumes/searxng/core-config/limiter.toml

Use plain text mode in TextEdit.

Check it worked#

Both files exist and the YAML has no tabs. You can check the names with ls ~/docker_volumes/searxng/core-config (Windows: dir).

Step 3: Start the stack with Docker Compose#

Save this as docker-compose.yml in the search-stack folder. It has no obsolete version: line. It publishes only two ports, both to your own computer.

File contentyaml
name: search-stack

services:
  searxng:
    image: docker.io/searxng/searxng:2026.10.25-54ca1d9cf
    restart: unless-stopped
    environment:
      SEARXNG_SECRET: ${SEARXNG_SECRET:?set SEARXNG_SECRET in .env}
    volumes:
      - "${DATA_ROOT}/searxng/core-config:/etc/searxng"
      - searxng-cache:/var/cache/searxng
    ports:
      - "127.0.0.1:8081:8080"
    depends_on:
      - valkey

  valkey:
    image: docker.io/valkey/valkey:9-alpine
    restart: unless-stopped
    command: valkey-server --save 300 1 --save 60 1000 --loglevel warning
    volumes:
      - valkey-data:/data

  open-webui:
    image: ghcr.io/open-webui/open-webui:v0.12.4
    restart: unless-stopped
    environment:
      WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY:?set WEBUI_SECRET_KEY in .env}
      OLLAMA_BASE_URL: http://host.docker.internal:11434
      ENABLE_WEB_SEARCH: "True"
      WEB_SEARCH_ENGINE: searxng
      SEARXNG_QUERY_URL: http://searxng:8080/search
      WEB_SEARCH_RESULT_COUNT: "3"
      WEB_SEARCH_CONCURRENT_REQUESTS: "10"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    volumes:
      - "${DATA_ROOT}/open-webui:/app/backend/data"
    ports:
      - "127.0.0.1:3000:8080"
    depends_on:
      - searxng

volumes:
  searxng-cache:
  valkey-data:

Notes on the choices:

  • The cache and Valkey volumes are disposable, so they are Docker-managed volumes. Your real data (settings, chats) is in docker_volumes.
  • SEARXNG_SECRET overrides secret_key from the environment. This is documented on SearXNG's server settings page.
  • The Valkey --save line is SearXNG's own default from its compose file.
  • Your model backend is not in this file. Add your own Ollama container here if you prefer one stack.
  • Open WebUI keeps some settings in its database after the first start. If you change the WEB_SEARCH_* variables later and nothing happens, change them in the Admin screen instead.

Linux#

Run in terminalbash
docker compose config --quiet
docker compose up -d
docker compose ps

Windows (PowerShell)#

Run in terminalpowershell
docker compose config --quiet
docker compose up -d
docker compose ps

Mac (Terminal)#

Same commands as Linux.

Check it worked#

docker compose ps should list three services as running. Never paste docker compose config output anywhere: it shows your secrets.

Now test SearXNG from inside the Open WebUI container:

Run in terminalbash
docker compose exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" | head -c 300

You should see the start of a JSON document ({"query": "test", ...). A 403 means JSON is not enabled. See Troubleshooting.

Step 4: First run in Open WebUI#

  1. Open http://localhost:3000 and create the first account. It becomes the admin. Use a strong password.
  2. Go to Settings, Admin, Connections and check that your model backend is connected.
  3. Go to Settings, Admin, Web Search. Confirm:
    • Web Search is on
    • Engine is searxng
    • Query URL is http://searxng:8080/search
  4. Open a new chat. Click Integrations (next to the plus button) and switch Web Search on.
  5. Ask something current, for example "What is the latest stable Linux kernel?"
  6. Check that sources appear under the answer.

Two details that surprise people:

  • The toggle is per chat. It resets when you reload or open another chat. Turn it on each time.
  • Do not type localhost in the query URL. Inside the container, localhost is the container itself. Use the service name searxng.

Button names can change between releases (VERIFY on your version).

Linux#

Open http://localhost:3000 in your browser. The steps are the same.

Windows (PowerShell)#

Same steps. Run docker compose commands from the search-stack folder.

Mac (Terminal)#

Same steps as Linux.

Check it worked#

You see source links under the answer. For a second proof, open http://localhost:8081 and search there: you should see normal results.

Query URL format#

In Open WebUI you enter only the endpoint:

PartValue
Query URLhttp://searxng:8080/search
What Open WebUI addsq (the query), format=json, and the language
Language fieldDefault all (SEARXNG_LANGUAGE)
Old style /search?q=<query>Still accepted

When engines block you#

SearXNG sends your query to engines like Google or Bing. Sometimes they show a CAPTCHA or slow you down. This is normal for a small private instance.

Try these in order:

  1. Open http://localhost:8081/preferences and switch off engines that show errors.
  2. Keep a mix of engines (for example DuckDuckGo, Brave, Wikipedia, Startpage; VERIFY the names in your instance).
  3. Lower Concurrent Requests and the result count in Open WebUI.
  4. Read the log: docker compose logs -f searxng.
  5. Be patient. Hammering the instance makes blocks last longer.

SearXNG's own docs also have a maintenance page about answering a CAPTCHA from your server's IP.

Privacy: what is and is not private#

WhoWhat they see
The search enginesThe query text and your server's IP address. Not your browser or your identity.
Sites your model readsThe page fetch request from your server
SearXNG itselfDoes not track or profile users
Your modelThe pages and snippets, as context

So it is more private, not invisible. Keep debug off (it is off by default) so your logs stay small.

Put it behind HTTPS (with or without Traefik)#

With Traefik#

This part is for a server with a domain. If you only use the stack on your own computer, skip it.

Only Open WebUI gets a Traefik route. Never give SearXNG a public route. A public SearXNG with JSON on is a free search API for bots.

Create the shared network once if it is missing:

Run in terminalbash
docker network create proxy

Add this to the open-webui service, and remove its ports: entry:

File contentyaml
    networks: [default, proxy]
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.owui.rule=Host(`chat.<your-domain>`)
      - traefik.http.routers.owui.entrypoints=https
      - traefik.http.routers.owui.tls=true
      - traefik.http.services.owui.loadbalancer.server.port=8080
      - traefik.http.routers.owui.middlewares=lan-only
      - traefik.http.middlewares.lan-only.ipallowlist.sourcerange=<your-lan-range>

At the bottom of the file add:

File contentyaml
networks:
  proxy:
    external: true

Name your entrypoint and certificate setup as in your own Traefik. The label syntax follows Traefik v3 (VERIFY against its docs for your version). You need a DNS name pointing to the server and ports 80 and 443 open on Traefik.

Without Traefik: Caddy#

Choose this if you have no Traefik yet and run one small server. Caddy is one small program that gets and renews certificates by itself.

Your caseWhat Caddy doesWhat you do
Public domainAutomatic Let's Encrypt certificatePoint a DNS A record at the server; open ports 80 and 443
Home only, no domaintls internal: Caddy's own local certificateTrust Caddy's root certificate on each device (below)

Caddyfile for Open WebUI (it listens on 127.0.0.1:3000 in this guide):

File contenttext
chat.example.com {
    reverse_proxy localhost:3000
}

Home-only variant (point the name chat.home.arpa at the server in your router, Pi-hole or hosts file):

File contenttext
chat.home.arpa {
    tls internal
    reverse_proxy localhost:3000
}

Run Caddy (Caddyfile in /etc/caddy/Caddyfile on Linux):

  • Linux: install from the official apt repository (see caddyserver.com/docs/install), save the Caddyfile, then sudo systemctl reload caddy.
  • Windows (PowerShell): winget install CaddyServer.Caddy, then caddy run --config Caddyfile in the folder with your Caddyfile.
  • Mac: brew install caddy, then caddy run --config Caddyfile.
  • Docker: add this service to the same compose file and put the Caddyfile in ~/docker_volumes/caddy/. Inside Docker, use reverse_proxy open-webui:8080 in the Caddyfile instead of localhost:3000.
File contentyaml
  caddy:
    image: caddy:2.11.7
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ~/docker_volumes/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - ~/docker_volumes/caddy/data:/data

Home-only: trust Caddy's root certificate. It is pki/authorities/local/root.crt in Caddy's data folder.

  • Linux: on the Caddy machine run sudo caddy trust. On other Linux devices copy root.crt to /usr/local/share/ca-certificates/ and run sudo update-ca-certificates.
  • Windows: caddy trust in an administrator PowerShell on the Caddy machine. On other devices double-click root.crt and install it into "Trusted Root Certification Authorities".
  • Mac: caddy trust on the Caddy machine. On other Macs add root.crt in Keychain Access and set it to "Always Trust".

Only Open WebUI gets a Caddy route. Never give SearXNG a public route. Set WEBUI_URL to the https address if you use that setting.

Check it worked: curl -I https://chat.example.com answers HTTP/2 200.

Security and testing#

  • Secrets: .env stays at chmod 600 and out of git (Linux, Mac). Rotate SEARXNG_SECRET if it leaks. Changing WEBUI_SECRET_KEY signs users out.
  • Keep SearXNG private: both ports are bound to 127.0.0.1. For remote use, prefer a VPN (WireGuard, Tailscale) to a port forward.
  • Strong admin password: the first account is the admin.
  • Update promptly: Open WebUI releases often carry security fixes.

Tests to run:

Run in terminalbash
docker compose ps
docker compose logs --tail=50 searxng
docker compose exec open-webui curl -s -o /dev/null -w "%{http_code}\n" "http://searxng:8080/search?q=test&format=json"

The last command should print 200. For HTTPS, curl -I https://chat.<your-domain> should return a normal response.

Troubleshooting#

ProblemSolution
403 Client Error: FORBIDDEN for url: http://searxng:8080/search?...format=jsonJSON is missing under search: formats. Add json, then docker compose restart searxng.
Web search is on, but "no sources" or the model ignores resultsCheck the per-chat toggle. Run the curl test from Step 3. Older versions (0.9.5 to 0.9.6) had search regressions, so stay on a current tag.
Open WebUI cannot reach http://localhost:8080Use http://searxng:8080/search. localhost inside the container is the container.
Works in the browser at 8081 but not from Open WebUIThe container name or network is wrong. Run the curl test with docker compose exec open-webui.
Empty results, engines suspendedUpstream engines blocked you. See "When engines block you".
Too many requests, or a blank page from SearXNGThe limiter is blocking. Check limiter.toml or set limiter: false.
can't register engine processor and SearXNG will not startSeen on SearXNG 2026.5.x (issue 6181). Mount your own settings.yml as in this guide, disable the named engine, or pull a newer tag.
Tool servers or terminals stopped working after v0.12.4ENABLE_DIRECT_INTEGRATIONS now defaults to off. Turn it on in Admin, Integrations.
Permission denied on core-configCheck that you own the folder and that Docker may share it (Docker Desktop file sharing).
Settings changed in .env but not in Open WebUIOpen WebUI stored them in its database. Change them in the Admin screen.

I could not collect verbatim log lines for the limiter and engine-block cases. Read your own logs and match on the engine name.

Known bugs#

  • SearXNG issue 6181 (May 2026): the image failed with can't register engine processor from 2026.5.15 on. A commenter fixed it by mounting settings.yml. The current tag is much newer.
  • Open WebUI web search issues 25908, 25585, 25618 (v0.9.x): no sources reached the model. The project has shipped many releases since.

Diagnostic commands:

Run in terminalbash
docker compose ps
docker compose logs --tail=100 searxng
docker compose logs --tail=100 open-webui
docker inspect search-stack-searxng-1 --format '{{.State.Status}}'

Updates and backups#

Update#

  1. Back up first (below).
  2. Read the release notes. Open WebUI says to back up before schema changes, and to update one version at a time if you are far behind.
  3. Edit the pinned tags in docker-compose.yml.
  4. Pull and recreate:
Run in terminalbash
docker compose pull
docker compose up -d
  1. Hard-refresh the browser (Ctrl+F5, or Cmd+Shift+R on Mac).

Pulling alone changes nothing: the running container keeps the old image until you recreate it. We pin tags because SearXNG's latest changes almost daily.

Backup script#

SearXNG is mostly stateless: only the two config files matter. Open WebUI's data folder holds your chats and settings.

Linux#

Run in terminalbash
cd ~/docker_volumes/search-stack
docker compose stop open-webui
tar czf backups/search-stack-$(date +%F).tgz -C ~/docker_volumes open-webui searxng search-stack/.env search-stack/docker-compose.yml
docker compose start open-webui

Schedule it with cron at 03:00 (crontab -e). Keep the backups folder private, because it contains .env.

Windows (PowerShell)#

Codepowershell
Set-Location "$HOME\docker_volumes\search-stack"
docker compose stop open-webui
Compress-Archive -Path "$HOME\docker_volumes\open-webui","$HOME\docker_volumes\searxng","$HOME\docker_volumes\search-stack\.env","$HOME\docker_volumes\search-stack\docker-compose.yml" -DestinationPath "backups\search-stack-$(Get-Date -Format yyyy-MM-dd).zip"
docker compose start open-webui

Schedule it with Task Scheduler.

Mac (Terminal)#

Same commands as Linux. Use launchd or cron for the schedule.

Restore test#

Do this once, now, so you know the backup works. It unpacks into a scratch folder and touches nothing live.

Run in terminalbash
mkdir -p /tmp/restore-test
tar xzf ~/docker_volumes/search-stack/backups/search-stack-$(date +%F).tgz -C /tmp/restore-test
ls /tmp/restore-test/open-webui /tmp/restore-test/searxng/core-config

You should see Open WebUI's data (including its database file, normally webui.db; VERIFY on your install) and your two SearXNG files. On Windows, run Expand-Archive into a temp folder and check the same names.

A real restore: docker compose down, put the folders back, docker compose up -d. After a version rollback, restore the backup taken before the upgrade.

Performance and resources#

  • SearXNG is light. A few searches a minute run fine on 1 to 2 CPU cores and a few hundred MB of RAM (our estimate, not measured; VERIFY with docker stats). The image is about 270 MB.
  • The real cost is the model. Every result you pass adds tokens, and your model has to read them.
  • Keep the result count at 3 to 5. Raise it only if your model has a large context window.
  • Keep concurrent requests modest. Fewer parallel calls means fewer CAPTCHAs.
  • Valkey holds only limiter counters.
  • Docker Desktop (Windows, Mac): if the model runs elsewhere, 4 GB for the VM is a sensible start. A model on the same machine needs far more.

Watch it live:

Run in terminalbash
docker stats --no-stream

FAQ#

Do I need an API key? No. SearXNG queries the engines without one.

Why do I get a 403 from SearXNG? JSON is not enabled. Add json to search: formats in settings.yml and restart the container.

Can I use the searxng-docker repo? Not for a new setup. It was archived on 2026-03-28. Use the compose file approach in this guide, which follows SearXNG's current docs.

Is it fully private? No. The engines still see your server's IP and the query text, and sites your model opens see the request. What you gain is that no single company gets a profile tied to your account.

Why pin tags instead of using latest? SearXNG's latest moves with almost every build, so a bad build reaches you without warning. A pinned tag lets you update on purpose and roll back.

Sources#

Checked against the official docs on 2026-10-26. Image tags confirmed against Docker Hub and the GitHub Container Registry. Nothing was run by DIY Lab Hub, and anything marked VERIFY could not be confirmed from an official page.