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 WebUI | v0.12.4, released 2026-10-25, image ghcr.io/open-webui/open-webui:v0.12.4 (official image) |
| SearXNG | No GitHub releases. Date tags on Docker Hub: docker.io/searxng/searxng:2026.10.25-54ca1d9cf (official image, newest tag on 2026-10-26) |
| Valkey | docker.io/valkey/valkey:9-alpine (the Redis-compatible store SearXNG's own compose file uses) |
| Ports | 3000 (Open WebUI) and 8081 (SearXNG screen), both bound to 127.0.0.1 |
| Official docs | SearXNG 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-dockerrepo. 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.
Three containers do the work:
| Container | Job | Who can reach it |
|---|---|---|
open-webui | Chat screen and the search caller | You, at 127.0.0.1:3000 |
searxng | Asks the engines, returns JSON | Open WebUI, plus you at 127.0.0.1:8081 for testing |
valkey | Remembers request counts for the limiter | Only SearXNG |
Before you start#
| Linux | Windows | Mac | |
|---|---|---|---|
| Docker | Docker Engine + Compose v2 | Docker Desktop, WSL2 backend | Docker Desktop |
| Shell | bash | PowerShell | Terminal |
| Memory | Plan for about 2 GB free for the two apps (our estimate) | Same, plus the WSL2 VM | Same, plus the Docker VM |
| Good to know | Add your user to the docker group | Turn on virtualization in the BIOS | Apple Silicon works with the multi-arch images (VERIFY on your chip) |
Check your tools:
docker --version docker compose version
Checklist:
- [ ] Docker runs (
docker psworks) - [ ] 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.
- Create the folders.
- Create
.envwith two random secrets. - Keep it private.
Linux#
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)#
$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#
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
use_default_settings: true
search:
formats:
- html
- json
server:
limiter: true
image_proxy: true
valkey:
url: valkey://valkey:6379/0
What each part does:
| Setting | Why |
|---|---|
use_default_settings: true | You only list what you change. Everything else stays default. |
formats: html, json | Open WebUI asks for JSON. SearXNG answers 403 Forbidden to any format not listed. |
limiter: true | Rate-limits requests and blocks bots. It needs Valkey. |
valkey: url | The host name valkey is the service name in the compose file below. |
secret_key | Not 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):
[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#
nano ~/docker_volumes/searxng/core-config/settings.yml nano ~/docker_volumes/searxng/core-config/limiter.toml
Windows (PowerShell)#
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)#
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.
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_SECREToverridessecret_keyfrom the environment. This is documented on SearXNG's server settings page.- The Valkey
--saveline 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#
docker compose config --quiet docker compose up -d docker compose ps
Windows (PowerShell)#
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:
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#
- Open
http://localhost:3000and create the first account. It becomes the admin. Use a strong password. - Go to Settings, Admin, Connections and check that your model backend is connected.
- Go to Settings, Admin, Web Search. Confirm:
- Web Search is on
- Engine is searxng
- Query URL is
http://searxng:8080/search
- Open a new chat. Click Integrations (next to the plus button) and switch Web Search on.
- Ask something current, for example "What is the latest stable Linux kernel?"
- 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
localhostin the query URL. Inside the container,localhostis the container itself. Use the service namesearxng.
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:
| Part | Value |
|---|---|
| Query URL | http://searxng:8080/search |
| What Open WebUI adds | q (the query), format=json, and the language |
| Language field | Default 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:
- Open
http://localhost:8081/preferencesand switch off engines that show errors. - Keep a mix of engines (for example DuckDuckGo, Brave, Wikipedia, Startpage; VERIFY the names in your instance).
- Lower Concurrent Requests and the result count in Open WebUI.
- Read the log:
docker compose logs -f searxng. - 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#
| Who | What they see |
|---|---|
| The search engines | The query text and your server's IP address. Not your browser or your identity. |
| Sites your model reads | The page fetch request from your server |
| SearXNG itself | Does not track or profile users |
| Your model | The 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:
docker network create proxy
Add this to the open-webui service, and remove its ports: entry:
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:
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 case | What Caddy does | What you do |
|---|---|---|
| Public domain | Automatic Let's Encrypt certificate | Point a DNS A record at the server; open ports 80 and 443 |
| Home only, no domain | tls internal: Caddy's own local certificate | Trust Caddy's root certificate on each device (below) |
Caddyfile for Open WebUI (it listens on 127.0.0.1:3000 in this guide):
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):
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, thencaddy run --config Caddyfilein the folder with your Caddyfile. - Mac:
brew install caddy, thencaddy run --config Caddyfile. - Docker: add this service to the same compose file and put the Caddyfile in
~/docker_volumes/caddy/. Inside Docker, usereverse_proxy open-webui:8080in the Caddyfile instead oflocalhost:3000.
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 copyroot.crtto/usr/local/share/ca-certificates/and runsudo update-ca-certificates. - Windows:
caddy trustin an administrator PowerShell on the Caddy machine. On other devices double-clickroot.crtand install it into "Trusted Root Certification Authorities". - Mac:
caddy truston the Caddy machine. On other Macs addroot.crtin 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:
.envstays atchmod 600and out of git (Linux, Mac). RotateSEARXNG_SECRETif it leaks. ChangingWEBUI_SECRET_KEYsigns 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:
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#
| Problem | Solution |
|---|---|
403 Client Error: FORBIDDEN for url: http://searxng:8080/search?...format=json | JSON is missing under search: formats. Add json, then docker compose restart searxng. |
| Web search is on, but "no sources" or the model ignores results | Check 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:8080 | Use http://searxng:8080/search. localhost inside the container is the container. |
| Works in the browser at 8081 but not from Open WebUI | The container name or network is wrong. Run the curl test with docker compose exec open-webui. |
| Empty results, engines suspended | Upstream engines blocked you. See "When engines block you". |
| Too many requests, or a blank page from SearXNG | The limiter is blocking. Check limiter.toml or set limiter: false. |
can't register engine processor and SearXNG will not start | Seen 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.4 | ENABLE_DIRECT_INTEGRATIONS now defaults to off. Turn it on in Admin, Integrations. |
Permission denied on core-config | Check that you own the folder and that Docker may share it (Docker Desktop file sharing). |
Settings changed in .env but not in Open WebUI | Open 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 processorfrom 2026.5.15 on. A commenter fixed it by mountingsettings.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:
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#
- Back up first (below).
- 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.
- Edit the pinned tags in
docker-compose.yml. - Pull and recreate:
docker compose pull docker compose up -d
- 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#
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)#
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.
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:
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#
- SearXNG: Installation container (docs build 2026.10.25, read 2026-10-26)
- SearXNG: server settings and valkey settings (build 2026.10.25, read 2026-10-26)
- SearXNG: Limiter (build 2026.10.25, read 2026-10-26)
- SearXNG official compose file (master, read 2026-10-26)
- SearXNG image tags on Docker Hub (tag confirmed through the Docker Hub API, 2026-10-26)
- Open WebUI: SearXNG web search provider (page undated; its install steps are outdated, its settings were used)
- Open WebUI v0.12.4 release (2026-10-25)
- searxng-docker (archived) (archived 2026-03-28, not used)
- Caddy docs: automatic HTTPS, reverse_proxy, install - Caddy v2.11.7, image tag
caddy:2.11.7confirmed on Docker Hub, read on 2026-10-26
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.