Hosting a Website on Tor (Onionsite)

This guide requires a system with Podman and Podman Compose installed. Read our guide on Podman

Introduction

Tor is the classic, most popular anonymizing overlay network. It also supports accessing clearnet websites, by having the traffic exit the Tor network via an exit node.

For maximum privacy, it is generally a good idea to prioritize using hidden services (.onion domain) when on Tor.

Keep in mind that, despite being the most popular one, Tor is not necessarily the best network for privacy. I2P is an alternative that’s becoming more and more popular, because it addresses many of the concerns that people have about Tor.

We have an article on I2P, also listing some of the differences between Tor and I2P: Hosting a Website on I2P (eepSite) Using i2pd

The Stack

Note: The Tor Project does not provide an official container image like PurpleI2P does for i2pd, so we’ll be using an unofficial image, goldy/tor-hidden-service. Since it’s not an official image, it is slightly more likely that it may become malicious. If you want, you could build the image yourself from the source Dockerfile.

Let’s say you want to run a website called “coolwebsite1337”.

Create a new directory named coolwebsite1337. Inside that directory, create a compose.yaml file:

services:
  tor:
    image: docker.io/goldy/tor-hidden-service:latest
    restart: always
    environment:
      COOLWEBSITE_TOR_SERVICE_HOSTS: 80:caddy:80
    volumes:
      - tor:/var/lib/tor

  caddy: # you can use nginx if you want, caddy is just better
    image: docker.io/library/caddy:latest
    restart: always
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./caddy:/data # caddy internal data
      - ./public:/public # public website files

volumes:
  tor:
    external: true

Create the tor external volume:

podman volume create tor

Alright, now we’re ready to start the hidden service, generate our keys and .onion address. Bring the container up:

$ podman-compose up -d tor

Finding Your .onion Address

Run the following command to look for the address in the logs:

$ podman-compose logs tor | grep .onion

This is the new .onion address people can access your website at. It should look something like ci4sreei4cjqqsak37rzdqlauamcwnrqh3h44meb2pqnsxh5yu33hmid.onion. But there’s no website yet……

Caddy Configuration

Note: Do not mix clearnet and Tor/I2P Caddy instances. Tor and I2P do not require you to expose ports 80 and 443 to clearnet, so you can run an infinite number of Tor/I2P servers. If you expose a Tor/I2P server to clearnet, it’s trivial for an attacker to spoof the Host header while doing a HTTP request to your server’s IP, allowing them to check whether an onion/eepsite is hosted at that IP.

Create a new file named Caddyfile in the coolwebsite1337 directory:

http://ci4sreei4cjqqsak37rzdqlauamcwnrqh3h44meb2pqnsxh5yu33hmid.onion {
  root * /public
  file_server
}

Replace the onion address with your own.

Create a file named index.html under coolwebsite1337/public. Obviously, this is supposed to be an HTML file, but for testing purposes you can put anything in there.

Just to double-check, this is what the directory structure should be at this point:

- coolwebsite1337
  - compose.yaml
  - Caddyfile
  - public/
    - index.html

Now we’re ready to really take this website online:

$ podman-compose up -d

That’s it. Your website is now on Tor. kthxbai~

Tipz’n’Trix

Vanity addresses

You can use mkp224o to generate vanity .onion addresses. There’s an official container image for it so we’re going to use that.

$ podman run --rm -it -v ./:/keys ghcr.io/cathugger/mkp224o:master -d /keys vanity

This will bruteforce private keys until it finds one which has a .onion address starting with vanity. You can, of course, replace vanity with anything else.

Note: Keep in mind that the longer the vanity part, the longer it will take to generate. Anything over 6 characters will probably take hours, if not days, to generate (on a consumer CPU)

Once you find an address you like, stop the process with CTRL+C, then encode the private key of the address with base64:

$ cat vain3vqcyqnhhjtvostyaoslndomq4db6sg3zaak7rek6aocm42j5uid.onion/hs_ed25519_secret_key | base64 -w 0
PT0gZWQyNTUxOXYxLXNlY3JldDogdHlwZTAgPT0AAABg99ec1VfpRgmqBWwYKLzDMKVJLfGusMjXloYUvCFXajiyQyUkUlKAVqyjezFXcH8OtIu/YV5INpiuw9FPu5kn

Add the encoded key as an environment variable to your tor container:

services:
  tor:
    image: docker.io/goldy/tor-hidden-service:latest
    restart: always
    environment:
      COOLWEBSITE_TOR_SERVICE_HOSTS: 80:caddy:80
      COOLWEBSITE_TOR_SERVICE_KEY: PT0gZWQyNTUxOXYxLXNlY3JldDogdHlwZTAgPT0AAABg99ec1VfpRgmqBWwYKLzDMKVJLfGusMjXloYUvCFXajiyQyUkUlKAVqyjezFXcH8OtIu/YV5INpiuw9FPu5kn
    volumes:
      - /var/lib/tor:tor
    depends_on:
      - caddy
...

Recreate tor (podman-compose up -d tor), and your website should now be up at the new vanity address. Remember to also update your Caddyfile and restart Caddy.