How to Host a Hidden Service .onion Site on the Dark Web

How to Host a Hidden Service .onion Site on the Dark Web

Warning: the recommendations made here are in reference to hosting your own personal content, not to become a web hosting provider on the dark web. You are responsible for the content that you host (maybe depending on region — I’m not a lawyer), but you don’t want to find yourself anywhere in the distribution pipeline related to the nefarious goods and services of others.

2/29/2020 Edit: I recently created a simplified Dockerfile for this setup, where this whole process can be abstracted to run inside a Docker container without exposing any clearnet ports. I would absolutely recommend running it on a standalone host with the docker0 interface secured using iptables, but to simply get it working, that is not a strict requirement. You can find all of the documentation in my GitHub repo, where you can clone and build it yourself, or alternately, create the document root and pull the image to run directly from DockerHub (lphxl/docktor:latest). All of the instructions are in the in the Docktor GitHub repo:


This guide is tuned toward hosting on Amazon Web Services (AWS) on Ubuntu 18.04. The same things can be achieved using RPM-based distros by substituting the packaging commands and tweaking the instructions as necessary.

This tutorial will cover a few basic areas:

  • AWS Systems Manager
    • Access your EC2 instance from anywhere, without SSH
  • Installing required dependencies
    • Installing a firewall
    • Spoofing the timezone
  • Configuring the Tor hidden service
  • Configuring Nginx
    • Hardening
    • Setting up document root
    • Setting up a server configuration

AWS Systems Manager

AWS Security Groups will allow us to easily disable external HTTP/HTTPS access, since your server will only be listening on localhost. It will also allow us to bypass some of the security lockdown measures like SSH hardening, because it’s not even going to be enabled. “How am I going to access my damn server?” says you, and I say, “ahhh, so now let me introduce you to SSM.”

Click here to read the full documentation on SSM. Here is a modified wrapper I created for connecting to AWS instances with the only dependency being having the aws cli tools installed on the client system:, (and make sure you are set to the correct region in your ~/.aws/config), but that is outside the scope of this tutorial. The README gives instructions of setting up the IAM role, policy, etc.

If SSM is too confusing for you, just use SSH. You should be fine, but I would recommend locking down SSH to specific network ranges or a single external IP via Security Group Inbound Rules. If you open port 22 to the world, I recommend installing failtoban, which is also outside the scope of this tutorial, but a decent tutorial on basic Ubuntu hardening using ufw firewall and failtoban can be found here, which you can use as you see fit.

Install required dependencies

sudo apt update
sudo apt install apt-transport-https

Append to /etc/apt/sources.list:

deb bionic main
deb-src bionic main

Add the Tor PGP public key, and keep it up-to-date:

curl | gpg --import
gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | apt-key add -
sudo apt update
sudo apt install tor

At this point, Tor should be running and enabled on startup, but just to make sure, run sudo systemctl status tor.

If it’s not running and/or enabled, run sudo systemctl start tor; sudo systemctl enable tor.

Enable apt-over-tor:

sudo apt install apt-transport-tor

Edit the lines we added earlier in /etc/apt/sources.list:

deb tor://sdscoq7snqtznauu.onion/ bionic main
deb-src tor://sdscoq7snqtznauu.onion/ bionic main

Now, since Tor is running, see if the package manager tries to give you any errors:

sudo apt update
sudo apt install tor

It should show as already installed, and you should receive no errors.

Install a firewall for good measure:

Because of your Security Groups, nothing is able to hit your server externally, but if you put this on an internal subnet and make any mistakes, lets just make sure that everything is locked down with default Deny rules.

sudo apt install ufw

Here is where you would want to run ufw allow ssh if you are not using SSM to connect to your host.

Enable the firewall:

sudo ufw enable

Spoof your server’s timezone

Not sure if this is really necessary, but we don’t want to possibly leak any information about our location in server headers, so also for good measure, we will spoof our timezone:

sudo timedatectl set-timezone Europe/Zurich
sudo timedatectl set-ntp on

Configuring the Tor hidden service

Backup the original Tor config file:

sudo cp /etc/tor/torrc /etc/tor/torrc.orig

Edit /etc/tor/torrc as root and uncomment the following lines:

#HiddenServiceDir /var/lib/tor/hidden_service/
#HiddenServicePort 80

