Installing Ghost

I haven’t published an article on here for over a year and a half… While this was mostly due to a lack of motivation, another reason was that I didn’t enjoy the blogging system I was using.

As lightweight as Pelican is, I found it cumbersome to use on a regular basis. Every time I wanted to publish or update an article, I had to : - edit local markdown files ; - regenerate the website files ; - start a webserver locally to proofread the article ; - commit and push the files to my git repo ; - pull the files on the webserver.

I hadn’t had a look at the CMS landscape for a while, and I started searching for one with a web editor that supports markdown. I also wanted to avoid anything that runs on PHP if possible.

I quickly discovered Ghost, and decided to give it a shot. I was convinced by it within a few hours and I decided to migrate this blog.

So, to celebrate my move to Ghost, I figured I’d write an article on how I’ve installed it on my server.

All commands in this article have to be run as the root user on a Debian server.

Installing nodejs

Unlike most CMS (Wordpress, for example), Ghost is not files that you have to upload to a webserver, but a daemon that runs on nodejs.

Here’s the official recommended way of installing the current LTS version of nodejs on Debian :

curl -sL | bash -
apt-get install -y nodejs

If, like me, you don’t want to run a bash script downloaded from the internet on your server, here are the commands you have to run to install it manually.

Since the nodejs repo uses https, we’ll first need to install the required package to use those :

apt install apt-transport-https

We’ll then have to add the nodejs repository public key to the system :

curl -s | apt-key add -

Now we have to add the nodejs repository to our sourcelist :

echo 'deb stretch main' > /etc/apt/sources.list.d/nodesource.list

We can now install nodejs

apt update
apt install nodejs

System configuration

Before installing Ghost, some system configuration is required.

First, let’s create a new ghost system user that’ll be used to run the Ghost daemon :

useradd -s /bin/false -r -d /opt/ghost -m ghost

Ghost needs an empty folder for the automated installation script to work. For that purpose, let’s create a subfolder in the ghost user home folder :

sudo -Hu ghost mkdir /opt/ghost/app


Ghost requires a MySQL/MariaDB database to store its data (technically, you could use a SQLite database, but please don’t).

I personnally have all my databases stored on a single LXC container running MariaDB. However, if you need to, you can install MariaDB locally this way :

apt install mariadb-server mariadb-common

We now have to declare a ghost user and database in the MariaDB shell :

create database ghost;
create user `ghost`@`%` identified by 'password';
grant all privileges on ghost.* to 'ghost'@`%`;

You can change the % to localhost in the create user command if you’ve installed MariaDB locally. Please also remember to change 'password' by an actual password.

Once that’s done, we’re ready to install Ghost !

Installing Ghost

Ghost CLI

To install Ghost, we first have to install the Ghost CLI :

npm i -g ghost-cli

The Ghost CLI is a tool that lets you install, upgrade and manage your Ghost installation easily. Its usage is thoroughly documented on the official website here.

Installing Ghost

Let’s install Ghost :

cd /opt/ghost/app
sudo -Hu ghost ghost install --no-setup-nginx --no-setup-systemd --no-setup-linux-user --no-setup-mysql

The command will ask you for the following information : - the URL of your website ; - the hostname or IP of the server that’s hosting your MariaDB installation ; - the username to use to connect to the database (ghost) ; - the password you’ve configured for the database user ; - the database name (ghost).

Once the script has finished running, you’ve successfully installed Ghost ! However, the daemon won’t start since we haven’t configured systemd yet.

Since it contains a password, let’s fix the permissions on our installation’s configuration file to make sure it’s not world-readable :

chmod 600 /opt/ghost/app/config.production.json

As you can see from the ghost install command, it can install and configure pretty much all of its dependencies on its own. However, since I’m a sysadmin, that’s not how I roll.

Systemd configuration

As I wrote earlier, Ghost runs as a daemon. For us to be able to start it, we now need to declare a systemd unit file :

Let’s create the file :

