GuidesFiles, photos and homePrivate search with SearXNG

Run your own private search engine with SearXNG and rootless Podman

SearXNG on Debian 13 in rootless Podman behind Caddy, a private search engine for you and your household that asks several engines at once, open without a password on your VPN and behind one everywhere else.

Tested on SearXNG 2026.10.2 on Debian 13 (trixie) on a Melonslab server Updated October 2, 2026

Recommended server for this guide

VC-S Micro · 2 vCPU · 8 GB Memory · 250 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€7.99/mo

Deploy now
On this page

What you will set up

SearXNG is a metasearch engine. When you search, it asks several search engines at once, such as DuckDuckGo and Wikipedia, merges their answers and shows them on one page, without ads or a profile of you. The engines get the question from your server, not from your browser, so they see the server's address and cannot set cookies on your devices.

SearXNG is made by the SearXNG project, a volunteer community on GitHub. No company stands behind it, and the project names no country. It is open source under the GNU Affero General Public License 3.0. It sends no telemetry. By design it sends each search to the engines you switch on, from the server's address, and by default it sends what you type in the search box to DuckDuckGo for suggestions. When it starts, it downloads the ClearURLs list of tracking parameters from cdn.jsdelivr.net, which carries nothing about you. Step 9 covers what each side sees, and how to switch suggestions off.

Here it runs from its official image, in a Podman pod under a user of its own called searxng, with Valkey, a small database its rate limiter needs, behind Caddy from the Podman guide.

It is for you and your household, not for the internet. A public instance attracts bots, and the engines then answer the server's address with CAPTCHAs or blocks, for everyone who uses it. So Caddy lets in your WireGuard VPN and your home without a password, and asks everyone else for one.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:

  • SearXNG 2026.10.2 answered through Caddy with a Let's Encrypt certificate. A device on the VPN, over IPv4 and IPv6, and the allowed home address got in without a password. Any other address got "401" until it logged in.
  • SearXNG saw each visitor's real address, not Caddy's, and ignored a forged X-Forwarded-For header. Its limiter answered "429" after 15 searches in 20 seconds from one address.
  • Pages of image, video and news results loaded everything from the server itself: the browser contacted no other host.
  • 34 searches over half an hour showed which engines answer this server and which block it (step 7).
  • Chromium found the search engine by itself and made it its default.
  • An update to a newer image installed itself, and everything came back by itself after a reboot.

SearXNG used about 150 MB of memory, and Valkey 8 MB.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running;
  • ufw as in the security guide, steps 1 to 4;
  • to search from your phone or laptop away from home, WireGuard as in the WireGuard guide, with the tunnel address 10.8.0.1;
  • an A record and an AAAA record for search.example.com pointing at your server.

The examples use search.example.com for SearXNG, 203.0.113.10 for the server's IPv4 address, 2001:db8:1f::/64 for its IPv6 range and 198.51.100.7 for your home's address. Replace them throughout. ifconfig.co, opened at home with the VPN off, shows your home's address.

1. Create the user

As root:

useradd -m -s /bin/bash searxng
loginctl enable-linger searxng

SearXNG logs the full address of an engine request that fails, and that address includes the search. Keep those lines out of the server's journal with a filter for this user's services:

mkdir -p /etc/systemd/system/user@$(id -u searxng).service.d
cat > /etc/systemd/system/user@$(id -u searxng).service.d/log-filter.conf <<EOF
[Service]
# A failed engine request is logged with its full address, search included.
LogFilterPatterns=~HTTP Request failed
EOF
systemctl daemon-reload
systemctl restart user@$(id -u searxng).service

Then switch to the user:

machinectl shell searxng@

Everything up to step 5 runs as searxng.

2. Write the settings

mkdir -p ~/config ~/.config/containers/systemd
openssl rand -hex 32

The last line prints a random key, which SearXNG uses to sign its links. Create ~/config/settings.yml, with the key in place of SECRET_KEY:

# Only the settings that differ from SearXNG's defaults.
use_default_settings: true

server:
  base_url: https://search.example.com/
  secret_key: "SECRET_KEY"
  limiter: true
  image_proxy: true

valkey:
  url: valkey://localhost:6379/0
chmod 600 ~/config/settings.yml

What the settings do:

  • use_default_settings: true keeps every default and changes only what the file lists, so updates bring new defaults with them.
  • base_url is the address SearXNG uses in its own links, such as the search engine description that browsers read (step 8).
  • limiter: true switches on SearXNG's bot detection and rate limit. It needs Valkey, at localhost inside the pod.
  • image_proxy: true sends result images through your server, so the sites that host them never see your devices' addresses.

The limiter has to know your visitors' real addresses. Caddy passes them on, and inside the pod Caddy's connections come from the server's own IPv4 address, so that is the proxy to trust. Create ~/config/limiter.toml:

[botdetection]
# Caddy's connections arrive from the server's own IPv4 address.
trusted_proxies = ['203.0.113.10/32']

3. Describe the pod

Create ~/.config/containers/systemd/searxng.pod:

[Unit]
Description=SearXNG pod

