This winter, rather than tend to my couch potato harvest, I thought it time to revamp my webpage from the mess I threw together in college. After some initial research, I decided Ghost would fit my purposes. It's a minimal, open-source blogging CMS aimed at developers; and it's free if you're willing to host it yourself.

Antique Typewriter on Dark Wood
Photo by Patrick Fore / Unsplash

Why NGINX?

My existing server solution was hosting a single-page bootstrap site via Apache2, and unfortunately wasn't utilizing SSL encryption. So along with the transition to Ghost, I decided to switch from Apache2 over to NGINX. The reasons for switching to NGINX were pretty simple:

  • I like learning new things.
  • I'm a sucker for clean architecture.
  • I had no idea what I was doing when I set up Apache2.

I'll save you the entire history of NGINX, but in my research I found it to be better designed, easier to configure and more stable. I don't think you can go wrong with either, but the lean and mean NGINX was calling my name.

Setting up Ghost

Getting up and running with Ghost was pretty straightforward. In the spirit of hopping aboard the microservice hype-train, I decided to utilize their Docker image. In the simplest of forms, we can get Ghost in one line:

docker run -p 2368:2368 ghost

Done! Now just head to http://localhost:2368 and see Ghost in all its glory.

Tweaking Ghost

Of course, this solution has a number of problems with it:

  1. There is no persistent data. If the container ever goes down then all your blogs, accounts and images are gone with it.
  2. If the container crashes, it won't self-heal. You'd have to manually restart it.
  3. Ghost doesn't know how to communicate with the "outernet." All of the internal links will try to go to localhost, and this will cause problems once we add NGINX into the mix and expose Ghost to the world.

Luckily, we can modify the docker run command to solve these problems:

docker run -p 2368:2368 \
    -v /some/host/folder:/var/lib/ghost/content \
    --restart=always \
    -e url=https://aarondevelops.com \
    --user 1001

I think most of these are self-explanatory, and address the problems above in chronological order. The last flag, --user, sets the user ID for the docker container. On my server, I created a new user just for Ghost and made sure the permissions for the mounted volume made sense.

Setting up NGINX

Just like Ghost, NGINX in the most simple form is pretty easy. Installation is a single line on my Ubuntu 18.04 LTS system:

sudo apt update && sudo apt install nginx

We can then validate that it is up and running with systemctl status nginx. We should see that it's running, but unfortunately that doesn't mean much with nothing configured. We need to do two things, firstly get our SSL certificates in line, and then tell NGINX to use them to start serving up Ghost to all zero of my readers.

SSL Certificates

The full range of SSL setup is beyond the scope of this article, but you'll first need a registered domain and DNS record that points to it. After that, getting certificates is far too easy utilizing certbot. For the purpose of this tutorial, I went with the "certificates only" option because I find doing things the hard way to be the best way to learn. After going through those hoops, you should end up with some certificate files on your computer ending with .pem.

NGINX Config

Getting NGINX configured was no problem. We just need to define two servers, one for the default HTTP port 80 and the other for the HTTPS port 443. The former port will just redirect all traffic to the SSL endpoint. The entirety of the NGINX configuration looks like this:

server {
    listen 80;
    listen [::]:80;
    server_name aarondevelops.com www.aarondevelops.com;
    
    return 301 https://$server_name:443$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name aarondevelops.com www.aarondevelops.com;
    
    ssl on;
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;
    
    location / {
        try_files $uri $uri @ghost-blog;
    }
    
    location @ghost-blog {
        proxy_pass http://127.0.0.1:2368;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}

We can drop this config into the appropriate NGINX directory, found by default at /etc/nginx/sites-available/ and name it whatever you want. To get NGINX to actually care about this config, we need to link to it in the sites-enabled directory. The command will look something like this:

ln -s /etc/nginx/sites-availabe/config.file /etc/nginx/sites-enabled/

Finally, we can restart the NGINX server via systemctl restart nginx. Once it's back up and running, we should be able to go to our domain via your favorite browser and see our brand new Ghost-based website.

Closing Thoughts

NGINX and Ghost make a pretty good combo, and overall it's not hard to get them up and running. After intial setup, you can navigate to the /ghost endpoint to create your admin account, install themes, add images and start writing posts.