vim /etc/systemd/system/ghost.service

And add the following content to it :

Description=Ghost systemd service

ExecStart=/usr/bin/ghost run


We can now reload systemd an start Ghost :

systemctl daemon-reload
systemctl start ghost.service

The daemon should now be running :

pgrep -alf ghost
14184 ghost run


With its default configuration, Ghost runs as a webserver on localhost, on a non-standard HTTP port (TCP 2368). For your website to be publicly browseable, you’ll need to configure a webserver as a reverse-proxy in front of your Ghost installation. We’ll use nginx for that purpose.

If you already have nginx running on a different server from your Ghost installation, you can use it for that purpose. For it to work, you’ll need to edit the server host IP in Ghost’s config.production.json configuration file with your Ghost server public IP and to restart Ghost. If you do so, make sure to limit direct access to your Ghost installation to the IP of your reverse-proxy by using iptables.

If you need to, you can install nginx locally this way :

apt install nginx

I won’t go into details on how to configure and secure a nginx installation here as it is beyond the scope of this article.

Here is my nginx configuration for this website :

  location / {
    include proxy.conf;
    add_header Front-End-Https on;
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
    proxy_set_header Authorization "";
    proxy_set_header Accept-Encoding "";
    proxy_redirect off;

  location /ghost/ {
    deny all;
    include proxy.conf;
    add_header Front-End-Https on;
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
    proxy_set_header Accept-Encoding "";
    proxy_redirect off;

As you can see, I’ve declared two location blocks : - / is publicly visible by anyone ; - /ghost (Ghost’s administation interface) is only accessible from (my public IP address).

I’d rather have left Ghost’s administation interface accessible from anywhere. However, since there is currently no way to replace /ghost by another subfolder and two-factor authentification is not available, I’ve decided against it.


As I mentionned in previous articles, I have monit running on all of my servers to make sure that my services are running and to restart them should they crash.

I’ve created a configuration file for Ghost :

vim /etc/monit/conf.d/ghost

With the following content :

check process ghost
  matching "ghost run"
  start program = "/bin/systemctl start ghost"
  stop program = "/bin/systemctl stop ghost"
  if changed pid then alert
  if changed ppid then alert

Let’s reload monit :

monit reload

Ghost should now appear in your monit summary.


Ghost writes its logs through syslog. If you don’t want those messages to end up in /var/log/syslog, you’ll have to configure your syslog daemon. For me, that’s syslog-ng.


Let’s create a dedicated folder for the Ghost daemon’s log files :

mkdir /var/log/ghost
chown root:adm /var/log/ghost

Then, we need to create a configuration file :

vim /etc/syslog-ng/conf.d/ghost.conf

And add the following content to it :

filter f_ghost { program ("ghost"); };
destination d_ghost { file (/var/log/ghost/ghost.log); };
log { source(s_src); filter (f_ghost); destination (d_ghost); flags(final); };

We can now reload syslog-ng :

service syslog-ng reload

Once that’s done, Ghost should start logging in /var/log/ghost/ghost.log. Accessing a page on your site will create a new log entry, so that’ll be enough to make sure it’s working properly.


As always with logs, let’s configure logrotate to make sure we don’t end up with huge files.

Let’s create a new logrotate configuration file :

vim /etc/logrotate.d/ghost

And add the following content to it :

/var/log/ghost/ghost.log {
  rotate 8
  create 640 root adm

There’s no need to reload anything here. This new configuration file will be read by logrotate automatically next time its cron runs.


This blog uses a previous version of Ghost’s default theme, Casper.

I’ve modified it a bit, and I really enjoy how it looks now ! You can get the theme with my modifications from my GitHub ! Credits to this article for some of the changes, and thanks @Aguay for the help !

You’ve also probably noticed that I now use a private installation of NodeBB for the comments section. I’ll probably write an article on how I’ve installed and implemented it in my Ghost installation in the near future. In the meantime, please feel free to make use of it !