How to deploy Onion website on Tor network

How to deploy Onion website on Tor network

Let's create a website that can only be accessed via Tor browser

·

5 min read

Tor and Tor Browser help two groups of users. One who wants to hide their web surfing, and the other who doesn’t expose meta information about their websites. In this tutorial, I’ll briefly work through the steps to deploy my website on the Tor network.

💡
If you want to skip some basic knowledge part and just deploy onion app, goto 'Onionize plain website' Section directly.

Basic concept: Sample with macOS

To understand how it works, we are going to deploy a website using macOS.

brew install tor # to deploy local web server
brew install --cask tor-browser # to access the web server via tor
cd /opt/homebrew/etc/tor/
cp torrc.sample torrc
vi torrc

In torrc, find the location-hidden services section (Line 63 or so). Uncomment HiddenServiceDir and HiddenServicePort, then set lines like below.

...

############### This section is just for location-hidden services ###

## Once you have configured a hidden service, you can look at the
## contents of the file ".../hidden_service/hostname" for the address
## to tell people.
##
## HiddenServicePort x y:z says to redirect requests on port x to the
## address y:z.

HiddenServiceDir /Users/roeniss/.tor/hidden_service/
HiddenServicePort 80 127.0.0.1:8080

...
💡
Don't forget to change 'roeniss' to yours (you can check with echo $USERNAME).
mkdir -p "/Users/$USERNAME/.tor/hidden_service/"
chmod 700 "/Users/$USERNAME/.tor/hidden_service/"
tor # use Ctrl-C to stop
# outputs: ...
# outputs: Jun 18 04:40:03.000 [notice] Bootstrapped 100% (done): Done

Before browsing this webpage, we need to deploy a simple web server with another terminal session.

cd "/Users/$USERNAME/.tor/"
cat <<EOF > index.html
<h1>Hello Tor!</h1>
EOF
python3 -m http.server --bind 127.0.0.1 8080
# output: Serving HTTP on 127.0.0.1 port 8080 (http://127.0.0.1:8080/) ...

At this time we can see "Hello Tor!" on visitinglocalhost:8080 with Chrome. But that's not what we want! Let's visit with onion domain name.

cat "/Users/$USERNAME/.tor/hidden_service/hostname"
# output: buoabp5fkdwrtp2sutk4yhigu73btjuwvqdudxs45h6ezeivinjgliqd.onion

So onion domain is automatically made when we execute tor. You might think this name is weird, ugly, and hard to remember. Just take it. Would you feel better if you know that even Facebook has such a name?

The Facebook onion address located at facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion (formerly facebookcorewwwi.onion) is a site that allows access to Facebook through the Tor protocol, using its .onion top-level domain. (wiki)

Honestly, the former name looks simpler, but we are late.

Anyway, let's copy the previous output. It's not available on Chrome, but Tor finds my website. Cool.

Onionize plain website

With onionize, we can add onion domain to any website in docker container, meaning that we can ship our web into tor network.

First, let's make a sample container with the previous python webserver.

mkdir onionize_test
cd onionize_test

cat <<EOF > index.html
<h1>Hello Tor!</h1>
EOF

cat <<EOF > Dockerfile
FROM python:latest
COPY index.html index.html
ENTRYPOINT ["python", "-m", "http.server", "--bind", "0.0.0.0", "80"]
EOF
docker build -t testapp .

Then deploy it in the isolated network.

export ONIONSERVICE_NAME="hello"
export ONIONSERVICE_PORT="8080"

docker network create \
    -o "com.docker.network.bridge.enable_ip_masquerade=false" \
    isolated_net

docker run \
    --name testapp \
    -d -p 80:80 \
    -e ONIONSERVICE_NAME=$ONIONSERVICE_NAME \
    -e ONOONSERVICE_PORT=$ONIONSERVICE_PORT \
    --net isolated_net \
    testapp

docker run \
    --name onionize \
    -d -v /var/run/docker.sock:/tmp/docker.sock:ro \
    torservers/onionize # if m1 macBook, use 'rriclet/onionize:arm32v7'

docker network connect isolated_net onionize

docker logs -f onionize
# output: ...
# output: Jun 18 16:22:51.000 [notice] Bootstrapped 100% (done): Done
# Ctrl-C
docker exec onionize cat /var/lib/tor/onion_services/$ONIONSERVICE_NAME/hostname
# output: gukodspgllguu4amcka6yhg6vng45f3tkfor5kc6r24spey6fzoo5wqd.onion

Unfortunately, this image doesn't support custom domain option.

What if production server

I would deploy a real server with docker compose with two containers - one for my app, the other for tor binaries - and locate the containers in private network, where no internet gateway exists. With this approach I would feel comfortable.

Sidenote: Take a friendly domain

Onion domain is always 56 characters. So it's hard to make the very domain we are looking for. But there are some tools that can fix some small length of prefix for us.

Did you remember what Facebook's onion domain is? We will make something like that.

docker run --rm -it --entrypoint bash ubuntu
sudo apt update && sudo apt install -y git
sudo apt install -y gcc libc6-dev libsodium-dev make autoconf # see instruction in mkp224o repository
git clone https://github.com/cathugger/mkp224o
cd mkp224o
./autogen.sh
./configure
make
./mkp224o hello

If you don't want to use Docker, try Multipass.

After finishing those commands, you will see output like this:

sorting filters... done.
filters:
    hello
in total, 1 filter
using 1 thread
helloswoujztsd3h3kh4tela7c5mqmm5xxw3fjagvjjtndvrkzhoyuid.onion
helloih7zaonhtub7mal2damewken6preqrk64khgbv6d7zcrxc5dhid.onion
helloyvktabg5ohycft3dyuyckexwmifbir5zrfsxrgezmin3a2bxiid.onion
# To stop searching, Ctrl-C.

Basically, what mkp224o does is to search available onion domains which are started with the given keyword. You can try with longer keywords but it will take more time.

Stop searching and you will find that the program already makes directories for you with those names.

ls
# output: ...
# output: base32_to.c       helloswoujztsd3h3kh4tela7c5mqmm5xxw3fjagvjjtndvrkzhoyuid.onion  worker.c
# output: ...

there are three files in the folder - hostname, hs_ed25519_public_key, hs_ed25519_secret_key. We will extract it.

Exit from the VM, then copy them.

cd ~/.tor
docker cp frosty_chaplygin:/mkp224o/helloswoujztsd3h3kh4tela7c5mqmm5xxw3fjagvjjtndvrkzhoyuid.onion/hostname ~/.tor/hidden_service/hostname
docker cp frosty_chaplygin:/mkp224o/helloswoujztsd3h3kh4tela7c5mqmm5xxw3fjagvjjtndvrkzhoyuid.onion/hs_ed25519_public_key ~/.tor/hidden_service/hs_ed25519_public_key
docker cp frosty_chaplygin:/mkp224o/helloswoujztsd3h3kh4tela7c5mqmm5xxw3fjagvjjtndvrkzhoyuid.onion/hs_ed25519_secret_key ~/.tor/hidden_service/hs_ed25519_secret_key

If you restart tor command at this time, you can access the local webserver with helloXXX domain!

Further readings

If you have any trouble following this article, feel free to add a comment! :D