Ghost 4 on Focal Fossa
This articles explains how to install Ghost 4 on Focal Fossa, and how to harden the Ghost instance against brute force attacks using fail2ban.

Since Xenial Xerus is reaching its end of life, it is time to migrate this blog to a new server running Focal Fossa and to update to Ghost 4.
Prerequisites
A server running Focal Fossa must already be set up, as described in the article A Focal Fossa on Digital Ocean.
A registered domain name is obviously necessary as well.
(Optional) Backup
If a running instance of Ghost is to be migrated to the new server, a complete json backup should be taken. The export features can be found under Settings -> Labs -> Export your content. Store the created .json-file on the computer.
The .json-file does not contain any information about themes or other custom content such as images, thus the content folder, i.e. /var/www/bell0bytes/content, of the previous Ghost installation must be backed up as well.
Installing NGINX
Ghost uses nginx as server and revery proxy.
Installation
Installing nginx is as easy as it gets:
sudo apt-get install nginx
Firewall Rules
To allow the outside world to communicate with nginx, and thus to later on access the Ghost blog, UFW must be told to allow traffic on the http(s) ports nginx listens to. Since nginx is quite commonly used, ufw comes with pre-defined rules for nginx:
sudo ufw allow 'Nginx Full'
This allows http and https traffic, in and out, on IPv4 and IPv6.
Installing MySQL
Unfortunately, Hello Mr. Ellison, Ghost officially suggests using a MySQL database for Ghost production instances. Although it is obviously possible to use MariaDB as well, this article will keep true with the official Ghost instructions and use MySQL. Fun Fact: My and Maria are the two daughters of the original creator of both databases, Ulf Michael Widenius.
To install MySQL, simply invoke aptitude:
sudo apt-get install mysql-server
Once the installation is finished, log in to the newly created instance and set up the root password, obviously using a secure password instead of pwd:
sudo mysql
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'pwd';
quit
Last, but not least, the deployed MySQL server instance should be hardened. Thankfully there is a nice script to do just that, the mysql_secure_installation script. Just run it as superuser do and follow the on-screen instructions.
sudo mysql_secure_installation
NodeJS, NPM and the Ghost-CLI
Ghost uses nodejs and npm. To install a supported version of both, first add the NodeSource APT repository for Node 14 to Aptitude:
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash
And then install node.js:
sudo apt-get install -y nodejs
Finally, globally install the commandline tool of Ghost using npm:
sudo npm install ghost-cli@latest -g
Installing Ghost
With the commandline tool installed, installing Ghost itself is now very easy.
First, a directory must be created with the proper owner and permissions:
sudo mkdir -p /var/www/bell0bytes
sudo chown symplectos:symplectos /var/www/bell0bytes
sudo chmod 755 /var/www/bell0bytes
cd /var/www/bell0bytes
Finally start the installation of Ghost and follow the on-screen instructions:
ghost install
Blog URL
The blog URL specifies the location of the ghost blog, i.e. https://bell0bytes.eu/. If HTTPS is used, the Ghost-CLI will automatically invoke LetsEncrypt to set up SSL.
MySQL Hostname
The hostname of the MySQL instance. When following this article, the hostname would be localhost.
MySQL Username and Password
Specify the username and password as set up above, i.e. root and pwd.
Ghost MySQL User
Allow the Ghost-CLI to create a non-root user to access and edit the Ghost relevant databases.
Configure NGINX
Allow Ghost to set up nginx.
Set up SSL
Allow Ghost to use LetsEncrypt to set up SSL as well.
E-Mail for the SSL Certificate
SSL certificates require an e-mail address to be able to contact administrators of websites.
Set up a SystemD Process
Allow the Ghost-CLI to set up a SystemD process.
Start Ghost
Select yes, then follow the on-screen instructions, i.e. go to https://yourdom.ain/ghost and finish setting up the Ghost instance.
(Optional) Restoring Old Content
Once Ghost 4 is up and running, delete the demo posts and pages, and then copy he themes and images from the old instance can to the content folder of the new instance.
Once done, go to Settings -> Labs -> Import Content and upload the previously backed up .json file.
(Optional) Disabling Login
To disable the newly created ghost user to be able to log in to the system, modify its shell to /bin/false with the usermod command as follows:
sudo usermod --shell /bin/false ghost
Protecting Ghost with Fail2Ban
Ghost already implements two types of login restrictions, namely:
- Account-level lockout after 5 failed login attempts
- IP-Level login attempt throttling after one login attempt per 2 seconds
These settings should be reasonably secure and implementing fail2ban on top of those protection methods may provide diminishing returns, but better safe than sorry. Following a previous article on how to secure nginx with fail2ban, a filter and jail can for Ghost can easily be configured.
Failed login attempts do generate a 404 in the /var/log/nginx/access.log file in the following form:
2001:7e8:c87b:d800:90d5:c60d:8804:6772 - - [20/Mar/2021:19:04:35 +0000] "POST /ghost/api/canary/admin/session HTTP/2.0" 422 299 "https://bell0bytes.eu/ghost/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0"
Thus a fail2ban filter can be written and added to a jail for Ghosts:
sudo vim /etc/fail2ban/filter.d/ghost.conf
# ghost bruteforce filter
[Definition]
failregex = ^<HOST> .* "POST /ghost/api/canary/admin/session
ignoreregex =
sudo vim /etc/fail2ban/jail.d/ghost.conf
[ghost]
enabled = true
filter = ghost
action = ufw
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 120
bantime = 1800
This jail definition would ban users for half an hour after ten unsuccessful login attempts within two minutes.
Remember to reload the fail2ban-client and to check the status:
sudo fail2ban-client reload
sudo fail2ban-client status
Status
|- Number of jail: 4
`- Jail list: ghost, nginx-botsearch, nginx-http-auth, sshd
The Road Ahead
Have a look at this older article, describing how to add LaTeX and Code Highlighting to Ghost.