Skip to content
Go back

Tor Hidden Services for Self-Hosters

By SumGuy 9 min read
Tor Hidden Services for Self-Hosters
Contents

Stop. Before you expose your home lab to the internet

You’ve got Nextcloud running. Maybe a Matrix server. Or you’re thinking about it. And your brain immediately goes: “I’ll just use Cloudflare Tunnels and call it a day.”

Tunnels are fine. They work. But you’re still giving Cloudflare a clear view of what’s inside. They see every request, every endpoint, every database query screaming across their infrastructure. They’re not malicious, but they’re not your grandmother either.

Tor hidden services (onion addresses) flip this completely. Instead of exposing your IP to the world, you hand out a .onion domain. Nobody, not your ISP, not Cloudflare, not a curious script kiddie scanning the internet, knows where your server lives. They can’t even see it without your permission.

That’s the real deal. And setting it up is way less painful than you’d think.

What is a Tor hidden service, actually?

Your regular server is like a storefront: you put your address on the sign, people find you. A Tor hidden service is more like a speakeasy. You exist, but you’re not on the map. Your users connect through a series of encrypted relays that scramble their origin and destination until neither party knows the other’s real location.

Here’s what happens when someone visits your .onion address:

  1. Their Tor client (usually the Tor Browser) connects to your advertised “introduction points”: a handful of relays you’ve chosen.
  2. Your server (also connected to Tor) maintains connections to the same introduction points.
  3. They meet in the middle, shake hands through multiple layers of encryption, and traffic flows both ways.
  4. Your server’s real IP never touches the conversation.

From the perspective of someone snooping on traffic, it looks like random encrypted noise bouncing between random Tor nodes. No pattern. No origin. No destination.

That’s powerful. That’s also why it matters.

The threat model: who are you actually protecting against?

Before you obsess over onion addresses, ask yourself: what are you protecting against?

ISP surveillance. Your ISP can see that you’re connecting somewhere, but Tor hides the destination. They see you’re using Tor; they don’t see what you’re doing inside it.

Mass port scanners. Shodan, Censys, anyone renting a botnet, they can’t find your .onion address unless you publish it. Your home IP stays dark.

Casual snooping. Someone on your home WiFi, or a malicious middle man on your network, can’t intercept traffic to a Tor hidden service because it’s encrypted from your app all the way to the Tor network entry point.

What Tor hidden services don’t protect against:

Know your adversary. If you’re protecting against your ISP learning that you run a homelab, Tor is overkill; Cloudflare Tunnels already do that. If you’re protecting against the entire internet discovering you run a homelab, Tor is the answer.

Setting up a Tor hidden service

You’re going to run Tor on your server, point it at a local port where your app lives, and Tor will advertise a .onion address. Simple diagram:

Internet (Tor Browser)
Tor Network
Your Tor Daemon (SOCKS localhost:9050)
Reverse Proxy (localhost:8080)
Your App (localhost:3000)

Here’s the torrc config:

# /etc/tor/torrc (or /usr/local/etc/tor/torrc on macOS)
# Listen on localhost only
SocksPort 9050 IsolateDestAddr
ControlPort 9051
# Turn on hidden service support
HiddenServiceDir /var/lib/tor/hidden_service/
HiddenServicePort 80 127.0.0.1:8080
HiddenServicePort 443 127.0.0.1:8443
HiddenServiceVersion 3
# Keep logs readable
Log notice syslog
# Performance tuning for a small home lab
ClientOnly 0
NumCPUs 2
MaxOnionsPending 100

Key points:

After you restart Tor:

Terminal window
sudo systemctl restart tor
cat /var/lib/tor/hidden_service/hostname

You’ll see something like:

a7x5k9m2j3b8c6d4e5f7g9h2i4j6k8l9m0n1p3q5r7s9t2u4v6w8x.onion

That’s your address. Share it like you’d share a phone number, carefully.

Running Tor in a container (easier)

If you’re a Docker person, here’s a docker-compose.yml that runs Tor and a reverse proxy side-by-side:

services:
tor:
image: osminogin/tor-simple
volumes:
- tor_data:/var/lib/tor
- ./torrc:/etc/tor/torrc:ro
ports:
- "9050:9050" # SOCKS proxy (optional, for debugging)
- "9051:9051" # Control port (optional)
networks:
- internal
restart: unless-stopped
reverse_proxy:
image: caddy:2-alpine
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
ports:
- "127.0.0.1:8080:8080"
networks:
- internal
depends_on:
- tor
restart: unless-stopped
your_app:
image: your-nextcloud-or-whatever
expose:
- "3000"
networks:
- internal
environment:
- TRUST_PROXY=127.0.0.1
restart: unless-stopped
volumes:
tor_data:
networks:
internal:
driver: bridge