[Pod]
PodName=searxng
# Only Caddy, on this server, can reach SearXNG: the port is not open to the internet.
PublishPort=127.0.0.1:8101:8080

[Install]
WantedBy=default.target

4. Add Valkey and SearXNG

These follow the files in SearXNG's own container setup. Create ~/.config/containers/systemd/searxng-valkey.container:

[Unit]
Description=Valkey for SearXNG's limiter

[Container]
ContainerName=searxng-valkey
Pod=searxng.pod
Image=docker.io/valkey/valkey:9-alpine
Exec=valkey-server --save 30 1 --loglevel warning --bind 127.0.0.1 ::1
Volume=searxng-valkey:/data
AutoUpdate=registry

[Service]
Restart=always

And ~/.config/containers/systemd/searxng-core.container:

[Unit]
Description=SearXNG
Requires=searxng-valkey.service
After=searxng-valkey.service

[Container]
ContainerName=searxng-core
Pod=searxng.pod
Image=docker.io/searxng/searxng:latest
Volume=%h/config:/etc/searxng
Volume=searxng-data:/var/cache/searxng
# Keep the files in ~/config yours, so you can edit them.
Environment=FORCE_OWNERSHIP=false
AutoUpdate=registry

[Service]
Restart=always

Valkey listens only inside the pod. Without FORCE_OWNERSHIP=false, SearXNG would hand ~/config to a user inside the container, and you could no longer edit your own settings.

Start the pod, and switch on Podman's daily updates:

systemctl --user daemon-reload
systemctl --user start searxng-pod
systemctl --user enable --now podman-auto-update.timer
podman logs searxng-core

The log ends with Started worker-1. Two lines above it say ahmia: can't register engine and torch: can't register engine: those two search Tor's hidden services and need a Tor proxy, so they stay off. The warning that /etc/searxng is not owned by searxng:searxng comes from FORCE_OWNERSHIP=false, and is expected.

5. Put Caddy in front, for your household only

Choose who gets in. The best way to search from a browser is with no login at all: the browser's address bar sends each search, and suggestions as you type, straight to the server. So addresses you trust get in without a password, and every other address must log in:

  • 10.8.0.0/24 and 2001:db8:1f::/64 are your WireGuard devices. They get addresses from these ranges in the tunnel, as in the WireGuard guide.
  • 198.51.100.7 is your home. Add your home's IPv6 range too, if it has one, as devices prefer IPv6.
  • Everyone else gets a login box, so you can still search from a phone that is off the VPN.

Go back to root with exit, and switch to machinectl shell caddy@. Make a password hash for the login, typing the password twice:

podman exec -it caddy caddy hash-password

It prints a line that starts with $2a$14$. Add this block at the end of ~/Caddyfile, with that line in place of HASH:

search.example.com {
    # Your VPN and your home get in directly; everyone else must log in.
    @outside not remote_ip 10.8.0.0/24 2001:db8:1f::/64 198.51.100.7
    basic_auth @outside {
        family HASH
    }
    reverse_proxy 127.0.0.1:8101
}

family is the user name. Restart Caddy:

systemctl --user restart caddy

If your home's address changes, edit the line and restart Caddy again.

6. Check what SearXNG sees

From home or the VPN, open https://search.example.com. It opens without a password. Search for ip: the first answer reads "Your IP is:" followed by your own address. If it shows 203.0.113.10 instead, the trusted_proxies line from step 2 does not match the server's address.

From any other network, such as a phone with the VPN and Wi-Fi off, the browser asks for a user name and password first.

The limiter allows 15 searches in 20 seconds from one address, which a household never reaches by hand. It also turns away anything that does not look like a browser. A search with curl gets a "429" or is sent back to the front page, while browsers work as normal.

7. Choose the engines

From a server, the engines see a datacenter address, and some of them block it. SearXNG shows the engines that failed above the results, with the reason, and https://search.example.com/stats lists each engine's reliability since the last restart. Over 34 searches across half an hour on the test server:

EngineAnsweredWhat happened otherwise
Seznam34 of 34
Yahoo33 of 34one "parsing error"
DuckDuckGo32 of 34two CAPTCHAs, after which it answered again
Mwmbl17 of 24timeouts
Bing16 of 24no results, without an error
Yep15 of 24"access denied"
Google14 of 24"CAPTCHA" after about 14 searches, then suspended for an hour
Brave0 of 24"too many requests" every time

Qwant answered with a CAPTCHA and PrivacyWall with "access denied" on the first search, so they were left out. Startpage and Mojeek are switched off in SearXNG itself, because both now ask for a proof-of-work CAPTCHA. For images, DuckDuckGo refused every request, and for news Reuters did, as did Vimeo for videos.

Of the engines on by default, Brave is blocked from this server, and several are Google's: Google's custom search, its images, news, videos and Scholar, and YouTube. Seznam and Yahoo worked almost every time but are off by default. As searxng, add this to the end of ~/config/settings.yml:

