DEV Community

Cover image for Getting started with Tor hidden services
Stepan Vrany
Stepan Vrany

Posted on

Getting started with Tor hidden services

This will be happening more and more frequently in the online world: people get silenced. If you're a ruler, it's tempting to selectively deliver only certain information and let's face it - surface web is perfectly equipped for such scenarios. Providers of services have too much to lose if disobey. This is just how power works. But that's a bit different topic...

Today we're gonna talk about the world without centralised authorities, the world of Tor hidden services.

Enter the .onion!

In Tor we don't use standard domain names like cz or eu. We use specialised .onion generic name that is resolvable only in Tor network. Compared to standard domain names it has one major difference beyond the scope of the visibility - there's no vendor where you have to purchase a name for you service.

This is how it works - you create a hidden service, a new keypair is generated and part of the public key is then used as your .onion name. You can read more about the mechanism in this post.

Please read previous 2 paragraphs again - there's no authority that's responsible for the allocation of names. And when there's no authority - you can't approach it and make it obey with the force 👊

Then you just point Tor daemon to your webserver and voila - everyone with Tor browser (or alternative configuration with SOCKS5 proxy) can access your hidden service!

Now let's create our first hidden service. That's gonna be fun.

Part I: server

We need some server first. It can be anything, you can use tiny Raspberry Pi or you can purchase some VPS from Public Cloud providers. I don't have any specific recommendations here. Server in your homelab is more decentralized, on the other hand VPS is easy to obtain. It's up to you.

I'm gonna use VPS purchased from Digital Ocean pre-installed with the latest LTS Ubuntu.

Image description

In a couple of seconds I can connect with ssh there and proceed with the configuration!

Part II: application

In this tutorial we'll be serving some dummy content from the application written in Go. For the sake of simplicity I'm gonna use some snippets from the Fiber framework's homepage. So here we go, this is our application.

package main

import ""

func main() {
    app := fiber.New()

    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Hello, World 👋!")

Enter fullscreen mode Exit fullscreen mode

Once again, this is Go but you can write your application in any available tool. And of you want to serve static content - use Caddy or Nginx.

So now I'm gonna build and test the app:

go mod init main
go mod tidy
go build main.go
curl localhost:3000
Enter fullscreen mode Exit fullscreen mode

This is what we get as the output:

Hello, World 👋!
Enter fullscreen mode Exit fullscreen mode

Part III: supervision

In the modern operating system we can always use some way how to supervise your applications. This is extremely important and I'm gonna give you one specific example: when your server restarts, you want to make sure that applications starts together with the server.

In the case we use Ubuntu so systemd service unit needs to configured.

So let's move the application to some well-known path

mkdir /opt/app
mv main /opt/app/main
Enter fullscreen mode Exit fullscreen mode

and create the systemd service unit in /etc/systemd/system/app.service file:



Enter fullscreen mode Exit fullscreen mode

Now we can enable and start the service with systemctl commands:

systemctl enable app
systemctl start app
Enter fullscreen mode Exit fullscreen mode

Status of the service can be verified by invoking systemctl status app:

● app.service - App
     Loaded: loaded (/etc/systemd/system/app.service; enabled; vendor preset: enabled)
     Active: active (running) since Wed 2022-03-02 18:08:29 UTC; 2s ago
   Main PID: 2289 (main)
      Tasks: 4 (limit: 1132)
     Memory: 1.3M
     CGroup: /system.slice/app.service
             └─2289 /opt/app/main
Enter fullscreen mode Exit fullscreen mode

Part IV: Tor daemon

All the stuff we were doing till now was pretty much the same as you'd do for standard surface web app, right? So let's make it special. To do so we need to install Tor daemon first.

In this Ubuntu 20.04 we just need to create a new apt source in /etc/apt/sources.list.d/tor.list

deb     [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] focal main
deb-src [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] focal main
Enter fullscreen mode Exit fullscreen mode

add public key to the keyring

wget -qO- | gpg --dearmor | tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null
Enter fullscreen mode Exit fullscreen mode

and install the package:

apt-get update
apt-get install tor -y
Enter fullscreen mode Exit fullscreen mode

If you're using different operating system - check out the official documentation. You know the drill I guess 😁

As the result we have Tor daemon running!

systemctl status tor
● tor.service - Anonymizing overlay network for TCP (multi-instance-master)
     Loaded: loaded (/lib/systemd/system/tor.service; enabled; vendor preset: enabled)
     Active: active (exited) since Wed 2022-03-02 18:16:30 UTC; 1min 28s ago
   Main PID: 2959 (code=exited, status=0/SUCCESS)
      Tasks: 0 (limit: 1132)
     Memory: 0B
     CGroup: /system.slice/tor.service
Enter fullscreen mode Exit fullscreen mode

Part V: pointing Tor to our app

And this is the very last part of this tutorial. Tor just needs to be configured to "advertise" our application to the network.

Open the main configuration file /etc/tor/torrc and add following lines to the respective part of the configuration.

HiddenServiceDir /var/lib/tor/app/
HiddenServicePort 80
Enter fullscreen mode Exit fullscreen mode

The second line basically says that incoming traffic on port 80 should be routed to localhost:300. And this is the port where the simple app listens.

Tor daemon needs to be restarted first with systemctl restart tor command and whent it's up again we can inspect /var/lib/tor/app/ directory - the home directory of our hidden service.

ls /var/lib/tor/app/ -lah
total 24K
drwx--S--- 3 debian-tor debian-tor 4.0K Mar  2 18:22 .
drwx--S--- 4 debian-tor debian-tor 4.0K Mar  2 18:23 ..
drwx--S--- 2 debian-tor debian-tor 4.0K Mar  2 18:22 authorized_clients
-rw------- 1 debian-tor debian-tor   63 Mar  2 18:22 hostname
-rw------- 1 debian-tor debian-tor   64 Mar  2 18:22 hs_ed25519_public_key
-rw------- 1 debian-tor debian-tor   96 Mar  2 18:22 hs_ed25519_secret_key
Enter fullscreen mode Exit fullscreen mode

The last 2 lines are interesting, this is the key pair I was talking about. Please note that private key needs to be kept in secret, anyone with access to this key can use your onion name for some nasty s**t! And when we talk about the name, let's just check the contents of the hostname file.

cat /var/lib/tor/app/hostname
Enter fullscreen mode Exit fullscreen mode

And this is, ladies and gentlemen, a domain name we can use from Tor browser. Let's do it.


Install Tor browser or Brave (Brave needs to be launched in specialised Tor mode) and enter onion name to the address bar.

Image description

This is the service we were working on! It looks just like any other web application but it has one major feature - putting this down requires much more effort that the surface web equivalent.

Now you can share your fantastic content even with the folks who lives in less free countries 🔥🔥🔥

Do you need any help with Tor hidden service? Don't hesitate to contact me. I'm 100% committed to make world a better place through the free speech and better privacy ❤️

Title image:

Top comments (1)

felipebrunet profile image

Thanks very useful! Do you know how to setup a fiber server on a remote VPS to do just to forward a URL get request?
I am running a home service via Tor, and I would like a mobile app to access it, the problem is that the app only access clearnet urls. Can I set up a remote VPS that receives get requests and forwards them to my onion home address?