The torrc config sits in your compose directory:

SocksPort 9050
ControlPort 9051
HiddenServiceDir /var/lib/tor/hidden_service/
HiddenServicePort 80 reverse_proxy:8080
HiddenServicePort 443 reverse_proxy:8443
HiddenServiceVersion 3
Log notice stdout

Caddy acts as your reverse proxy, handling TLS termination and routing to your app:

localhost:8080 {
reverse_proxy your_app:3000
encode gzip
}
localhost:8443 {
reverse_proxy your_app:3000
tls internal
encode gzip
}

After startup, grab your onion address:

Terminal window
docker exec <container_id> cat /var/lib/tor/hidden_service/hostname

Network isolation: the boring but critical part

Here’s where most people mess up.

Your .onion service is hidden, but your clearnet IP is not. If someone finds both, they can correlate them and figure out where you live. So: never access your own hidden service from the clearnet IP.

Break it down:

  1. Tor service listens on 127.0.0.1 only. Nobody can reach it from outside the container or machine.
  2. Reverse proxy listens on 127.0.0.1:8080. Again, only local traffic.
  3. App is isolated to a Docker network. No external ports exposed except through the reverse proxy.
  4. All traffic from the internet to the .onion address flows through Tor’s introduction points. Your ISP, your router, Cloudflare: none of them see it.

The mistake people make: they run a clearnet site on 0.0.0.0:8080 alongside the hidden service. That’s hiring a forklift to move a couch. One port scan and your onion service is linked to your IP.

Getting a vanity .onion address

Default onion addresses are random garbage. Some people want something memorable: thesumguy.onion or whatever.onion.

You can generate vanity addresses with mkp224o, but heads up: it’s a brute-force search. Finding a 6-letter prefix takes a few hours on a modern CPU. Finding 8 letters? Days. Months for 10+.

Terminal window
mkp224o -d ~/onion_search/ "thesumguy"

This searches for any address starting with “thesumguy”. When it finds one, it saves the keypair to ~/onion_search/thesumguy...onion/. Copy that to your Tor HiddenServiceDir and restart.

Fair warning: vanity addresses are slower to find than they sound, and they’re not necessary. Your random 56-character address is just as secure and way faster to set up. Vanity is ego. Security is the point.

Operational security: the ways you’ll accidentally doxx yourself

You’ve got a hidden service. Great. Now don’t blow it up.

Mistake #1: Using the same username everywhere.

If your .onion service requires a login, and you use “KingPin” as the username, and you also use “KingPin” on GitHub or Twitter, someone can connect the dots. Use a different persona for sensitive services.

Mistake #2: Logging your real IP.

Your app logs every request. If you or someone accessing from the clearnet accidentally hits the .onion service, that request gets logged. Now your app knows about both the clearnet and hidden service traffic. If logs leak, you’re connected.

Keep clearnet and hidden service completely separate. Different apps, different databases if possible. At minimum, different auth tokens.

Mistake #3: Syncing data between clearnet and hidden.

If you run the same Nextcloud instance on both your clearnet IP and a hidden service, they’re obviously the same person. Pick one. If you need both, run separate instances with separate data.

Mistake #4: Checking your hidden service from the clearnet.

Your browser history, your DNS lookups, your connection patterns, all of it can leak if you’re not careful. Access your hidden service only through Tor Browser. Not Firefox with a SOCKS proxy. Not a VPN. The Tor Browser, period.

Mistake #5: Leaving the hidden service keypair world-readable.

The HiddenServiceDir and its contents are sensitive. Make sure permissions are tight:

Terminal window
ls -la /var/lib/tor/hidden_service/
# Should show: drwx------ (700)

If someone gets that keypair, they can impersonate your service and serve malware to your users. That’s worse than being doxxed.

When to use a hidden service (and when not to)

Use Tor hidden services if:

Don’t use Tor hidden services if:

Tor is a tool. It solves a specific problem: hiding location and identity on the network. It doesn’t solve everything. If your threat model is “I don’t want my ISP to know I run a homelab,” Tor works. If it’s “I don’t want the FBI to find me,” Tor is a starting point, not the whole answer.

In short

Tor hidden services are not magic. They’re not paranoid. They’re a straightforward technical solution to a real problem: keeping your home IP off the internet.

Set up a Tor daemon, point it at your app, grab the onion address, and share it with people you trust. Keep your clearnet and hidden service separate. Don’t be stupid about opsec.

Everything else is just friction.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
Collateral Freedom: Costly to Block

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts