hosting a Tor onion service

Posted on
security tor

Tor onions are a way to host secure services that protect the anonymity of you and your clients. It also removes load from Tor exit nodes. If you open this page in the Tor browser it will redirect you to the following address:


which can only be opened from inside the Tor network.

getting started permalink

To host an onion service, we’ll have a Docker container running Tor that decodes requests and forwards them to another container hosting the service. I’ve got a GitHub repo that will help you build the Tor image and shows you an example docker-compose.yml:

git clone
cd tor-onion-docker
docker-compose build

Take a look at the docker-compose.yml to see an example with NGINX as the backend service.

generating an onion address with a prefix permalink

These onion addresses are long because they’re cryptographically generated public keys that verify that you are indeed the host of the address. This means that onion services come with the security benefits of HTTPS out of the box, but it also means the addresses are ugly. We can increase memorability by brute-forcing a vanity prefix:

git clone
pushd mkp224o
docker build -f contrib/docker/Dockerfile -t mkp224o .
mkdir output
docker run -it --rm -u 1000:1000 -v $(pwd)/output:/stuff mkp224o ${PREFIX} -v -n 1 -d /stuff
mv mkp224o/output/${PREFIX}* data/onionserver/

The prefix can’t be much longer than 6 characters without the expected runtime becoming infeasible.

onion location permalink

If you’d like users of your website to know about the onion version, add a <meta> tag to your documents:

<meta http-equiv="onion-location"
    content="http://kylrthjj7mpvktolz7u6fnudt3hpdvjw4hzquanjpepgsf5vcq5divad.onion" />

Since I use Hugo to build my site, I can direct users to the onion version of the specific page they requested:

<meta http-equiv="onion-location"
    content="http://kylrthjj7mpvktolz7u6fnudt3hpdvjw4hzquanjpepgsf5vcq5divad.onion{{ .RelPermalink }}" />

In cases where you have less control of the documents, you can instead add an Onion-Location header with a reverse proxy. Here’s how you do it with NGINX:

add_header Onion-Location http://kylrthjj7mpvktolz7u6fnudt3hpdvjw4hzquanjpepgsf5vcq5divad.onion$request_uri;

You can see more details here.