You should now have something that looks like this:

############### 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 /var/lib/tor/hidden_service/
HiddenServicePort 80

#HiddenServiceDir /var/lib/tor/other_hidden_service/
#HiddenServicePort 80
#HiddenServicePort 22

Restart Tor:

sudo systemctl restart tor

Now go look in /var/lib/tor/hidden_service. You will have a hostname file that tells you the name of your new hidden service (abunchofrandomlettersandnumbers.onion). There should also be a public key and private key file, all of which you will need if you ever migrate this service to a different host. Copy the contents of /var/lib/tor/hidden_service/hostname, because you’ll need it later (and to actually access the website).

Configuring Nginx

Forget Apache, we’re using Nginx, and more specifically the nginx-extras package, because the included Headers More module makes it super easy to inject response headers:

sudo apt install nginx-extras

Edit /etc/nginx/nginx.conf as root, and make sure to uncomment/add the lines below inside the http block.

server_tokens off;
server_name_in_redirect off;
port_in_redirect off;

Set up a document root for your hidden service

We will create a document root, a default landing page, and a blank page where all errors will redirect.

sudo mkdir /var/www/darkweb
echo 'Welcome to the dark web.' | sudo tee /var/www/darkweb/index.html
sudo touch /var/www/darkweb/error.html
sudo chmod 755 /var/www/darkweb

Remove the default server config:

sudo rm -vf /etc/nginx/sites-{available,enabled}/default

Create a new Nginx server config

Create and edit /etc/nginx/sites-available/80.darkweb.conf as root, and add the lines below, replacing abunchofrandomlettersandnumbers.onion with the contents of /var/lib/tor/hidden_service/hostname.

server {
    server_name abunchofrandomlettersandnumbers.onion;
    more_set_headers 'Server: Secure';
    more_set_headers 'X-XSS-Protection: 1; mode=block';
    root /var/www/darkweb;
    index index.html;
    error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html;

    location = /error.html {

This will add a localhost listener on port 80 for your .onion hostname, which serves up the content from the /var/www/darkweb document root that we created earlier and will redirect all errors to the blank error.html page that we created. We also add some headers to hide the fact that this is an Nginx server and protect against very obvious XSS attacks if you were to implement some very stupid JavaScript in the future. Feel free to add an html redirect to your main site to the error.html page.

Make this configuration available to Nginx:

cd /etc/nginx/sites-enabled
sudo ln -s ../sites-available/80.darkweb.conf

Check the Nginx configuration and reload:

sudo nginx -t
sudo nginx -s reload

I have had problems in the past when messing around with document roots, so just for good measure, let’s fully restart Nginx to make sure the settings take effect.

sudo systemctl restart nginx

You should now be able to curl and see your message, “Welcome to the dark web.”

Access your hidden service via Tor

Go ahead and do whatever it is you do to access the dark web on your client machine — preferably fire up your favorite VPN and launch Tails in a VM, open the Tor Browser, and navigate to abunchofrandomlettersandnumbers.onion (or more accurately, whatever .onion doman was listed in /var/lib/tor/hidden_service/hostname on the host you just configured.

Bingo bango. You now have a hidden service on the dark web.

Stay tuned for a later post on how to slightly customize this domain with a vanity version 3 .onion URL, or even a shorter (albeit much more insecure) version 2 .onion URL — which you might even be able to remember without having to bookmark.

Comments ( 5 )

  1. Host an .onion site | 0ddn1x: tricks with *nix
    […] […]
  2. Darkwebhost
    Using AWS is a shame even AWS websecurity is a mess. remembers servers were hack and many creditcards were stolen ...
    • Jamey
      You obviously have no cloud experience and have no idea how AWS works or what you're talking about. Get outta here.
  3. gcvgevcgv
    I examined your docker image file. On restart of docker container the code attempts to create a symlink that already exists and exits with errors. I couldn't get the sed command format to work on the terminal so I rewrote it to "sed -i "s/head -3 hiddenservice.conf | tail -1/ server_name $onion;/" hiddenservice.conf". Otherwise great job.
    • Jamey
      Thanks for this info. I just pushed a commit (here) which should fix the issue by forcing creation of the symlink when is executed. It was just a matter of changing ln -s to ln -sf. Let me know if this helps.

Leave a Reply