A hidden service in ten lines of config

The “dark web” sounds exotic, but the mechanics of putting a site on it are closer to a reverse proxy config than anything exotic. David Alvarez Rosa published a walkthrough of self-hosting a personal site as a Tor hidden service, and the whole setup is small enough to fit in one config edit and one nginx block.

A hidden service works differently from a normal website. Regular Tor traffic bounces through volunteer relays to hide the client. A hidden service extends that anonymity to the server: the site gets a .onion address that resolves only inside the Tor network, and neither side learns the other’s IP address. For a homelabber, that means serving a site without opening a port, without a public IP, and without a CDN in the middle.

The setup

Two lines in /etc/tor/torrc do the heavy lifting:

HiddenServiceDir /var/lib/tor/blog/
HiddenServicePort 80 127.0.0.1:8080

Restart Tor, and it generates an onion address in the service directory. That directory must be dedicated and Tor-owned, not your web root, since Tor stores its private keys there. Anyone who copies those keys can impersonate your onion address.

From there, Tor forwards the onion’s port 80 to 127.0.0.1:8080, and an nginx server block listens on that loopback port like any other vhost. One detail worth internalizing: skip TLS entirely. Tor already encrypts and authenticates the path between client and server, so stacking HTTPS on top adds certificate headaches for nothing. Plain HTTP on loopback is the standard pattern for onion services.

The build problem nobody warns you about

The genuinely useful part of the post is the second half. Static site generators bake absolute URLs into every link at build time. Build once for your clearnet domain, serve it over Tor, and every internal link drags visitors back to the clearnet domain, leaking that the two sites are the same and breaking navigation inside the Tor Browser.

The fix: build twice. The same Hugo site builds once with the clearnet base URL and once with the onion address as the base, and each output rsyncs to its own web root. Wire it into the deploy pipeline so both copies stay in sync on every push, and the onion version links only to itself.

Why bother

For a personal blog, this is mostly a fun afternoon. But the pattern generalizes. Any admin panel, documentation portal, or internal service you want reachable without exposing your network gets a hidden service for the cost of two config lines. No port forwarding, no firewall exceptions, no CGNAT workarounds. If you already self-host behind a NAT router you cannot punch holes in, this is an alternative ingress path that also hides your home IP by design.

One small detail from the post is easy to miss and matters for anyone automating this: the author’s deploy pipeline builds the site once per target on every push, so the two versions cannot drift apart. If you maintain the onion copy by hand instead, expect drift within a week. The onion build is not a mirror to set and forget; it is a second build output that belongs in the same CI job as the first.

It is also worth noting what the setup does not do. There is no login wall, no special gateway, nothing for a visitor to configure beyond having Tor Browser installed. That simplicity is the point. Most “publish to the dark web” guides drag you through hosting providers and obfuscation layers, when a stock Tor package and the web server you already run will do.

Verify before you trust

Test the site from a different machine with Tor Browser before you consider it done. Testing from the server itself works for the wrong reason and proves nothing about a real visitor’s experience. If the page loads, links stay inside the network, and nothing 404s, the config is good.

Operational notes

Two more before you ship it. First, hidden services are slower than clearnet sites by design: the connection negotiates an introduction point and a rendezvous point through the network, which adds seconds of latency to the first request. Static content hides this well, since everything after the first page load is fast, but it is a bad fit for anything interactive. Keep the site static and small, and the latency is a non-issue.

Second, the hidden service hides your location but does not harden the application. The nginx block above serves static files, about as safe as web serving gets. Put a dynamic app behind an onion instead, and you should treat it as facing hostile territory: patch it, isolate it, and check that it never phones home to a clearnet endpoint, because application behavior can leak your address in ways Tor cannot mask. Test the site from a different machine with Tor Browser before you trust the config. Testing from the server itself works for the wrong reason and proves nothing about a real visitor’s experience.

Leave a Reply

Your email address will not be published. Required fields are marked *