How to update Ghost on a v15.x TurnKey Ghost appliance

In this post, I'll cover the process required the first time you update the Ghost blog software, on a TurnKey Linux Ghost appliance. This applies equally, not matter what platform it's running on; from self hosted VM, to the Hub. Once you've run through this tutorial, in future, you'll be able to simply run:

su - ghost_user -c "ghost update"

If you'd like to get straight in, then please skip straight to where the action starts. If you'd rather take your time and read all the preamble and background, please read on.

This post was inspired by a recent support request in the forums. Fritz has been running the TurnKey Ghost appliance for a while now. Beyond hitting the recent Let's Encrypt issue with Confconsole and a few issues he's had while climbing the learning curve, he said that he's been quite happy with it.

However, he noted that he'd like update the Ghost software itself. But when he ran the commands recommended in the Ghost docs, he was getting an error. I gave him the command that I was fairly sure should "just work", but he reported that it didn't... :(

So with the expectation that it was something minor, I decided to put aside a few minutes to look into this further. Unfortunately, the minutes turned into hours. So I figured I may as well turn my lemon into lemonade and write it all up as a blog post style step-by-step tutorial.

For the next release, we'll try to improve the set up to bring it more into line with the upstream instructions and thus hopefully making it easier for TurnKey users in the future.


It seems that our current Ghost install has diverged somewhat from the one that the Ghost developers now recommend and support. So these instructions should assist you to get to a state where you can use the ghost cli tool to update and maintain your ghost instance. But first some context of why we do things the way we've done them.

By default, TurnKey installs Ghost as the 'node' user. We use a generic 'node' user to allow us to have some common code across all NodeJS applications. We also have permissions quite locked down (most of the ghost files are owned by the 'root' user by default). This does have a security advantage in some respects. E.g. if a hacker were to hack into your Ghost system front end, the amount of damage that they can do is quite limited (because most files are owned by root). OTOH, as many of the commands need to be run as root, it also means that any malicious NodeJS code that may have crept in (e.g. in NPM libraries) will also be running as root.

A system such as Ghost has the competing desires to be both user friendly and secure. As security is a continuum, rather than an on/off switch, compromises are always required. Usually the compromise of locking things down, is at the cost of ease of use and/or user friendliness, particularly for newer, less experienced users. Getting that balance right is a constant battle, but an important one to keep tweaking the dials on IMO.

So there are pros and cons for both set ups. Personally, I still prefer to have root owning stuff. Then, as a system administrator, I can have control over what runs as root. I have to initiate any commands myself, the node user can only do a limited set of tasks without my explicit intervention.

However, in the case of Ghost, that's something of a moot point, as Ghost is designed to be run as a sudo user. It will no longer let you run as root. It's also worth being aware that Ghost is primarily designed for installation on Ubuntu. By default, Ubuntu has sudo installed and root disabled. Whilst I agree that on a desktop system, using sudo (and having root disabled) has serious security advantages. On a server though, not so much... Anyway, that debate continues to rage, so I won't get bogged down in that right now.

So here's how to change the default TurnKey Linux Ghost install and config to bring your Ghost install more into line with how the Ghost developers have designed things to be. And update to the latest Ghost version (currently 3.0.3 at the time of writing).

Important note for non root users

Generally, the default for TurnKey appliances is to log in as root user. However, some users may be logging via an alternate user account. So before we start, please note that all the following commands expect that you are logged in as the 'root' user.

If for some reason you are not logged in as root, then you almost certainly already have sudo installed and configured. So please either enable root and log in as the root user. Or alternatively, get a root shell like this:

sudo su -

Install sudo

As hinted above, by default, most TurnKey installs don't include sudo. But it's simple to install:

apt update
apt install sudo

Set up a new user

Whist it's possible to leverage the 'node' user, it's probably best to just create a new user. I'm going to call mine 'ghost_user'. You can name yours something else if you want, although please note that a 'ghost' system user account already exists; that is created by Ghost for internal usage. Do not attempt to use that account. If you use a name other than 'ghost_user" you'll need to adjust all the following instructions.

It's also worth noting that I'm going to use the useradd command to create my new user. useradd is a powerful low level command that allows you to create a new user account with a very specific set of qualities. The generally preferred method to create a new "normal" user is to use the more user friendly adduser command. But it also has a very clear set of defaults that may not always be appropriate. With a clear understanding of the exact user config we want for this new user, useradd is the preferable tool in this case:

Add a new ghost_user

useradd ghost_user -MN -d /opt/ghost -s /bin/bash -g node -G sudo

This will create a new 'ghost_user' account, with /opt/ghost (the Ghost install directory) as it's "home directory". It will be a will be a member of the "node" group by default, but also a member of the 'sudo' group (so the new user can use sudo). For full and explicit explanation of the switches I'm using here, please see explain shell.

Then set a password for your new user. Note that this will be a "normal" user with full sudo privileges, who can log in via SSH etc, so make it a good password!:

Set a good ghost_user password

passwd ghost_user

As an aside, if you wish to block SSH login for the new 'ghost_user' (advised, unless you plan to log straight in as this new user for future Ghost maintenance tasks), then you can do that like this:

Disable SSH login (optional)

echo "DenyUsers ghost_user" >> /etc/ssh/sshd_config
systemctl restart ssh

So we can have a nice terminal (among other things) when we log in as this new user, let's also copy across the default user config files:

Copy across default user config files

find /etc/skel/ -maxdepth 1 ! -wholename /etc/skel/ -exec cp -Rt /opt/ghost/ {} +

For explanation of what this command does, please see explain shell again. Note that the exclamation mark ('!') means 'not', so essentially the opposite of the match.

Finally, ensure that the new user owns all the ghost files, with the exception of /opt/ghost/content (that needs to be owned by the 'ghost' system user). We also need to ensure that the permissions are all set correctly:

Set up permissions

chown -R ghost_user:node /opt/ghost
chown -R ghost:ghost /opt/ghost/content
find /opt/ghost -type d -exec chmod 00775 {} \;
find /opt/ghost ! -path "/opt/ghost/versions/*" -type f -exec chmod 664 {} \;

Running updates

Install latest ghost-cli

So next we'll install the latest ghost cli tool:

npm i -g ghost-cli@latest

Note, that you could install ghost cli for the new ghost_user instead, but you would need to add the local user NPM bin path ('/opt/ghost/.npm-packages/bin/'). Also, as Ghost itself runs under a separate 'ghost' user account, I think that having a global/root install is the way to go.

Now all we need to do is log in as our new user and do our updates. If you didn't disable SSH for the new user, you could log out of your current session and log back in as this user (e.g. via SSH). Or you can just use 'su', like this:

Log in as ghost_user

su - ghost_user

You should get a prompt that looks like this:

ghost_user@ghost ~$

Note that the tilde ('~') is short hand for $HOME (i.e. your home directory). So in the case of our new ghost_user, this should be /opt/ghost. To double check, use the 'pwd' command (stands for "print working directory", i.e. print the current directory). You should see this:

ghost_user@ghost ~$ pwd

First, I suggest running 'ghost doctor' to ensure that all is well. Do that like this:

Run 'ghost doctor' (optional)

ghost doctor

Note, that as TurnKey is based on Debian, and Ghost technically only supports Ubuntu, you'll need to skip the OS check (type y at the prompt). So assuming all is well, your results should look like this:

✔ Checking system Node.JS version
✔ Checking logged in user
✔ Ensuring user is not logged in as ghost user
✔ Checking if logged in user is directory owner
✔ Checking current folder permissions
System checks failed with message: 'Linux version is not Ubuntu 16
or 18'
Some features of Ghost-CLI may not work without additional
For local installs we recommend using `ghost install local` instead.
? Continue anyway? Yes
System stack check skipped
ℹ Checking operating system compatibility [skipped]
✔ Checking for a MySQL installation
+ sudo systemctl is-active ghost_localhost
Instance is currently running
ℹ Validating config [skipped]
✔ Checking folder permissions
✔ Checking file permissions
✔ Checking memory availability

Next up is running the Ghost update itself. Before you do anything more though, it's highly recommended that you double check the major changes noted on the Ghost Upgrade FAQ The breaking changes between major versions are noted under "Major Version Changelog".

If you want to double check the latest version available:

Check for latest version (optional)

ghost check-update

Or, you could just install the latest update like this:

Install the update

ghost update

For other 'ghost' commands, please see the Ghost CLI docs.

After running that, everything reported as ok for me. And I double checked by rerunning 'ghost doctor' & 'ghost version' to ensure all was well and I had the latest version.

All looks good, so I exited back to the root account by simply running:


Note that if you logged in via SSH as the 'ghost_user' then running 'exit' will exit the SSH session.

Final step

Despite everything reporting as ok. When I browsed to my Ghost site, I discovered a "502 Bad Gateway" message?! Turns out I just needed to manually start the ghost_localhost service:

Restart ghost service

systemctl start ghost_localhost.service

But now another issue...

After the update, I discovered another issue. It appears that for some reason, some of the links in my Ghost site now redirect to 'localhost:2368' (which obviously won't work). TBH, I'm not 100% sure why that happened, but I can only assume that the upgrade process changed something. It probably warants some closer inspection (perhaps there is something that we can adjust during the initial install to eliminate that issue), but for now I'm just going to solve the immediate issue on the local server.

The fix was pretty simple though. Simply edit the value of URL in the config file, to be in the form SCHEMA://YOUR_DOMAIN/ e.g.

Alternatively, you can use this sed line (substitute YOUR_DOMAIN for your actual domain and swap http for https if you don't want https):

sed -i '/"url":/ s|:.*|:"https://YOUR_DOMAIN/"|' /opt/ghost/config.production.json

That's all folks

Hopefully this tutorial on updating Ghost in the v15.x TurnKey Ghost appliance was useful. If you encounter any issues or anything isn't clear, please post below in the comments. Or alternatively open a new thread in the forums.


Jeremy Davis's picture

I hope you don't mind but I reformatted your post a little to make it display a little nicer. And it hopefully makes your mistake a little more obvious.

Before I go on, I'd like to thank you for your kind words on my post too. I'm sure my rambling style of writing, combined with the minutia I often get sidetracked with annoys some people who just want to get on with it. So I'm glad it appeals to you. I really try to not just explain what to do and how to do it, but also why. I guess ultimately, I try to be the Linux guy that I wish I'd had access too when I first encountered Linux. And play forward all the occasions where someone took the time to give me the detailed assistance and explanations that were given to me in my early days with Linux.

Anyway, it looks like you've inadvertently added in an extra slash to your ghost_user's shell. If you look closely at the error message and the output of the /etc/passwd file (I assume that's what your second codeblock is?) you'll notice that in the error it's complaining about '/bin/bash/' (note the trailing slash). Then if you compare root's shell and ghost_user's shell (in the second case), you can see that root has '/bin/bash' and ghost_user has '/bin/bash/' (again, note the extra trailing slash). In an attempt to demonstrate my point really explicitly:

root@tkldev ~# ls /bin/bash
root@tkldev ~# ls /bin/bash/
ls: cannot access '/bin/bash/': Not a directory

/bin/bash/ doesn't exist, but /bin/bash does! :)

To fix your ghost_user's shell, use the 'chsh' command (change shell):

chsh -s /bin/bash ghost_user

I'm probably overdoing it now, but note no trailing slash on the end of /bin/bash... :)

To double check that ghost_user now has the right shell:

grep ghost_user /etc/passwd

It should now show this:


And now the 'su - ghost_user' command should work! :)

Jeremy Davis's picture

Awesome, glad that was all it was and that you now have the latest Ghost installed... And extra awesome to hear that your research was on the right track! :)

FWIW beer tends to be the drink of choice for most here in Australia, especially men (although lots of women drink beer too). My tastes aren't too refined and I tend to believe that beer comes in 2 distinct flavours; "good" and "very good"! :) Many Aussies are also happy to drink wine too though. Personally, I prefer beer as a general rule, and aren't a big fan of whites, but a nice red with a rare/medium rare steak is hard to beat! :) As a total aside, if you're into red wine and would like to try some Australian wine, then I can recommend Cab Sav from the Margret River or Coonawarra regions or Shiraz from the (almost legendary) Barossa Valley region. The Hunter valley region also produces some good Shiraz IMO.

Jeremy Davis's picture

I've just noticed (and have now fixed) a fairly minor typo, which may have some fairly serious consequence... (Oops!)

It was in the sed line that I gave right near the end. If you ran that, you'll need to fix your /opt/ghost/config.production.json file. I no longer have a ghost server running, so can't explicitly double check, but from the mistake I made, the rest of the command I've posted and memory, you need to double check the "url" line. To do that, run this:

grep '"url"' /opt/ghost/config.production.json

It should look something like this:

     "url": "https://YOUR_DOMAIN/"

If you applied my previous sed command, yours will have a colon missing between "url" and your actual URL.

Fritz Ferrante's picture

Hey Jeremy,

A new update came out!  It looks like I need to get a little help running ghost update.  I'm in the correct directory and use the SU command, but the error is that my ghost user account doesn't have all permissions to files in the directory.  Any thoughts?



Jeremy Davis's picture

Remember that the 'ghost' user account is a Ghost specific account which is intended for internal use only. You need to use the 'ghost_user' account...

If you are definately using the right account, then perhaps some of the permissions have got messed up somewhere, somehow? I suggest re-running the "Setup permissions" step again. I.e.:

chown -R ghost_user:node /opt/ghost
chown -R ghost:ghost /opt/ghost/content
find /opt/ghost -type d -exec chmod 00775 {} \;
find /opt/ghost ! -path "/opt/ghost/versions/*" -type f -exec chmod 664 {} \;


Add new comment