[Tutorial] One ghost blog, available on clearnet and as onion service

Hey everyone,

I just wanted to share my tutorial to setup Ghost to be accessible on the clearnet, but also as onion service.


  • One blog to maintain
  • One DigitalOcean instance
  • HTTPS on clearnet
  • HTTP to HTTPS redirect on clearnet page
  • One tor onion service
  • HTTP on onion service

Ghost always redirects port 80 to 443. This breaks the normal TOR service configuration, as we would use HTTP (the TOR network is per default encrypted).
If we use port 443 in our TOR config, it works but every visitor will be greeted with an ugly SSL cert warning. Which is to be expected, because we now serve the certificate with the name of the clearnet site, for example wwww.my-site.com, on an onion site, for example 248080dsfasf.onion.

Setup one Ghost instance for the clearnet and setup a second instance for the onion service but which is linked to the content and database of the clearnet version. The onion service instance can be configured to serve HTTP, while the clearnet version works with HTTP to HTTPS forward (always use HTTPS on the clearnet!)


  • Install Ghost via DigitalOcean template
  • Update installation:
    sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get dist-upgrade && sudo do-release-upgrade

Install TOR services:

  • Link Tor Project | Set up Your Onion Service
  • Verify your Ubuntu release (we need this for the apt sources list):
    *lsb_release -c
  • Install apt transport https
    apt install apt-transport-https
  • Create a new sources list for the TOR packages (my release is Ubuntu focal)
    vi /etc/apt/sources.list.d/tor.list
    and add
    deb [arch=amd64] https://deb.torproject.org/torproject.org focal main
    deb-src [arch=amd64] https://deb.torproject.org/torproject.org focal main
  • add the keys for the TOR sources
    curl https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --import
    gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | apt-key add -
  • Update and install keyring
    apt update && apt install tor deb.torproject.org-keyring
  • Install TOR services
    apt install tor

Configure your TOR services:

  • Edit torrc
    vi /etc/tor/torrc
  • Set your hidden service params. This will create a configuration in the HiddenServiceDir. You can name it whatever you want. The port will be used to redirect incoming traffic (from the TOR network) on port 80 to port 8080.
    HiddenServiceDir /var/lib/tor/your-website-name-here/
    HiddenServicePort 80

Retrieve tor onion name:

  • Restart tor services
    sudo systemctl restart tor
  • Get your onion address
    cat /var/lib/tor/your-website-name-here/hostname
  • Result will look like this:

Duplicate nginx config:

  • Copy site config
    cp /etc/nginx/sites-available/your-ghost-site-ssl.conf cp /etc/nginx/sites-available/your-ghost-site-onion.conf
  • Your new onion site config has to listen to the same port as configured in torrc above, we need to adapt the server_name, root and proxy_pass, for which we need a new local port number:
server {
   # same as in torrc
    listen 8080;

   # your onion service name which we retrieved above:
    server_name nytimes3xbfgragh.onion;
   # the new root for the copy of our clearnet ghost installation.
    root /var/www/ghost.onion/system/nginx-root;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
      # new local port number for the second Ghost instance which serves the onion version


    location ~ /.well-known {
        allow all;

    client_max_body_size 50m;
  • Link it to sites enabled
    cd /etc/nginx/sites-enabled
    ln -s ../your-ghost-site-onion.conf
  • Restart nginx
    systemctl restart nginx

Duplicate the clearnet Ghost installation:

  • As root
    mkdir /var/www/ghost.onion
    chown ghost-mgr /var/www/ghost.onion
    chgrp ghost-mgr /var/www/ghost.onion
  • As ghost-mgr, copy the content
    sudo -i -u ghost-mgr
    cp /var/www/ghost/* /var/www/ghost.onion/
  • Run ghost setup
    cd /var/www/ghost.onion
    ghost setup
  • Change the name to the HTTP(!) onion url (simply changing the config file didnt work for me, so I did it like this)

Edit the config file for your onion service:
*Manually set the new port number for the onion service, to receive requests from nginx.

  • Make sure the contentPath is still linked to the clearnet version of Ghost
  • It should look like this:

vi /var/www/ghost.onion/config.production.json

  "url": "http://nytimes3xbfgragh.onion",
  "server": {
    "port": 2369,
    "host": ""
  "database": {
    "client": "mysql",
    "connection": {
      "host": "localhost",
      "user": "ghost",
      "password": "password",
      "database": "ghost_production"
  "mail": {
    "transport": "Direct"
  "logging": {
    "transports": [
  "process": "systemd",
  "paths": {
    "contentPath": "/var/www/ghost/content"

Start ghost

  • Start ghost via ghost start in the directory of /var/www/ghost.onion
  • It also asked me to create a systemctl entry which I did.

Now you should have a clearnet address:
which redirects you from insecure http to secure https
And you have a tor onion service which is accessible via http:

You can also advertise your onion address to a browser like Brave.
Just add the following line to your nginx ssl config of your clearnet page:

location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;

# add this new header
        add_header onion-location http://nytimes3xbfgragh.onion;

I hope this helps!


Unfortunately this doesnt work. When I add a new entry on the clearnet page, it is not update on the onion instance.
However when I go to my publisher profile on the onion site, I can see the new entry. Clicking it produces a 404 error.
I have to manually restart the ghost instance, then it works.