Completing the private search stack: Tor proxying, systemd service setup, browser hardening, and an honest accounting of what it all actually protects.
This is Part II of a two-part series. Part I — "The Search Box Is a Confession Booth" — covers the philosophy of private search, what SearXNG is and isn't, and walks through installation and initial configuration. If you haven't read it, start there.
At the end of Part I you had a working SearXNG instance running on localhost — a self-hosted metasearch engine querying Google, Bing, and a dozen other engines simultaneously, with no account, no company, and no privacy policy standing between you and your results. That's meaningful. It's also incomplete.
The outbound search queries — the requests SearXNG makes to upstream engines on your behalf — are still leaving your machine carrying your IP address. A determined observer watching your network traffic would know which search engines you're hitting, even if they couldn't read the queries themselves. And the moment you click a result, your browser makes a direct connection to the target site, bypassing SearXNG entirely.
This article closes both gaps. By the end of it, your search queries will exit through Tor. Your browser will route clicked results through the same Tor tunnel. The whole stack will start automatically at boot and shut down cleanly without your involvement. And you'll have an honest accounting of what it all protects — and what it doesn't.
Configuring Tor Proxying
Installing and Starting Tor
On Debian and Ubuntu-based systems, Tor installs cleanly as a system service from the standard package repositories:
bash
sudo apt install tor
sudo systemctl enable --now torVerify it's listening on the expected port:
bash
ss -lntp | grep 9050You should see:
LISTEN 0 4096 127.0.0.1:9050 0.0.0.0:*Two things to confirm: port 9050 is open, and it's bound to 127.0.0.1 only — not exposed on any external interface. Tor as a local SOCKS proxy has no business being reachable from outside your machine.
Verifying the Full VPN→Tor Chain
Before wiring SearXNG into Tor it's worth confirming the complete stack is functioning. Two curl commands tell you everything you need to know:
bash
# Your VPN exit IP — what the world normally sees from your machine
curl -s https://ifconfig.me/ip ; echo
# Your Tor exit IP — what the world sees when traffic goes through Tor
curl -s --socks5-hostname 127.0.0.1:9050 https://ifconfig.me/ip ; echoThe first should return your VPN's exit IP — not your real ISP-assigned address. If it returns your bare home IP, your killswitch has a problem that needs resolving before going further. The second should return a completely different IP — a Tor exit node somewhere in the world with no relationship to your identity or your VPN.
If both conditions are met, your stack is functioning correctly: your ISP sees only the WireGuard tunnel, and traffic routed through Tor exits from an anonymized node that knows nothing about you.
The --socks5-hostname flag in that test is important and reflects something we'll carry forward into the SearXNG config. Plain --socks5 resolves DNS locally before sending the request through the proxy — meaning your DNS queries leak outside the tunnel even if the HTTP traffic doesn't. socks5-hostname pushes DNS resolution through the proxy as well. No leak. We'll use its YAML equivalent, socks5h, in the SearXNG configuration for the same reason.
Wiring SearXNG into Tor
With Tor confirmed functional, configure SearXNG to route all its upstream engine queries through it. This requires editing the outgoing: section of settings.yml. First, strip out any existing proxy configuration that might conflict:
bash
sed -i '/^outgoing:$/,/^[^[:space:]]/{
/^[[:space:]]*proxies:/,/^[[:space:]]*[a-zA-Z0-9_-]\+:/d
/^[[:space:]]*using_tor_proxy:/d
}' searx/settings.ymlThen append the Tor proxy configuration immediately after the outgoing: key:
bash
sed -i '/^outgoing:$/a\
proxies:\
all://: socks5h://127.0.0.1:9050\
using_tor_proxy: true\
' searx/settings.ymlVerify the section looks correct:
bash
sed -n '/^outgoing:$/,/^[^[:space:]]/p' searx/settings.yml | sed -n '1,20p'You're looking for:
yaml
outgoing:
proxies:
all://: socks5h://127.0.0.1:9050
using_tor_proxy: true
request_timeout: 3.0
...The socks5h:// protocol specifier is doing meaningful work. The h suffix means hostname resolution happens inside the Tor tunnel rather than locally — every DNS query SearXNG makes when contacting upstream engines travels through Tor along with the request itself. Without it, the content of your queries would be obscured but the pattern of which domains you're querying would remain visible to anyone watching your network traffic.
using_tor_proxy: true instructs SearXNG to apply Tor-specific behavioral adjustments — primarily widening timeouts and disabling certain features that don't perform well under Tor's latency characteristics.
What This Protects — and the Proof
With this configuration in place, the flow for a search query is:
SearXNG receives your query → constructs requests to Google, Bing, and whatever other engines you have enabled → routes those requests through Tor → the requests exit from a Tor node somewhere in the world → the search engine receives a query from an anonymous IP with no cookies, no session history, no persistent identity.
The clearest empirical confirmation that the anonymization is working comes from the engines that refuse to cooperate. DuckDuckGo returns a 403. Brave Search rate-limits the exit node immediately. The engines that market themselves most aggressively on privacy are the first to block Tor exits — because a genuinely anonymous user represents a direct threat to the data collection their business model depends on. Google, whose surveillance operation dwarfs both of them, returns results without complaint. It already has you everywhere else.
The latency hit is real and worth acknowledging. Tor adds roundtrip time. SearXNG searches through Tor will take two to four seconds rather than under one. For a privacy-focused research workflow this is an acceptable tradeoff. The timeout values in settings.yml — request_timeout and max_request_timeout — can be tuned upward if you find engines timing out before returning results.
Running as a Systemd User Service
Why Systemd Over a Shell Script
You could start SearXNG by running a shell script every time you log in. It would work. It would also mean manually launching it after every reboot, losing it silently if it crashes, having no structured log trail, and needing to remember to shut it down cleanly before powering off. Systemd solves all of that with a service file — twenty lines that buy you automatic startup, automatic restart on failure, clean shutdown, and integration with the journal for logging.
On a system you're building to take privacy seriously, having your tooling run reliably without manual intervention isn't a convenience — it's part of the discipline. A privacy tool you have to consciously deploy is a privacy tool you'll eventually skip.
We'll use a user service rather than a system service. SearXNG runs under your own account rather than as root or a dedicated system user, with access to your home directory, your environment, and your venv, without requiring elevated privileges.
Creating the Service File
bash
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/searxng.service <<'EOF'
[Unit]
Description=SearXNG local search engine
After=network.target tor.service
[Service]
Type=simple
WorkingDirectory=%h/searxng
Environment="SEARXNG_SETTINGS_PATH=%h/searxng/searx/settings.yml"
Environment="PYTHONPATH=%h/searxng"
ExecStart=%h/searxng/venv/bin/python %h/searxng/searx/webapp.py
ExecStop=/bin/kill -TERM $MAINPID
Restart=on-failure
[Install]
WantedBy=default.target
EOFEvery line is doing deliberate work. The non-obvious ones:
After=network.target tor.service establishes startup ordering. SearXNG won't attempt to start until both the network stack and Tor are up. Without this, SearXNG might start during boot before Tor's SOCKS proxy is listening, fail its first connection attempt, and crash or cache a broken state.
%h is systemd's specifier for the service owner's home directory. Using it rather than hardcoding the path means the service file is portable across users and machines.
Environment="PYTHONPATH=%h/searxng" is the line that separates a working service from one that crashes immediately with ModuleNotFoundError: No module named 'searx'. When you run SearXNG manually, activating the venv adjusts the Python path so the searx package is discoverable. Systemd doesn't activate the venv — it invokes the venv's Python binary directly, which handles interpreter isolation but not working directory path resolution. Setting PYTHONPATH explicitly tells Python where to find the searx package regardless of how the process was started. Without it, the service starts, Python launches, immediately fails to import searx, and exits — cycling through five restart attempts before systemd gives up.
ExecStart invokes the venv's Python binary directly rather than activating the venv in a wrapper script. The venv binary already has the correct interpreter and site-packages path baked in. No activation dance required.
Restart=on-failure means that if SearXNG crashes for any reason other than a clean shutdown signal, systemd restarts it automatically. A Tor restart, a network hiccup, an unexpected exception — none of these leave you without search until you notice and intervene.
Enabling Startup at Boot
bash
systemctl --user daemon-reload
systemctl --user enable --now searxng.servicedaemon-reload tells systemd to re-read its service file definitions — required any time you create or modify a service file. enable --now both registers the service to start at boot and starts it immediately in the current session.
One additional step is required for a user service to start at boot without an active login session:
bash
loginctl enable-linger $USERBy default, systemd user services only run while that user is logged in. enable-linger maintains your user's systemd session from boot to shutdown, independent of login state. Without it, the service definition is correct and the enable is registered, but SearXNG won't actually start until you log in — which defeats the purpose.
Verifying the Service
bash
systemctl --user status searxngA healthy service shows active (running). If instead you see failed (Result: exit-code), check the journal:
bash
journalctl --user -u searxng -n 50 --no-pagerThe journal output is precise about what went wrong. ModuleNotFoundError: No module named 'searx' means PYTHONPATH isn't set correctly. A YAML parsing error points back to settings.yml. A connection refused error on startup usually means Tor isn't up yet, which the After=tor.service dependency should prevent but can still occur if Tor is slow to initialize on a particular boot.
One gotcha worth knowing: if the service hits its restart limit cycling through repeated failures, it enters a hard-failed state that systemctl --user restart alone won't clear. The command to reset it is:
bash
systemctl --user reset-failed searxng
systemctl --user start searxngLog Monitoring
For day-to-day use the journal gives you clean access to SearXNG's runtime output:
bash
# Follow live output
journalctl --user -u searxng -f
# Review recent history
journalctl --user -u searxng -n 100 --no-pagerThe engine errors you'll see — Brave rate limiting, DuckDuckGo 403s, occasional timeouts from engines slow to respond through Tor — are normal operational noise. SearXNG handles them gracefully and routes around them. As long as searches are returning results in the browser, the errors in the log are informational.
If a particular engine is consistently failing, the cleanest solution is disabling it in SearXNG's preferences UI at http://127.0.0.1:8888/preferences. The UI exposes the full engine list with individual enable/disable toggles. Disabling DDG and Brave, given their consistent hostility to Tor exits, is a reasonable housekeeping step.
The Click Problem
What the Tor Proxy Actually Covers
There is a gap in the privacy stack as configured so far, and it's worth being direct about it rather than letting it hide in the fine print. SearXNG's Tor proxy configuration governs one specific thing: the requests SearXNG's backend makes to upstream search engines when processing your query. That's meaningful and real protection. The search engines learn nothing about you.
But the moment you click a result, SearXNG is no longer in the loop. Your browser takes over and makes a direct HTTP request to the target site — carrying your IP address, your browser fingerprint, whatever cookies that domain has previously set, and the full complement of third-party trackers embedded in the page. The search was private. The click is a completely separate transaction that the stack as configured does nothing to anonymize.
This is not a flaw in SearXNG's design — it's an architectural boundary. But understanding it is essential to understanding what the tool actually provides, and to deciding whether you need to close the gap.
A Note on Morty
SearXNG was designed to work with a companion tool called morty — a small privacy proxy that intercepts clicked result links, fetches the target page on your behalf through Tor, strips trackers, and serves sanitized content back to your browser. It's an elegant solution in principle. At the time of writing it is effectively a dead end: its dependency chain requires a version of Go that has not yet been released, and installation fails on any current system. Until the project's dependencies are updated, this path is closed.
Routing Your Browser Through Tor
The most practical solution to the click problem is configuring a dedicated browser to route all its traffic through Tor's local SOCKS proxy. This requires no additional software and covers not just SearXNG clicks but all browser traffic — which for a privacy-focused research workflow is the right scope.
The important nuance is which browser. Routing your primary browser through Tor while staying logged into Google, your email, and a dozen other services that know exactly who you are provides essentially no anonymity benefit — the identity signals overwhelm the IP anonymization. The correct approach is a dedicated browser used exclusively for private research, never logged into any account, never used for anything personally identifying. Firefox is well suited to this role.
Configuring Firefox for Tor:
Open Firefox's network proxy settings:
Preferences → General → Network Settings → SettingsSelect Manual proxy configuration and enter:
SOCKS Host: 127.0.0.1
Port: 9050
SOCKS v5: ✓The critical checkbox: "Proxy DNS when using SOCKS v5" — enable this without exception. Without it, Firefox resolves DNS locally before sending the request through the SOCKS proxy. Your browsing destinations remain hidden but the DNS queries for those destinations are visible to your ISP and DNS resolver — a meaningful leak that negates a significant portion of what you're trying to achieve.
Verify it's working by navigating to http://check.torproject.org. A correctly configured browser will confirm it's routing through Tor. The IP shown should match neither your real IP nor your VPN exit IP.
Additional hardening worth applying to this browser:
uBlock Origin in medium or hard mode blocks third-party scripts and frames by default, eliminating the vast majority of surveillance infrastructure embedded in commercial web pages before it can make outbound connections.
NoScript disables JavaScript by default with per-site whitelisting. JavaScript is the primary vector for browser fingerprinting — disabling it by default substantially reduces the fingerprinting surface at the cost of degraded experience on JS-heavy sites, which for a research browser is usually an acceptable tradeoff.
Firefox's built-in hardening worth enabling manually in about:config:
privacy.resistFingerprinting → true
network.dns.disablePrefetch → true
browser.send_pings → falseprivacy.resistFingerprinting activates a suite of anti-fingerprinting measures that normalize reported screen resolution, timezone, language headers, and other browser characteristics that fingerprinting scripts use to build persistent identifiers.
The Full Stack in Review
What You've Built
It's worth pausing to take stock of what the assembled stack actually represents, because the individual components are less interesting than the way they interact. Privacy tooling is only as strong as its weakest seam — the place where one layer hands off to the next is where leaks happen, and understanding those handoffs is what separates a coherent privacy posture from a collection of loosely related tools.
Here is the complete flow, from intent to result:
You type a query into SearXNG at 127.0.0.1:8888. That query never leaves your machine unencrypted. SearXNG constructs requests to Google, Bing, and whatever other engines you've enabled, and routes them through Tor's SOCKS proxy at 127.0.0.1:9050. Those requests enter the Tor network through an entry node that sees your VPN IP — not your real IP. They traverse a middle relay that sees neither origin nor destination. They exit through an exit node that contacts the search engine directly. The search engine returns results to the exit node, which passes them back through the circuit to SearXNG, which aggregates and displays them in your browser. You click a result. Firefox routes that request through the same Tor SOCKS proxy. The target site receives a connection from a Tor exit node with no cookies, no prior history, no account association.
What Each Observer Sees
| Observer | What They See |
|---|---|
| Your ISP | Encrypted WireGuard tunnel. Traffic volume and timing. Nothing else. |
| Your VPN provider | Tor traffic entering the tunnel. No query content, no destinations. |
| Tor entry node | Your VPN exit IP. Encrypted traffic. No destination, no content. |
| Tor exit node | Request to search engine or target site. No origin IP. |
| Search engine | Query from anonymous Tor exit node. No cookies, no account, no history. |
| Target site | Page request from anonymous Tor exit node. Fingerprint mitigated by Firefox hardening. |
No single observer in that chain holds enough information to reconstruct the complete picture. That's the point of layering — not that any individual component is impenetrable, but that compromising one doesn't compromise the whole.
The Systemd Backbone
Underpinning all of this is a service architecture that requires no manual intervention. Tor starts as a system service on boot. SearXNG starts as a user service after Tor, automatically, every time the machine comes up. If either crashes it restarts. Shutdown is clean and ordered. The journal captures everything. You open your dedicated Firefox profile, navigate to 127.0.0.1:8888, and the full stack is simply there — no activation ritual, no checklist, nothing to forget.
That reliability matters precisely when it's most needed — when you're in a hurry, distracted, or researching something sensitive enough that the friction of a manual setup might tempt you to skip it. Systemd removes the decision point entirely.
Limitations and Honest Threat Modeling
This Stack Has a Specific Threat Model
Everything built across this series is optimized against one threat: passive, large-scale surveillance by commercial platforms, data brokers, and the ISP-level infrastructure that feeds them. It is a strong defense against that threat. It is not designed for, and should not be trusted as, protection against a targeted adversary with significant resources and specific interest in you as an individual.
Understanding the difference between these threat models is not a reason to dismiss what you've built — it's a reason to use it appropriately.
The Identity Collapse Problem
The most reliable way to defeat this entire stack requires no technical attack whatsoever. If you open your dedicated research browser, conduct an anonymous search, click through to a site, and then — out of habit, convenience, or inattention — log into any account, the anonymization collapses instantly and completely. The account knows who you are. The behavioral trail from that point forward is attached to your identity regardless of what IP address is visible.
The same applies to any persistent identifier: a cookie you forgot to clear, a browser extension that phones home, a font or plugin that creates a fingerprint consistent across sessions. Anonymity is not a property of tools — it's a property of behavior. The tools create the conditions for anonymity. Consistent, disciplined behavior is what actually achieves it.
Fingerprinting and Behavioral Analysis
Browser fingerprinting has become sophisticated enough that IP anonymization alone is insufficient against a determined tracker. Your browser's reported screen resolution, installed fonts, WebGL renderer string, canvas rendering characteristics, audio context fingerprint, and dozens of other signals can be combined into an identifier that is often more persistent and unique than a cookie. privacy.resistFingerprinting degrades the quality of these signals but does not eliminate them.
More subtle is behavioral fingerprinting — the pattern of your interests over time. A researcher who consistently investigates the same geographical region, the same organizations, the same categories of information will develop a recognizable pattern even across anonymized sessions. No IP, no cookie, no account is needed. The shape of the queries is enough. Against an adversary analyzing traffic patterns at scale this is a real and largely unsolved problem.
For High-Stakes Use Cases
If your threat model includes a state-level adversary, law enforcement with legal process, or anyone with the motivation and resources to pursue a specific individual, this stack is a meaningful baseline but not a ceiling.
Tor Browser rather than a hardened Firefox with a SOCKS proxy. Tor Browser is specifically engineered to normalize the browser fingerprint across all users — everyone using it presents the same fingerprint by design, making individual identification significantly harder. A regular browser pointed at the Tor SOCKS proxy, however well hardened, cannot replicate this property.
Tails OS for the highest-sensitivity work. Tails is a live operating system booted from USB that routes all traffic through Tor by default, leaves nothing on disk, and resets to a clean state on every boot. It is the standard recommendation for journalists working with sensitive sources, activists in hostile environments, and anyone whose physical safety depends on the integrity of their operational security. It is not convenient. Convenience and that level of security are fundamentally in tension, and Tails has chosen correctly.
Compartmentalization as a practice, not just a tool configuration. Separate devices or virtual machines for sensitive research, never crossed with personal use, never connected to accounts or services that know your identity. The hardware and the identity used for sensitive work should have no overlap with the hardware and identity of your ordinary life.
What This Stack Is Actually For
None of these caveats should diminish what you've built. The vast majority of surveillance people are exposed to is not targeted — it's the ambient, automated, commercial kind that treats every user as a data point to be monetized. Against that surveillance, which is pervasive and affects everyone, the stack in this series provides genuine and substantial protection. Your search behavior is not being logged, profiled, or sold. The queries you make do not feed recommendation algorithms, advertising profiles, or data broker inventories. The sites you visit through your research browser do not know who you are.
That is not nothing. That is, in fact, considerably more than almost anyone achieves with their current tooling.
The goal of threat modeling is not to find reasons why nothing is good enough. It is to understand what you're protected against, what you're not, and to make clear-eyed decisions about where to invest additional effort based on your actual situation. For most people reading this, the stack as built is the right level of investment. For journalists, activists, researchers in sensitive domains, or anyone operating in an environment where exposure carries real consequences — this is a foundation to build on, not a complete solution.
Know which situation you're in. Build accordingly.
A Final Word
There is a version of this conversation that ends with a product recommendation — a browser extension, a paid VPN service, a privacy-branded search engine that promises to handle everything on your behalf. That version is easier to write and easier to follow. It's also the version that leaves you dependent on someone else's business model, someone else's infrastructure, and someone else's definition of what your privacy is worth.
What we've built instead is infrastructure. SearXNG running on your own hardware, routing queries through Tor, backed by a VPN killswitch that tolerates no leaks, served by a systemd service that requires nothing from you after initial setup. No subscription. No privacy policy. No company that can be acquired, subpoenaed, or quietly pressured into changing the terms of the arrangement. The threat model doesn't include a vendor relationship because there is no vendor.
The deeper point is that privacy is not a feature — it's a practice, and practices require tools you understand well enough to use correctly. The exercise of building this stack is itself part of the value. You now know what socks5h means and why it matters. You know what your VPN provider can and cannot see. You know why DuckDuckGo blocking Tor exits is actually confirmation that your anonymization is working. That knowledge doesn't expire when a company pivots, gets acquired, or quietly changes its data retention policy.
The commercial internet was not designed with your privacy as a priority. It was designed for engagement, for retention, for the extraction of behavioral data at scale. Every search query you make through Google or any of its proxies — however privacy-branded — is a transaction in that system. This stack is simply a decision to stop making those transactions. To treat your search behavior, your research, your questions, as your own.
It is not the last word on privacy. It is a principled starting point, built from tools you control, that you now understand well enough to extend, harden, and adapt as your needs evolve. That's considerably more than most people have. Build on it.
Part I: "The Search Box Is a Confession Booth" — covers the philosophy of private search, what SearXNG is, and walks through installation and initial configuration.
Border Cyber Group
Member discussion: