What an onion service actually is
A Tor onion service is a server that is reachable only through the Tor network, at an address that ends in .onion and is derived from a cryptographic key rather than assigned by a registrar. There is no DNS lookup, no public IP in any record, and no TLS certificate to buy: the 56-character address is the service's public key, so the connection is authenticated and end-to-end encrypted by construction. A visitor running Tor Browser types the address; the network arranges an encrypted meeting point; the two halves talk without either learning the other's location. Nobody in the middle — not a relay, not an ISP, not the hosting provider — sees both ends of the conversation at once.
The consequence that matters for hosting is structural, not cosmetic. A normal web server has to listen on a public port, which means its address is discoverable, scannable, and ultimately linkable to whoever pays the bill. An onion service listens on nothing the internet can reach. Tor on the box makes only outbound connections, builds circuits to a handful of introduction points, and waits. The service is online and serving traffic while ss -tlnp shows nothing bound to a routable address. That inversion is the entire reason to run one.
When it is the right tool — and when it is not
Reach for an onion service when the location of the machine is part of what you are protecting: a site that must stay reachable even if its clearnet domain is seized, a submission box for sources or researchers, an admin panel you would rather not expose to the scanning internet at all, or a service whose audience already runs Tor and values that you cannot log an IP you never receive. It is also the honest answer when "hide the origin" is the actual requirement — a reverse proxy in front of a clearnet server only moves the target, whereas an onion service removes it.
It is the wrong tool when your visitors will not or cannot run Tor, when you need the raw throughput and sub-100-millisecond latency of a directly reachable server, or when discoverability is the point — an onion address is not something people stumble onto from a search engine. Many operators split the difference and run both: a normal site for reach and an onion service for the users who want it, served from the same application. We cover that pattern below. Nothing here is a substitute for hardening the box itself; if the machine is compromised, its location and contents leak regardless of how it is published, so treat the first-hour hardening checklist as a prerequisite, not an afterthought.
SP·03What the setup reveals, and to whom
It is worth being precise about the anonymity model, because "onion service" is often assumed to mean "untraceable" and the reality is more specific. Clients never learn the server's IP address — that property is strong and is the whole point. Your hosting provider, however, still sees a machine on their network making a steady stream of encrypted outbound connections to the Tor network; they cannot see the onion address, the content, or the visitors, but they can see that the box speaks Tor. On a mainstream host tied to your identity that is a thread worth pulling. On an offshore, anonymously paid VPS with no name on file, it leads nowhere — which is exactly why the two techniques belong together.
The failure modes that actually deanonymise onion services are almost never Tor itself. They are the application leaking a real IP in an error page, a header, an email it sends, or an outbound request to a clearnet analytics or update endpoint; a misconfigured server revealing its hostname or timezone; or correlation from the operator reusing the same box, key, or writing style across a clearnet identity. The cryptography holds; the plumbing around it is where people get caught. Every step below is written to keep that plumbing tight — bind to localhost, send nothing outbound in the clear, and keep the box's real identity off the machine per keeping your name off a server.
SP·04Install a current Tor, not the one your distro ships
Distributions freeze Tor at whatever version shipped with the release, and onion-service defenses move fast — the intro-point denial-of-service protection, v3 client authorisation and several stability fixes all landed in versions newer than what a stable Debian or Ubuntu carries by default. Install from the Tor Project's own signed repository so you track current releases and get security updates on their schedule, not the distribution's. The snippet below detects your release codename automatically and pins the repository to the project's signing key, so an attacker who tampers with the mirror cannot feed you a downgraded package.
Tor runs unprivileged as the debian-tor user on Debian and Ubuntu, drops into a sandbox, and needs no special network privileges — it dials out like any client. There is nothing to open in the firewall for the onion service itself, which is the first pleasant surprise of the whole exercise: your default-deny firewall can stay exactly as strict as it was.
Bind the application to localhost and nothing else
An onion service is a front door onto a plain local service — a web server, an app, an SSH daemon, anything that speaks TCP. The rule that keeps the whole thing safe is that this local service must listen only on the loopback interface, 127.0.0.1. If it also listens on the public IP, you have quietly recreated the problem the onion service was meant to solve: the content is now reachable and fingerprintable directly, and a scan that matches your clearnet server to your onion content undoes the anonymity in one request. Bind to loopback, confirm it with ss -tlnp, and let Tor be the only thing that ever connects to that port.
Because the traffic between Tor and your app stays on 127.0.0.1, it never touches the network and needs no TLS of its own — the Tor circuit already encrypts everything end to end. Do not put a self-signed certificate on the loopback listener; it adds a fingerprintable surface and buys nothing. Keep the app boring: no clearnet redirects, no absolute https://your-domain links baked into templates, no third-party fonts or analytics that would make the visitor's browser reach off the onion and out to the open web.
Declare the service, then read your address
The onion service is three lines of torrc: a directory where Tor keeps the key material, and a mapping from a virtual port on the onion address to the local port your app listens on. On the first restart, Tor generates an Ed25519 keypair in that directory and writes the hostname file — the 56-character .onion address you will hand out. That address is permanent for as long as you keep the key. Lose the key and the address is gone forever; leak it and someone else can impersonate your service. Treat the HiddenServiceDir as the crown jewels: 0700, owned by the Tor user, backed up encrypted and off the box.
Two settings earn their place next to the mapping. HiddenServiceEnableIntroDoSDefense 1 lets your introduction points rate-limit connection floods before they reach your box, which is the closest thing an onion service has to DDoS protection. Pinning HiddenServiceVersion 3 makes the version explicit and future-proof. If the service should be private rather than public, v3 client authorisation restricts it to holders of a key you distribute out of band — a clean way to run an admin panel that simply does not answer to anyone without the credential.
Advertise it, run both, and pick a memorable address
If you also run a clearnet site, tell Tor Browser about the onion version with the Onion-Location response header (or the equivalent HTML meta tag). Browsers with onion support then offer visitors the .onion automatically — the standard way sites like major newspapers and search engines surface theirs. Serving the same application on both a domain and an onion address is the norm, not an exotic case: one codebase, one database, two front doors, with the clearnet side sitting behind the usual origin-hiding reverse proxy and the onion side exposing nothing at all.
The default address is a random 56-character string, which is secure but unmemorable. A vanity address lets you brute-force a key whose base32 encoding starts with a chosen prefix — a few readable characters is quick, but each additional character multiplies the work by 32, so anything past seven or eight letters becomes a serious compute job. Generate vanity keys on a trusted local machine, never on the server, and drop the resulting key into the HiddenServiceDir exactly as you would restore a backup. A recognisable prefix also blunts one real attack: phishing clones that count on nobody reading a wall of random characters.
Keep it up: performance, monitoring, and key hygiene
An onion connection crosses six relays instead of the usual none, so latency is higher and throughput lower than a directly reachable server — that is physics, not misconfiguration, and no setting removes it. Resist the temptation of single-hop or non-anonymous modes to claw back speed; they trade away the exact property you built this for. What you can do is keep Tor current, leave the intro-point DoS defense on, front a busy service with caching so each request does less work, and size the box for CPU headroom because Tor's cryptography is not free. A modest offshore VPS from $8.00/mo handles a typical onion site comfortably.
Operationally, three habits keep an onion service healthy. Watch journalctl -u tor@default for reachability and descriptor-publishing errors rather than assuming silence means health. Back up the HiddenServiceDir the day you create it and after any change, encrypted and off the machine, because that key is the one thing you cannot regenerate. And keep the box's operational identity clean — a fresh key on a fresh anonymous VPS is worth little if the same operator habits tie it back to a clearnet self. The service will happily run for years untouched; the discipline is in the plumbing around it, not the service itself.
Step by step
-
01
Start from a hardened, anonymous box
Deploy an offshore VPS — online in about 15 min — and do the boring, essential work first: a named sudo user, key-only SSH, a default-deny firewall, and automatic security updates. The onion service adds nothing you have to open in that firewall, so a locked-down box stays locked down. Do not skip this; an onion address in front of a compromised server hides nothing.
ssh admin@203.0.113.10 sudo apt update && sudo apt full-upgrade -y # firewall stays default-deny; the onion service needs no inbound port sudo ufw status verbose
-
02
Install Tor from the Tor Project repository
Add the project's signed repository so you run a current Tor with the latest onion-service defenses, then install it. The codename is detected from
/etc/os-release, and the repository is pinned to Tor's signing key so a tampered mirror cannot downgrade you.sudo apt install -y apt-transport-https wget gpg . /etc/os-release wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc \ | gpg --dearmor | sudo tee /usr/share/keyrings/tor.gpg >/dev/null echo "deb [signed-by=/usr/share/keyrings/tor.gpg] https://deb.torproject.org/torproject.org $VERSION_CODENAME main" \ | sudo tee /etc/apt/sources.list.d/tor.list sudo apt update && sudo apt install -y tor deb.torproject.org-keyring
-
03
Bind your service to localhost only
Point whatever you are publishing at the loopback interface and nowhere else. Here nginx serves a static site on
127.0.0.1:8080; the same principle applies to any app — bind to127.0.0.1, not0.0.0.0. Reload, then prove withssthat nothing is listening on a routable address.# /etc/nginx/sites-available/onion.conf server { listen 127.0.0.1:8080; server_name _; root /var/www/onion; index index.html; }Enable it, reload, and confirm the bind is loopback-only:
sudo ln -s /etc/nginx/sites-available/onion.conf /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx ss -tlnp | grep 8080 # must show 127.0.0.1:8080, never 0.0.0.0:8080
-
04
Declare the onion service in torrc
Add the service to
/etc/tor/torrc: a key directory and a port mapping from the onion's virtual port 80 to your local 8080. Turn on the introduction-point DoS defense and pin the address version. Then restart Tor — on Debian and Ubuntu the running instance istor@default.# /etc/tor/torrc HiddenServiceDir /var/lib/tor/onion-www/ HiddenServicePort 80 127.0.0.1:8080 HiddenServiceVersion 3 HiddenServiceEnableIntroDoSDefense 1 sudo systemctl restart tor@default
-
05
Read the address and lock down the key
Tor has now created the key material and written your address. Read it, hand it out — and make sure the directory is
0700and owned by the Tor user, which Tor enforces but which a careless restore can break. Thehs_ed25519_secret_keyfile in that directory is your identity; nothing else is.sudo cat /var/lib/tor/onion-www/hostname # example: 2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion sudo ls -l /var/lib/tor/onion-www/ # hostname hs_ed25519_public_key hs_ed25519_secret_key sudo chown -R debian-tor:debian-tor /var/lib/tor/onion-www sudo chmod 700 /var/lib/tor/onion-www
-
06
Test it over Tor, then advertise it
Verify the service from a machine that has Tor running, using
torsocksso the request goes through the network rather than the clearnet. Once it answers, optionally point clearnet visitors at the onion version with anOnion-Locationheader on your normal site.# from any box running tor: torsocks curl -sSI http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/ | head # advertise the onion from your clearnet nginx (inside the https server block): add_header Onion-Location "http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion$request_uri";
-
07
Back up the key and write the runbook
The address lives and dies with the key directory. Archive it the moment it exists, move the archive off the box to encrypted storage, and note that restoring it anywhere reproduces the exact same
.onionaddress. Then write down the two commands you will actually need in six months — where the key is and how to check health — because the service runs untouched long enough that you will forget.sudo tar czf onion-www-key.tar.gz -C /var/lib/tor onion-www # copy onion-www-key.tar.gz to encrypted off-box storage, then delete the local copy # health check any time: sudo systemctl status tor@default sudo journalctl -u tor@default --since "1 hour ago" | grep -i "onion\|error"


