Fixing Ghost on a DigitalOcean Droplet

Hello! I recently moved my blog (you're reading it!) from the official Ghost hosting to DigitalOcean. I can spend a moment talking about why I made the transition: It's hella fucking cheaper and way easier now. That was quick, so I guess now is where I talk about how I actually did it.

Strangely, it went quite according to the documentation. I signed up for a DigitalOcean account, and added my domain name first before I did anything. There was an option to simply add all the gmail mail servers, so I did that as well.

DNS QuickStart

I had to boot up my chromebook's chroot and install the Windows Subsystem for Linux (WSL) on my desktop because I needed to make SSH keys to add to DigitalOcean.

Adding a new key to your DigitalOcean account

Making the keys was easy, done and done. I added a password to the keys as well because it was important to do, right? After I made them I uploaded them through the dashboard.

ssh-keygen -t rsa -b 4096 ~/.ssh/id_rsa

Now I have some keys I can attach droplets at creation so that the root account never has to have a password. Now I just need the droplets. So that goes by pretty quick too. The creation-process is very quick. In the image below, I selected 'One-click apps' and selected Ghost. I also selected the cheapest droplet ($5/mo) because I know that my blog doesn't take much to run.

I had to wait for my droplet to spin up and get assigned an IP address. At this point is where this blog post becomes a work of fiction because I ran into a host of tiny issues (ones partially of my creation and partially the universe's). I'll go over them because they are all really interesting little things.

I wanted to make SSH config files so I would be able to just jump straight into my droplet. You can do it with whatever editor you want, but this is what mine ended up being.

Host ghost
    Hostname <ip>
    User root
    Port 22
    Identityfile ~/.ssh/id_rsa

I ran into issues with my config file on my Windows computer. This is what happened when I tried to ssh ghost:

malachite@localhost:~$ ssh ghost
Bad owner or permissions on /home/malachite/.ssh/config
malachite@localhost:~$ ls -alh .ssh
total 8.0K
drwx------ 1 malachite malachite  512 Jul 31 18:40 .
drwxr-xr-x 1 malachite malachite  512 Jul 31 18:40 ..
-rw-rw-rw- 1 malachite malachite   99 Jul 31 18:40 config
-rw------- 1 malachite malachite  222 Jul 31 16:42 known_hosts

Do you see the problem? Because I sure as hell didn't until I googled "bad owner or permissions" and came across this gist. As it turns out, ssh wants your config file to have very specific permissions. As a quick primer for how permissions in unix work, here's a comic from Julia Evans that does a much better job that I can in the space I have.

Julia Evans' comic

According to the gist, linked previously, the proper permissions for config should be 644, which translates to -rw-r--r--. The owner can read/write, and everyone else can just read it. This is quickly solved with a chmod 644 .ssh/config. With the permission fixed I can just pop into my droplet with a quick ssh ghost.

I followed the configuration steps from the community tutorials. It did not work. I ran into issues that no matter what I did I could not access the site with my domain name, but could on either http or https with the plain IP address. Specifically I was getting a 504 Bad Gateway error when I attempted to access with my domain name, which told me that it was something to do with the nginx configuration and not DNS. Clearly I am reaching my server, but my server can't handle the request.

When I initially loaded my droplet and signed into it, there was a message on screen that said that it was setting things up and that it would update Ghost if updates were available. I happen to recall that the DigitalOcean droplet comes installed with Ghost 1.24.9, but it updated to Ghost 1.25.3.

Semantic Versioning is a version number system that contains three positions. The first is MAJOR version, the second is MINOR version, and the third is PATCh version.

  1. MAJOR version when you make incompatible API changes,
  2. MINOR version when you add functionality in a backwards-compatible manner, and
  3. PATCH version when you make backwards-compatible bug fixes.

This had nothing to do with the issue, but made me consider the possibility that the update broke a dependency. So I updated stuff and it worked. Here's the steps to avoid encountering this.

sudo apt-get update
sudo apt-get upgrade
sudo apt-get autoremove
sudo -i -u ghost-mgr
cd /var/www/ghost
ghost update
ghost restart

ghost update shouldn't do anything, but the ghost restart is really important because it reinitializes things. The upgrade and restart did the trick and my droplet was back online.

Okay, it's running, anything else?

Well part of the reason I wanted to move to self-hosting wasn't just because it was cheaper but because I could also use it to host files. I wanted to host my resume on my server so people could just click a link and get it right there in front of them.

From my Windows PC:

scp /mnt/c/Users/malachite/Desktop/resume.pdf ghost:/var/www/ghost/content/images/2018/07/devon_taylor_resume.pdf
ssh ghost
sudo chown ghost:ghost /var/www/ghost/content/images/2018/07/devon_taylor_resume.pdf
sudo chmod 644 /var/www/ghost/content/images/2018/07/devon_taylor_resume.pdf

I knew that since I was going to be copying it into the content folder as root, I needed to change it to the owner. All the files in /var/www/ghost/content is owned by ghost:ghost, so I logged into the server to give them permission of the file. I also had to change the permissions since when the file was copied in it came with some wonky permissions that made the pdf an executable.


While I was writing this post Ghost crashed causing it to return 404 errors, right around the time I was saying that everything was working beautifully and I solved all the problems. I was mostly right.

Edit: The 404 errors were caused by Chrome sporadically choosing to use an old DNS record that targeted my website at a previous IP address where it is no longer located. After struggling with the developer tools in Chrome a little I managed to force it to ignore its cache and do a fresh lookup. So I configured things correctly the first time, Chrome was just gaslighting me.

Support the Author

Devon Taylor (They/Them) is a Canadian network architect, security consultant, and blogger. They have experience developing secure network and active directory implementations in low-budget and low-personnel environments. Their blog offers a unique and detailed perspective on security and game design, and they tweet about technology, security, games, and social issues. You can support their work via Patreon (USD), or directly via ko-fi.