Setting up Ghost with a dynamic IP on RHEL 8

Setting up Ghost with a dynamic IP on RHEL 8

I wanted to start a blog and here we are. This blog you are reading right now is using Ghost and is completely self-hosted. The process was quite painless but there were a few hiccups along the way.

In this post, I will be going through the steps that I did to get Ghost up and running. My existing infrastructure includes an Nginx reverse proxy and a dynamic IP that I get from my ISP.

Ghost has a great guide on installing Ghost on Ubuntu. The only thing is that I like RHEL and dislike Ubuntu. So I set up an RHEL 8 VM and followed the instructions.

First, we are tasked with installing Node. Ghost does not support node v16.x yet so let's install node v14.x  from NodeSource:

curl -fsSL https://rpm.nodesource.com/setup_14.x | sudo bash -

Then I installed the MySQL server with

sudo yum install @mysql

After that, I ran mysql_secure_installation.

Installing Ghost CLI and then installing Ghost itself was quite pleasant with the help of the guide. The Ghost CLI setup does not detect Nginx correctly when using a non-Debian distro so I had to do that myself. The Nginx configuration used by Ghost CLI is described here.

Now let's talk a bit about my network setup. My server resides in my house and as such, I don't have a static IP from my ISP. I've been using Duck DNS as my dynamic DNS for a while now.

I have a couple of subdomains that are pointed towards Duck DNS via CNAME records but using a CNAME record at the root of the domain is prohibited.

Gratefully my DNS provider offers a custom record called ALIAS. This functions like a CNAME but in the background, the DNS provider goes through the DNS tree until it encounters an IP.

Now that I had a domain pointing at the right IP I just needed to proxy the traffic to Ghost. My gateway has been configured to forward 80 and 443 to another VM that I use to host a couple of other HTTP Projects. But for Ghost, I had made a completely new VM.

I think it could have been possible to expose Ghost to the network by editing the host parameter inside config.production.json but as I think I'll be adding other web services to this VM I decided to configure Nginx to act as a reverse proxy and then chain that to the "main" Nginx reverse proxy that I have forwarded from my gateway.

Network diagram of my setup

I started with making a new configuration onto the Main Proxy.

server {
  server_name serverspike.io;
  listen 80;
  location / {
    proxy_set_header Host $host;
    proxy_pass http://192.168.1.13/;
    proxy_redirect off;
  }
}

The next step was to run certbot. Certbot modifies the configuration file somewhat but it should now work with HTTPS traffic. I tested this by running http-server on 192.168.1.13 and it worked! Now we just needed to configure the Ghost VM's Nginx instance.

location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    
    proxy_pass http://127.0.0.1:2368;
}

client_max_body_size 50m;

The most important line here is: proxy_set_header X-Forwarded-Proto https;

If that header is omitted or set to $scheme then Ghost thinks that we are running on bare HTTP and it proceeds to go into a redirect loop. With my current setup, only the outbound proxy uses HTTPS. The traffic between the proxies and between the inner proxy and Ghost is unencrypted.

In the future, I may also secure the traffic between the proxies so that the main proxy decrypts and then re-encrypts the data before sending it.

This works! Almost. For some reason Ghost had decided to change its port to 2369 and I had no idea until I ran netstat -tulpn. One could change the port in config.production.json but I chose to edit it on the Nginx side. After restarting Nginx everything worked!

Setting up Ghost was a pleasure and I'm looking forward to seeing what it can provide. Cheers!