How to deploy Onion website on Tor network
Let's create a website that can only be accessed via Tor browser
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.
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
...
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
The official explanation really helps me to understand how onion works even though I didn't manually expose my router in the house: https://community.torproject.org/onion-services/overview/
The official guide introduces how to manage multiple app once: https://community.torproject.org/onion-services/setup/ (see step 5)
This article introduces some useful tips: https://riseup.net/en/security/network-security/tor/onionservices-best-practices
If you have any trouble following this article, feel free to add a comment! :D