Ghost: making me learn nginx on Ubuntu

So the time finally came: Ghost 1.0 is out. My setup is a little more complex than the vanilla one: on this site, there are two blogs next to one another: one here at /blog, and a separate one at /sk. I also have other Ghost blogs in different places and domains. This post documents the stuff I had to learn, and what I ended up using, in case I need a refresher.

For various reasons, I run them in Azure. Setting it up was never easy - I'd wasted many, many hours on failed attempts and ended up patching it together somehow, using Ghost-Azure. When I got it running, I never even tried updating it, thinking I'd wait until 1.0 rolls around, and it would hopefully have an automated update process like WordPress.[1]

When the big day came, I was again faced with a dilemma: do I keep running on Azure Web Services, or migrate to a Linux VM? In the end, I'd decided that using MySQL (which now is the recommended default for Ghost, instead of SQLite) and keeping Ghost up-to-date would be more pain than gain in a setup like the current one, so I decided to make the leap.

I also decided to stick to the recommended defaults, when it comes to software.[2]
Unfortunately, I had no previous experience with nginx at all, and no experience with creating a production-ready deployment with Linux servers or Node.

Fortunately, there are some fantastic tutorials on DigitalOcean: they cover MySQL installation, nginx installation, Node.js installation and many other interesting topics. The tutorials tend to just work.

When I used the ghost install command, it offered to create an SSL cert with Let's Encrypt; this failed, but DigitalOcean also has a tutorial for Let's Encrypt.. I had an issue with the certbot - on one domain it worked correctly, but for the other, it said there is no nginx configuration it can modify, even when there was one. But it was easy enough to fix by hand.

Now I'm not saying I am suddenly an expert, but I got all of those parts working and (hopefully) secured. One of the most pleasant surprises for me was how easy to configure nginx was. I got everything working in maybe half a day, even when my specific case was not in any tutorial and required actual thinking. 🙃 I was a little afraid of the redirect - because even though it it is very simple, I've managed to burn myself with redirects both on IIS and Apache before, and debugging those issues was never pleasant. But again, nginx was very friendly.

Details of my setup:

  1. I chose a root folder for the entire domain, i.e. /var/www/zblesk-net.
  2. In it, there is a folder called wwwroot that handles the documents in the domain's root.
  3. There are subfolders for Ghost blogs, blog and sk. In each of those, I did a ghost install.
  4. With the install done, I edited the nginx .conf and added the reverse proxies pointing to the two Ghost blogs. (The config sections only differ by root folder and port number.) I also added listening on the SSL ports.[3]
  5. After changing the DNS settings, I used certbot to generate a SSL certificate, set up a cron job to renew them, and updated the Diffie-Hellman config to use a longer key. (That's the ssl_dhparam line in the config file.)
  6. Adding the root redirect (the location / part) was the last finishing touch.

This is the relevant part of the resultant config:

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443;

    server_name zblesk.net
    root /var/www/zblesk-net/wwwroot;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    location / {
        allow all;
	rewrite ^/$ /blog last;
    }

    location /blog {
        root /var/www/zblesk-net/blog/system/nginx-root;
        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;
        proxy_pass http://127.0.0.1:123;
        proxy_redirect off;
    }

    location /sk {
        root /var/www/zblesk-net/sk/system/nginx-root;
        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;
        proxy_pass http://127.0.0.1:124;
        proxy_redirect off;
    }

    location ~ /.well-known {
        allow all;
    }

    client_max_body_size 50m;

    ssl_certificate /etc/letsencrypt/live/zblesk.net/fullchain.pem; 
    ssl_certificate_key /etc/letsencrypt/live/zblesk.net/privkey.pem; 
    include /etc/letsencrypt/options-ssl-nginx.conf; 

    if ($scheme != "https") {
       return 301 https://$host$request_uri;
    } 
}

I wasn't able to get a usable FTPS up and running quickly enough, so I abandoned it and ended up using SFTP with WinSCP. (I use MTPuTTY for SSH.)

So far, it has worked well. UptimeRobot didn't notice any outages, either. And the new Ghost with its new MarkDown editor and "Night Mode" is a blast.
Still on my to-do list: figuring out how to best do backups.


  1. It doesn't. But at least the ghost-cli utility is nice, and I only broke a blog with it once. ↩︎

  2. I'd tried to get a beta or RC version of Ghost running in various environments, including Azure Web Services, a Windows VM, or 2 different Linux distros, but it only worked when I tried it on the recommended Ubuntu server. (I'm sure the only thing at fault is my lack of linux admin skills, not the software I'd used.) ↩︎

  3. By that point I was really glad that I'd learned some vim some time ago. ↩︎