2 How It Works
Sam Bent edited this page 2026-07-16 11:13:26 -04:00

How It Works

This page explains what HiddenForge actually does, from the moment the container starts to a live .onion address. You don't need to know any of this to use it, but if you're going to trust it, you should be able to see inside it.

First, what an onion service is

A normal website has an IP address, and anyone who connects learns that address. An onion service works differently. Instead of accepting connections directly, your Tor daemon reaches out and picks a handful of relays in the Tor network to act as introduction points. It publishes a signed record saying "to reach me, go through these relays." Your .onion address is really the public half of a cryptographic key, and that record is signed with the private half.

When a visitor opens your .onion, their Tor client and your Tor daemon each build circuits to a shared rendezvous point and connect through it. Neither side ever learns the other's IP address, and no one in the middle learns where your server is. That is the property HiddenForge is built to protect: the network can't see where you are.

What happens when the container starts

The container's entrypoint runs as root just long enough to set things up, then hands off to an unprivileged tor user. Step by step:

  1. It reads your settings. It scans the environment for any variable ending in _TOR_SERVICE_HOSTS and turns each one into a hidden service. It processes them in a fixed order so the generated config is always identical for the same input.

  2. It resolves your backend. For a mapping like 80:web:80, it looks up the name web on the container network and records its current IP address. Tor only ever gets an IP, never a name. (This is a snapshot taken at startup, which matters for upgrades; more on that below.)

  3. It writes the Tor config. It generates a torrc from scratch with the hardening baked in (the full list is on the Security Model page), then sets the ownership and permissions Tor insists on. The data directory has to be private (mode 0700) or Tor refuses to start.

  4. It drops privileges and starts Tor. It uses su-exec to switch from root to the tor user and launches Tor. From here on, Tor is not running as root.

  5. It waits for Tor to be ready. It watches for Tor's control socket to appear, checking as the tor user because the data directory is locked down. It waits up to two minutes.

  6. It starts Vanguards. Once Tor is up, it launches the Vanguards addon, which connects to Tor over that control socket and adds guard-protection defenses. If Vanguards can't start, the container logs a warning and keeps running anyway; your service still works, with slightly less protection.

  7. It supervises. From then on it watches both processes. If Tor exits, the container exits, because Tor is the whole point. If Vanguards crashes, it's restarted, up to a few times before it's left off for the rest of that run.

How your address survives restarts

Your .onion address is a key file on disk, kept in the folder you mounted at /var/lib/tor/hidden_service. On every start, HiddenForge creates that folder only if it's missing and never touches an existing key. So restarting the container, or upgrading to a new image, keeps the same address, as long as you keep that folder. Lose the folder and the address is gone permanently. See Backing Up Your Onion Address.

How "healthy" is decided

Docker's health check doesn't just ask "is Tor running?" A running-but-stuck Tor (say, a censored network) would pass that and lie to you. Instead, HiddenForge asks Tor two questions over the control socket: has it bootstrapped all the way to 100%, and has it actually built a circuit? Only if both are true does the container report itself healthy. That's why "healthy" can take a couple of minutes on first boot, and why it means something real when it arrives.

The moving parts, named

  • Tor is the daemon that runs your onion service. Its version is pinned in the image.
  • Vanguards is an addon that watches the relays your service uses and defends against attacks that try to map its path back toward the server. See The Security Model for what it does and doesn't cover.
  • The control socket is a private Unix socket Tor exposes so Vanguards and the health check can talk to it. It's protected by file permissions, not a password, and only the tor user can reach it.
  • Your backend is your actual app, on an internal network with no way out to the internet and no way in from it.

That's the whole machine. Nothing reaches your backend except Tor, nothing reaches Tor's control except the tor user, and nothing learns where any of it lives.