engines:
  # Blocked from this server: Brave answers every search with "too many requests".
  - name: brave
    disabled: true
  - name: brave.images
    disabled: true
  - name: brave.videos
    disabled: true
  - name: brave.news
    disabled: true
  # Google's engines are on by default. Switch them off to keep your searches away from Google.
  - name: google cse
    disabled: true
  - name: google cse images
    disabled: true
  - name: google videos
    disabled: true
  - name: google news
    disabled: true
  - name: google scholar
    disabled: true
  - name: youtube
    disabled: true
  # Refused every request from this server.
  - name: duckduckgo images
    disabled: true
  - name: reuters
    disabled: true
  - name: vimeo
    disabled: true
  # Worked from this server in every test.
  - name: yahoo
    disabled: false
  - name: seznam
    disabled: false

And restart the pod:

systemctl --user restart searxng-pod

Web searches now go to DuckDuckGo, Yahoo and Seznam, with Wikipedia and Wikidata for facts. Images come from Bing, Pinterest and Wikimedia Commons, videos from Bing, DuckDuckGo, Dailymotion, SepiaSearch and Wikimedia Commons, and news from Bing, DuckDuckGo and Wikinews. Engines treat each server's address differently, so check /stats after a week, and switch off any engine that keeps failing in the same way. An engine that is switched off still answers its shortcut: !bi searches Bing only, and !mwm Mwmbl only.

SearXNG describes itself to browsers with OpenSearch, so a browser can add it with its suggestions. You only need to search on the site once first.

Chromium and Chrome: open chrome://settings/searchEngines. SearXNG is listed under Inactive shortcuts: choose Activate. It moves to Site search. There, open More actions (the three dots) next to it, and choose Make default. Searches from the address bar now go to your server, and it shows SearXNG (Default).

Firefox: after a search on the site, Firefox offers to add SearXNG in the address bar's list of search engines. Then choose it under Settings, Search, Default Search Engine. We did not test this in Firefox.

Phones: a phone gets in without a password while it is on the VPN, so set WireGuard to stay connected. In Chrome on Android, search on the site once, then choose SearXNG under Settings, Search engine. Firefox on Android can add it under Settings, Search, with https://search.example.com/search?q=%s as the address. Safari on an iPhone only offers its own list of engines, so use another browser there, or put the site on the home screen. We did not test these steps on a phone.

9. Know what each side sees

  • The engines see your server's address, and the words you search for. They do not see your address, your browser or its cookies. Everything the engines know about your searches, they know about one server, which your whole household shares.
  • Sites in the results see your address only when you open a link. Images in the results come through your server, so showing them reveals nothing.
  • Suggestions as you type come from DuckDuckGo, through your server. To switch them off in a browser, open Preferences on the site and set Autocomplete to -. The choice is stored in that browser only.
  • Your server keeps almost no record of searches with these settings. Caddy logs no visits, only requests it could not pass on, such as a search while SearXNG restarts for an update: that line holds the visitor's address and the search. SearXNG logs failed engine requests without the search (step 1), and Valkey holds the limiter's counters under scrambled keys, which expired within 30 minutes in our test. Whoever has root on the server could switch logging on, so this setup is private for the people who trust its owner.

10. Keep it up to date

SearXNG publishes a new image several times a week, sometimes several a day, often with fixes for engines whose sites changed. The timer from step 4 checks once a day, and restarts the pod with the new image when there is one. Each update leaves the old image behind, 271 MB each time. As searxng, have the update remove it:

mkdir -p ~/.config/systemd/user/podman-auto-update.service.d
cat > ~/.config/systemd/user/podman-auto-update.service.d/prune.conf <<EOF
[Service]
# Remove the images that an update replaced.
ExecStartPost=/usr/bin/podman image prune -f
EOF
systemctl --user daemon-reload

To update right away, and see what changed:

systemctl --user start podman-auto-update.service
podman exec searxng-core sh -c 'echo $__SEARXNG_VERSION'

The last line prints the version, such as 2026.10.2-19ffbcd30, the build date and the code it was built from.

11. Back up

The settings are all that matters: Valkey only holds counters, and SearXNG keeps no history. As searxng:

tar czf ~/searxng-config.tar.gz -C ~ config

Copy the file to another machine. It holds the secret key, so store it securely.

Troubleshooting

"Your IP is" shows the server's address, or everyone shares one limit. The trusted_proxies line in ~/config/limiter.toml does not name the server's IPv4 address. Correct it, and run systemctl --user restart searxng-pod.

The log says X-Forwarded-For nor X-Real-IP header is set!. Something reached SearXNG on port 8101 without going through Caddy, such as a curl on the server itself. Requests through Caddy always carry the header.

An engine shows "too many requests", "CAPTCHA" or "access denied" above the results. It is blocking your server's address. SearXNG pauses it for a while by itself. If it keeps happening, switch it off as in step 7.

A search with curl gets "429", or a script's search lands on the front page. The limiter has taken it for a bot, which is what it is for. Browsers are not affected.

Your phone asks for a password at home. It is using its mobile connection, or your home's address has changed. Check the address on ifconfig.co, and correct the remote_ip line from step 5.

Run it on your own server

VC-S Micro

€7.99/mo

vCPU
2
Memory
8 GB
Storage
250 GB
Transfer
10 TB
Standard
HDD · RAID 10
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides