How to Run Your Own Private SearXNG with Docker
Run SearXNG on your VPS with Docker Compose. Configure settings.yml, limiter and nginx TLS, then call the JSON search API from your scripts without API keys.
Wetin you dey build
Self-hosting SearXNG gives you private search engine wey dey run for your own server. SearXNG na metasearch engine: e takes your query, asks other engines like Google, Bing, DuckDuckGo and Wikipedia, then e joins wetin come back into one result page. E no build profile and e no set tracking cookie, because na only your own machine dey keep your query.
The stack small. Two containers, one settings file, and one reverse proxy. The real decision na whether the instance go private, meaning say na only you and your own scripts fit reach am, or public, meaning anybody for internet fit query am. That choice changes the security settings, so make you decide am before you type anything. The default answer na private.
Another reason to run one dey. A SearXNG instance speaks JSON, so any script or AI agent wey you write go get search API wey you own, without key, per-query billing or quota mail.
Install SearXNG with Docker Compose
The project dey publish one container image and one Compose file. Pull both go one fresh Ubuntu 24.04 server wey already get Docker Engine and the Compose plugin. If Docker new to you, start with Docker Compose basics for VPS and come back.
sudo install -d -o "$USER" -g "$USER" -m 750 /opt/searxng
cd /opt/searxng
mkdir -p core-config
curl -fsSL \
-O https://raw.githubusercontent.com/searxng/searxng/master/container/docker-compose.yml \
-O https://raw.githubusercontent.com/searxng/searxng/master/container/.env.example
cp -i .env.example .envThe Compose file dey define two services. core na SearXNG itself, and valkey na in-memory data store wey dem dey use for rate limiting and short-lived state. E mount ./core-config/ inside /etc/searxng/ for the container, so everything wey you configure dey inside that one directory for the host.
Now edit .env. Every line for the example wey dem ship dey commented out, na why the container dey start for port 8080 on every address. Uncomment and set these three.
SEARXNG_VERSION=latest
SEARXNG_HOST=127.0.0.1
SEARXNG_PORT=8080SEARXNG_HOST=127.0.0.1 na the important one. E make the published port 127.0.0.1:8080:8080 instead of [::]:8080:8080, so the container go answer only on the loopback address and internet no fit reach am directly. If you skip this, the container go expose as e start, because published Docker port dey enter before your firewall rules. Read this trap well: published Docker ports dey bypass ufw.
SEARXNG_VERSION=latest dey okay while you dey learn. For server wey matter to you, pin the tag. As of July 2026, the release tags dey use dates and look like 2026.3.25-541c6c3cb, so pinned deployment go upgrade when you decide, no be when the registry change by itself.
settings.yml: di parts wey matter
Create core-config/settings.yml before you start am for the first time. use_default_settings: true tells SearXNG make e load the defaults wey e come with, then apply only the keys wey you write. This keeps your file short and makes upgrades wey add new options safer.
Generate the secret first, because the value go enter the file directly.
openssl rand -hex 32use_default_settings: true
general:
instance_name: "search.example.com"
server:
base_url: "https://search.example.com/"
secret_key: "paste-the-openssl-output-here"
limiter: false
public_instance: false
image_proxy: true
valkey:
url: valkey://valkey:6379/0
search:
safe_search: 0
autocomplete: "duckduckgo"
formats:
- html
- jsonsecret_key dey sign session and token data. The default wey come with am na the exact string ultrasecretkey. If you leave am like that, anybody wey know the default fit forge those tokens. Replace am once, then leave am. If you change am later, all saved preferences go disappear.
base_url must be the public HTTPS address, with the trailing slash. Na this address SearXNG writes inside the links wey e render. If you leave am pointing to localhost, the "next page" link for remote browser go point to the reader own machine and e go fail.
formats decides the output types wey the web endpoint go produce. json no dey inside the default list, so a JSON request go return 403 until you add am. image_proxy: true routes result thumbnails through your server, so the sites wey host those images no go see your visitors' addresses.
The valkey.url uses hostname valkey because na the service name inside the Compose file. Compose puts both containers for one network where service names resolve. If you point am to localhost, the limiter go fail, because inside the core container, localhost na that same container.
The secret dey inside a plain file, so protect the directory around am instead of the file itself. chmod 750 /opt/searxng keeps other host users out. No tighten core-config/settings.yml to mode 600: the container dey run as its own unprivileged user, and if e no fit read the file, SearXNG no go start at all.
Start the stack and check am.
cd /opt/searxng
docker compose up -d
docker compose ps
curl -I http://127.0.0.1:8080/docker compose ps suppose show both containers for state running. The curl suppose answer HTTP/1.1 200 OK. If e no answer anything, read docker compose logs core, because YAML mistake inside settings.yml go show there as a parse error wey name the line.
Put am behind nginx with TLS
The container dey listen for loopback only, so na nginx dey make am reachable. Na nginx still dey add transport layer security (TLS). Write /etc/nginx/sites-available/searxng.
server {
listen 80;
server_name search.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}sudo ln -s /etc/nginx/sites-available/searxng /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d search.example.comnginx -t go print syntax is ok and test is successful before you reload. Certbot go rewrite the same file to listen on 443 with a certificate, and e go add redirect from port 80. DNS record for search.example.com must don point to this server, because certificate authority dey prove ownership by fetching file through HTTP. The full walkthrough, including renewal, dey for Certbot and nginx guide for Ubuntu 24.04.
The two forwarding headers no be decoration. Without X-Forwarded-For and X-Real-IP, every request wey dey enter SearXNG go carry the proxy address. So rate limiter go see one client dey make all the traffic, and e no fit tell visitors apart.
Why scripts and agents want a JSON search API
With json for formats, na the same endpoint wey dey render the page dey return structured data.
curl -s 'http://127.0.0.1:8080/search?q=wireguard+mtu&format=json' \
| jq -r '.results[0:5][] | .url'You go get one object with one results array. Each entry get url, title, content and the engine wey supply am, together with answers, infoboxes and suggestions. This one enough to feed summariser, link checker, or research loop.
This one matter for anything wey be agent. Language model get training cutoff, so e need live search to answer questions about wetin dey happen now. Commercial search APIs dey charge for each query and dey rate-limit heavily. Local instance na one container for server wey you already dey pay for, and queries no dey leave am. If you dey connect tools to model, na the same reason make people dey run MCP servers for VPS, where search tool usually be the first one people add.
Follow two rules when you dey use API. Keep the instance private. Bind the API side to loopback address or private network, and allow only your own hosts to reach am. Then send queries gently. SearXNG dey forward your request to real search engines, so script wey dey run one hundred queries per second dey ask Google to block your server.
The limiter, and wetin go change for public instance
The limiter na SearXNG bot defence. E dey monitor request headers, addresses and request rates, and e dey drop network traffic wey look automated. E need Valkey to store that state, na why Compose file ship am.
For private instance, keep limiter: false. Your own scripts na automated traffic by definition, so limiter go block exactly the JSON calls wey you build the instance for. Access control na reverse proxy work instead: an allow and deny pair for nginx location, HTTP basic authentication, or firewall wey only allow your other servers.
If you publish the instance for other people, turn on both switches.
server:
limiter: true
public_instance: trueMore detailed control dey inside core-config/limiter.toml, wey container dey read from /etc/searxng/limiter.toml. Write only the keys wey you want change. If proxy dey in front, you must declare the proxy, or limiter go treat your nginx address as the one abusive client.
[botdetection]
trusted_proxies = [
'127.0.0.0/8',
'::1',
]
[botdetection.ip_limit]
link_token = truelink_token = true make SearXNG issue a token wey only real browser session fit fetch, and this dey stop most simple scrapers. Expect public instance to attract dem within days. Expect engine errors too, because the more network traffic you forward, the sooner upstream engines go start returning CAPTCHAs to your server address. Public SearXNG instance na ongoing job. Private one no be, na why e dey among most short lists of things wey worth self-hosting for 2026.
Why searches return nothing
Open /stats for your instance. E dey list every engine with e error rate and response time. Na the first place to check when results no plenty.
If engine show "Access denied" or "CAPTCHA" errors, e don block your server address. This one common for addresses wey dey data centre ranges, because search engines assume say scrapers dey use them. SearXNG go then suspend the engine wey dey fail for some time instead of trying am again. So one blocked engine fit quietly comot from your results. Disable am for settings.yml or accept the loss. The other engines go still answer.
If every engine fail at the same time, the container no get working outbound name resolution or route to the internet. Test am from inside the container.
docker compose exec core wget -qO- https://duckduckgo.com > /dev/null && echo okFAQ
SearXNG dey make my searches anonymous?
E dey hide who you be from the engines wey e dey query, because dem dey see your server make the request instead of your browser. E no dey hide the query from your server, and e no dey hide your server from dem. For instance wey na one user dey use, all traffic from that address na your own, so the address itself become the identifier. TLS certificate dey protect traffic between your browser and your instance.
Why JSON request dey return 403 Forbidden?
Two things fit cause am, and both na configuration matter. Either json dey miss from the formats list under search: for settings.yml, wey be the default state, or limiter dey on and e don classify your script as bot. Add the format first, restart with docker compose restart core, then try again. If e still fail, set limiter: false and control access for the reverse proxy instead.
I need Valkey container if I keep limiter off?
Make e keep running. SearXNG fit work without am, but you no fit turn limiter on later without am, and e still dey hold other short-lived state. The container small and e only dey store cached data, so removing am save very little and e make you lose the option.
How I fit update SearXNG?
Run docker compose pull then docker compose up -d inside /opt/searxng. Compose dey recreate any container wey image don change and e dey leave your core-config/ directory untouched, so settings.yml still dey. Because use_default_settings: true dey merge your keys over the shipped defaults, options wey upstream add go come with sensible values instead of breaking the file.
Several people fit share one instance?
Yes, and na this situation you go turn limiter on and set public_instance: true. Each visitor own browser dey store preferences, so you no get accounts to manage. Monitor /stats for one week after you open am, because upstream engines go start rejecting your server long before you notice say results dey miss.