Background
After getting my somewhat self-hosted LLM up and running, one of the first things I missed was web search.
After some research and trying out different things, I landed on SearXNG running as a Docker container on the same server. It was easy to get up and running, but I immediately had some qualms.
My server IP was being sent to all these search services, which 1) is a privacy concern for me and 2) since SearXNG is a meta-search engine, I would probably start getting blocked. Using a VPN would get me around the privacy concern at least, and for getting blocked, still likely on a VPN - but at least I could change my IP in a pinch.
Luckily for me, I had done something like this before and I had documented it. I adapted my config from that and spun it up.
Bonus here: I now have my own meta-search engine behind a VPN that I can use for normal search-engine purposes outside of Open WebUI.
References
Agentic Search & URL Fetching / Open WebUI
Web Search / Open WebUI
SearXNG / Open WebUI
Gotchas
Using traditional RAG mode instead of Native mode
Initially I was trying to configure with the legacy options that I didn’t realize were legacy - apparently Open WebUI’s agentic search is the new default. Being somewhat naive and skimming the documentation, I thought “agentic” search was allowing the models to use built-into-the-model web search tools, not the search engine set up in Open WebUI.
I was just plain wrong on this. If I understand it correctly now, this allows the model itself to call certain functions when it deems it appropriate, instead of injecting search results as RAG. Something like that, it’s laid out here. But I was stuck on the SearXNG configuration page and not understanding why it wasn’t working. These two Github issues helped: issue 25585 and issue 25038
Configuring the correct search URL
In the web UI, the correct URL for me was this:
http://gluetun-searxng:8888/search?q=<query>
gluetun-searxngis the name of the Docker container exposing the port.8888is the internal docker port that is exposed. It needs to be on the same Docker network as the Open WebUI container.
I ran into a couple issues here.
First, I wasn’t able to figure out what hostname to use - host.docker.internal wasn’t working, localhost or 127.0.0.1 wasn’t working, the SearXNG container name wasn’t working.
I was running it with localhost for a minute, and I thought it was working, because curl’ing inside the container returned a result. But I kept getting “incorrect mime-type text/html” errors - when I figured it out, I realized it’s because I was curl’ing the Open WebUI container, and returning the HTML of the web page, not the JSON result that SearXNG should have been returning. Eventually I figured out I needed to use the hostname.
Second was the port. I never gave much thought to the backend port my Docker containers use, because I never really interact with that port - only the one that is exposed to the localhost of the parent machine. I had both SearXNG and Open WebUI running on port 8080 internally, so I was getting a conflict with that port and needed to change SearXNG’s default internal port.
Configuration
Steps
- Generate Mullvad Wireguard key and download config files
- Create Docker compose file and .env file with Mullvad information
- Create Nginx reverse proxy entry
- Spin everything up
- Edit Open WebUI settings to enable Web Search and Agentic mode
SearXNG
Compose stack
This uses an adapted compose file from SearXNG’s documentation, as well as my own Gluetun compose file that I used previously. Hopefully when SearXNG updates it doesn’t cause any breakage - I imagine I will need to adapt this when the default Compose template updates upstream.
name: searxng
services:
gluetun:
image: qmcgaw/gluetun
container_name: searxng-gluetun
restart: unless-stopped
env_file:
- .env
ports:
- 127.0.0.1:${WEBUI_PORT}:${SEARXNG_PORT}
volumes:
- ${GLUETUN_CONFIG_DIR}:/gluetun
networks:
- npm_network
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- NET_ADMIN
core:
container_name: searxng-core
image: docker.io/searxng/searxng:latest
# image: docker.io/searxng/searxng:${SEARXNG_VERSION:-latest}
restart: unless-stopped
# restart: always
# ports:
# - 127.0.0.1:${WEBUI_PORT}:8080
# - ${SEARXNG_HOST:+${SEARXNG_HOST}:}${SEARXNG_PORT:-8080}:${SEARXNG_PORT:-8080}
env_file:
- .env
volumes:
- ${SEARXNG_CONFIG_DIR}:/etc/searxng/:Z
- ${CACHE_DIR}:/var/cache/searxng/
# - core-data:/var/cache/searxng/
network_mode: service:gluetun
depends_on:
gluetun:
condition: service_healthy
valkey:
container_name: searxng-valkey
image: docker.io/valkey/valkey:9-alpine
command: valkey-server --save 30 1 --loglevel warning
restart: unless-stopped
env_file:
- .env
# restart: always
volumes:
- ${VALKEY_DIR}:/data/
# - valkey-data:/data/
networks:
- npm_network
# volumes:
# core-data:
# valkey-data:
networks:
npm_network:
external: true.env file
WEBUI_PORT=8888
SEARXNG_PORT=8888
GLUETUN_CONFIG_DIR=./gluetun_config
SEARXNG_CONFIG_DIR=./config
CACHE_DIR=./cache
VALKEY_DIR=./valkey
VPN_SERVICE_PROVIDER=mullvad
VPN_TYPE=wireguard
WIREGUARD_PRIVATE_KEY=<private-key>
WIREGUARD_ADDRESSES=<ip-address>
SERVER_COUNTRIES=<country>
Nginx reverse proxy
server {
server_name searxng.domain.com;
location / {
proxy_pass http://127.0.0.1:8888;
}
listen 443 ssl;
include snippets/domain-com.conf;
include snippets/ssl-params.conf;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_read_timeout 900s;
}
Open WebUI
- Admin Panel → Settings → Web Search
- Enable globally
- Select SearXNG as engine
- Set SearXNG query URL to
http://container-name:8888/search?q=<query> - Adjust other settings as desired here, such as result count and concurrency
- Admin Panel → Models - > specific model
- Enable Web Search under Capabilities, Default Features, and Builtin Tools
- Under Advanced Params, change Function Calling to Native
Conclusion
I’ve been using this for a few days now with OpenAI’s gpt-oss-120b, and it’s been impressive. I had to cough up $15 to HuggingFace, but I’ve only spent like 20 cents of it, and I was hitting it pretty hard there for a minute working on my next project.
I’m taking a bit of a step back from it for now I think, but I will be back. It’s cool to have a near-frontier model ready for me if a simple search isn’t enough.
The next logical step would be to spin up a dedicated pay-as-you-go instance with a service such as RunPod or Vast.ai, but I’d like to do some more research, model testing, playing around with advanced parameters, and figuring out all the different Open WebUI features before paying up for a model deployment. There’s plenty I can do with Inference Providers for the time being.
EOF