Ghost is a completely open source (MIT License) blogging (publishing) platform that is gaining popularity among developers and ordinary users since its 2013 release. Ghost source code is publicly available on GitHub. Ghost generally takes around ~300MB of RAM to run well, so it can run on modest hardware. It puts focus on content and blogging. The most attractive thing about Ghost is its simple, clean, elegant and responsive design. You can write your blog posts from a mobile phone. Content for Ghost is written and formatted using the Markdown language. Ghost is perfect fit for individuals or small groups of writers. Ghost also has built-in support for Accelerated Mobile Pages (AMP) which will make loading of your blog lightning fast on mobile. Ohh, and it supports Emoji. ๐ŸŽ‰๐Ÿ‘ป๐Ÿ’ฉ๐Ÿ‘น๐Ÿ‘ฟโ˜ ๐Ÿ‘ฝ๐Ÿ‘พ๐Ÿค–๐ŸŽƒ๐Ÿ˜บ๐ŸŽ‰๐Ÿด

Ghost 3.0 is the third major, stable release of the Ghost content management system (CMS). Ghost 3.0 has a new business model for publishing (memberships and subscriptions), a more powerful headless CMS, a refined publishing experience etc. You can read more about awesome new stuff here.

In this guide we are going to install, configure and deploy a secure Ghost v3 Stable blog with the help of Ghost-CLI on a production Ubuntu Server 18.04.4 LTS bionic Virtual Private Server (VPS) using Let's Encrypt, Certbot/acme.sh, Node.js, npm, Yarn, NGINX, MySQL/MariaDB and PM2.

Requirements

  • Ubuntu 16.04 or Ubuntu 18.04
  • NGINX (minimum of 1.9.5 for SSL)
  • A supported version of Node.js (12.x Erbium LTS, 10.x Dubnium LTS)
  • MySQL 5.5, 5.6, or 5.7 (but not >= 8.0) or MariaDB equivalent
  • Register (purchase ๐Ÿ’ฐ ๐Ÿ’ต) a domain name. I recommend Namecheap registrar.
  • Deploy (spin up) ๐Ÿ”Œ a fresh Ubuntu Server 18.04 LTS instance (droplet๐Ÿ’ง) on DigitalOcean or Vultr or Linode with the minimum of 1GB RAM (swap can be used).
  • Sudo user.
โš ๏ธ โš ๏ธ โš ๏ธ
NOTE: If you use VPS smaller than 1GB of RAM, you must configure swap in order to run the install process successfully. Hence I recommend using 1GB RAM server.

Before You Begin

First, add color to bash shell prompt for the root user by editing /root/.bashrc file:

nano ~/.bashrc

Screenshot below show how the Ubuntu shell prompt looks by default.

Poor prompt

This is what's called poor man's shell prompt. ๐Ÿ˜‚ ๐Ÿ˜Š

Next, press CTRL + W on your keyboard and copy/paste #force_color_prompt=yes and uncomment that line (remove # at the beggining).

Next, save by pressing CTRL + O, then press Enter and exit by pressing CTRL + X.

Lastly, refresh ~/.bashrc file by typing:

source ~/.bashrc

After doing the above steps, my prompt looks something like this. Not so sexy, but better than previous version, right ? ๐Ÿ˜‰

Better prompt

Make your $PS1 fancy. Run vim ~/.bashrc and copy/paste the below line at the end of file, save and exit :wq!:

export PS1="\[\033[38;5;13m\]\d\[$(tput sgr0)\]\[\033[38;5;15m\] \[$(tput sgr0)\]\[\033[38;5;49m\]\t\[$(tput sgr0)\]\[\033[38;5;15m\] \[$(tput sgr0)\]\[\033[38;5;11m\][\[$(tput sgr0)\]\[\033[38;5;75m\]\u\[$(tput sgr0)\]\[\033[38;5;10m\]@\[$(tput sgr0)\]\[\033[38;5;199m\]\H\[$(tput sgr0)\]\[\033[38;5;11m\]]\[$(tput sgr0)\]\[\033[38;5;15m\] \[$(tput sgr0)\]\[\033[38;5;208m\]:\w\[$(tput sgr0)\]\[\033[38;5;15m\]\\$ \[$(tput sgr0)\]"

Refresh ~/.bashrc file by typing:

source ~/.bashrc
Sexy prompt

Check Ubuntu version:

lsb_release -ds
# Ubuntu 18.04.3 LTS

Create a new non-root user account and set its password:

useradd -d /home/johndoe -m -s /bin/bash -c "John Doe" johndoe && passwd johndoe
โš ๏ธ โš ๏ธ โš ๏ธ
NOTE: Please don't call this user ghost, Ghost-CLI will create the ghost user for managing your Ghost blog. The user you are creating is for administrating your server.

Make it superuser by adding it to sudo group. This gives the johndoe user sudo privileges:

usermod -aG sudo johndoe

Switch to this newly created user and continue your server administration from this account:

su - johndoe

Repeat the above steps if you want sexy prompt for this user also.

Set up the timezone:

sudo timedatectl set-timezone 'Region/City'

Update your operating system packages (software). This is an important first step because it ensures you have the latest updates and security fixes for your operating system's default software packages and new updates for Kernel:

sudo apt update && sudo apt upgrade -y && sudo apt autoremove --purge -y

Ensure the required tools and packages (zip/unzip, vim, nano, openssl, wget, curl, GnuPG, git, sudo, htop, tree, tmux, dstat, sysstat, zopfli, gzip, socat...) are installed before proceeding:

sudo apt install -y build-essential apt-transport-https zopfli gzip zip unzip vim nano openssl wget curl gnupg git sudo htop tree tmux ssl-cert ca-certificates socat dstat sysstat iotop systemd bash-completion lsb-release gnupg2

Step 1 - Installing acme.sh client and obtaining certificate from Let's Encrypt

We are going to use Let's Encryptยฎ Certificate Authority (CA) and Acme.sh Automated Certificate Management Environment (ACME) client to obtain free SSL/TLS digital certificate for our Ghost blog. Don't forget to replace all instances of example.com with your domain/host name.

Download and install acme.sh, ACMEv2 TLS client, in pure Unix shell script:

sudo -s
git clone https://github.com/Neilpang/acme.sh.git
cd acme.sh
./acme.sh --install --accountemail "security@example.com" # Provide valid email address
source ~/.bashrc
cd

Check the acme.sh version:

acme.sh --version
# v2.8.6

Obtain RSA and ECDSA certificates for example.com domain/hostname:

# *********************** SAN Certs from LE using Standalone validation ***********************
# RSA 2048
acme.sh --issue --standalone -d example.com -d www.example.com --ocsp-must-staple --keylength 2048
# ECDSA/ECC P-256
acme.sh --issue --standalone -d example.com -d www.example.com --ocsp-must-staple --keylength ec-256

# *********************** SAN Certs from LE using Webroot validation ***********************
# RSA 2048
acme.sh --issue -d example.com -d www.example.com --webroot /var/www/_letsencrypt --reloadcmd "sudo systemctl reload nginx.service" --ocsp-must-staple --keylength 2048
# ECDSA/ECC P-256
acme.sh --issue -d example.com -d www.example.com --webroot /var/www/_letsencrypt --reloadcmd "sudo systemctl reload nginx.service" --ocsp-must-staple --keylength ec-256

# *********************** Wildcard Certs from LE using DNS API validation ***********************
# export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
# export CF_Email="xxxx@example.com"
# RSA 2048
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com' --ocsp-must-staple --keylength 2048
# ECDSA/ECC P-256
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com' --ocsp-must-staple --keylength ec-256

After running the above commands, your cert and cert key will be in ~/.acme.sh/example.com for RSA, and ~/.acme.sh/example.com_ecc for ECC.

To list your previously obtained certificates you can run:

acme.sh --list
# Main_Domain  KeyLength SAN_Domains       Created                       Renew
# example.com  "2048"     www.example.com  Mon Nov 18 07:43:26 UTC 2019  Fri Jan 17 07:43:26 UTC 2020
# example.com  "ec-256"   www.example.com  Mon Nov 18 07:43:45 UTC 2019  Fri Jan 17 07:43:45 UTC 2020

Create /etc/letsencrypt directory where we will symlink certificates and keys from home directory:

mkdir -p /etc/letsencrypt/example.com && mkdir -p /etc/letsencrypt/example.com_ecc

Install certs to proper directories:

acme.sh --install-cert \
        --domain example.com \
        --cert-file /etc/letsencrypt/example.com/cert.pem  \
        --key-file /etc/letsencrypt/example.com/key.pem \
        --fullchain-file /etc/letsencrypt/example.com/fullchain.pem \
        --reloadcmd "systemctl reload nginx.service"

acme.sh --install-cert \
        --ecc
        --domain example.com \
        --cert-file /etc/letsencrypt/example.com_ecc/cert.pem  \
        --key-file /etc/letsencrypt/example.com_ecc/key.pem \
        --fullchain-file /etc/letsencrypt/example.com_ecc/fullchain.pem \
        --reloadcmd "systemctl reload nginx.service"

Step 2 - Installing Node.jsยฎ and npm

โš ๏ธ โš ๏ธ โš ๏ธ
NOTE: Latest Ghost 3.0.0 currently supports Node.js versions 12.x+ Erbium and 10.x+ Dubnium only.

Ghost is built on Node.js. We are going to install recommended version for Ghost which is ย v12 Erbium LTS at the time of this writing. On Linux you have a few installation options: Linux Binaries (x86/x64), Source Code or via Package Managers.

Download and install the latest Long-Term Support (LTS) version (release) of Node.js:

NOTE: npm is distributed with Node.js - which means that when you download Node.js, you automatically get npm installed on your computer.

# Node.jsยฎ 12.x Erbium LTS
# Via NodeSource Repository - https://github.com/nodesource/distributions
# Add the NodeSource APT repository for Node 12
$ curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
$ sudo apt install -y nodejs

# ########################################
# or
# ########################################

# Latest LTS Version: 12.16.1 (includes npm 6.13.4)
$ wget https://nodejs.org/dist/v12.16.1/node-v12.16.1-linux-x64.tar.xz
$ tar xfJ node-v12.16.1-linux-x64.tar.xz
$ cd node-v12.16.1-linux-x64
$ rm CHANGELOG.md LICENSE README.md
$ cd ~
$ rm node-v12.16.1-linux-x64.tar.xz
$ cd /usr/
$ sudo cp -fR ~/node-v12.16.1-linux-x64/* .
$ cd ~
$ rm -rf node-v12.16.1-linux-x64

Check Node.js and npm version:

$ node -v && npm -v
# v12.16.1
# 6.13.4

Npm is a separate project from Node.js, and tends to update more frequently. As a result, even if youโ€™ve just downloaded Node.js (and therefore npm), youโ€™ll probably need to update your npm. Luckily, npm knows how to update itself! To update your npm, type this into your terminal:

$ sudo npm install -g npm@latest

Recheck npm version:

$ npm -v
# 6.14.2

Step 3 - Installing MySQL Community Server or MariaDB server

Ghost supports MySQL/MariaDB and SQLite databases. In this tutorial, however, we will be using the MySQL database. If you prefer, you can use MariaDB, which is a drop-in replacement for MySQL.

Download and install the latest stable version of MySQL 5.7 Community Server from the Oracle maintained MySQL repository on your machine:

# ################################################################################ #
# 
# __  __      ___  ___  _      ___  ____ 
# |  \/  |_  _/ __|/ _ \| |    | __||__  |
# | |\/| | || \__ \ (_) | |__  |__ \_ / / 
# |_|  |_|\_, |___/\__\_\____| |___(_)_/  
#         |__/                            
#
# ################################################################################ #
# Steps for a Fresh Installation of MySQL
$ cd /tmp
# https://dev.mysql.com/downloads/repo/apt/
# Download MySQL APT Repository - mysql-apt-config_0.8.12-1_all.deb
# (mysql-apt-config_0.8.14-1_all.deb)   MD5: 5cc94c7720fcd3124449b3e789441b98
$ wget https://dev.mysql.com/get/mysql-apt-config_0.8.14-1_all.deb

# Verifying the MD5 Checksum
$ md5sum mysql-apt-config_0.8.14-1_all.deb
# or
$ openssl md5 mysql-apt-config_0.8.14-1_all.deb
# Resulting checksum (the string of hexadecimal digits) must matche the one displayed on the download page immediately below the respective package.

# Install the downloaded release package
$ sudo dpkg -i mysql-apt-config_0.8.14-1_all.deb
$ rm mysql-apt-config_0.8.14-1_all.deb
$ sudo dpkg-reconfigure mysql-apt-config
$ sudo apt update
# Install MySQL 5.7 with APT
$ sudo apt install -y mysql-server
$ cd ~
$ dpkg -l | grep mysql | grep ii



# ####################################################################################################### #
#  /$$      /$$                     /$$           /$$$$$$$  /$$$$$$$          /$$    /$$$$$$      /$$$$$$ 
# | $$$    /$$$                    |__/          | $$__  $$| $$__  $$       /$$$$   /$$$_  $$    /$$__  $$
# | $$$$  /$$$$  /$$$$$$   /$$$$$$  /$$  /$$$$$$ | $$  \ $$| $$  \ $$      |_  $$  | $$$$\ $$   |__/  \ $$
# | $$ $$/$$ $$ |____  $$ /$$__  $$| $$ |____  $$| $$  | $$| $$$$$$$         | $$  | $$ $$ $$     /$$$$$$/
# | $$  $$$| $$  /$$$$$$$| $$  \__/| $$  /$$$$$$$| $$  | $$| $$__  $$        | $$  | $$\ $$$$    /$$____/ 
# | $$\  $ | $$ /$$__  $$| $$      | $$ /$$__  $$| $$  | $$| $$  \ $$        | $$  | $$ \ $$$   | $$      
# | $$ \/  | $$|  $$$$$$$| $$      | $$|  $$$$$$$| $$$$$$$/| $$$$$$$/       /$$$$$$|  $$$$$$//$$| $$$$$$$$
# |__/     |__/ \_______/|__/      |__/ \_______/|_______/ |_______/       |______/ \______/|__/|________/
# ####################################################################################################### #

# Setting up MariaDB 10.2 [Stable] repositories
# https://downloads.mariadb.org/mariadb/repositories
# Here are the commands to run to install MariaDB 10.2 from the MariaDB repository on your Ubuntu system:
$ sudo apt-get install software-properties-common
$ sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8
$ sudo add-apt-repository 'deb [arch=amd64] https://mirrors.nxthost.com/mariadb/repo/10.2/ubuntu bionic main'

$ sudo apt update
# Install MariaDB 10.2 with APT
$ sudo apt install -y mariadb-server

# -------------------------------------------------------------------------------------------

# ####################################################################################################### #
# $$\      $$\                     $$\           $$$$$$$\  $$$$$$$\          $$\   $$$$$$\      $$$$$$\  
# $$$\    $$$ |                    \__|          $$  __$$\ $$  __$$\       $$$$ | $$$ __$$\    $$ ___$$\ 
# $$$$\  $$$$ | $$$$$$\   $$$$$$\  $$\  $$$$$$\  $$ |  $$ |$$ |  $$ |      \_$$ | $$$$\ $$ |   \_/   $$ |
# $$\$$\$$ $$ | \____$$\ $$  __$$\ $$ | \____$$\ $$ |  $$ |$$$$$$$\ |        $$ | $$\$$\$$ |     $$$$$ / 
# $$ \$$$  $$ | $$$$$$$ |$$ |  \__|$$ | $$$$$$$ |$$ |  $$ |$$  __$$\         $$ | $$ \$$$$ |     \___$$\ 
# $$ |\$  /$$ |$$  __$$ |$$ |      $$ |$$  __$$ |$$ |  $$ |$$ |  $$ |        $$ | $$ |\$$$ |   $$\   $$ |
# $$ | \_/ $$ |\$$$$$$$ |$$ |      $$ |\$$$$$$$ |$$$$$$$  |$$$$$$$  |      $$$$$$\\$$$$$$  /$$\\$$$$$$  |
# \__|     \__| \_______|\__|      \__| \_______|\_______/ \_______/       \______|\______/ \__|\______/
# ####################################################################################################### #

# Setting up MariaDB 10.3 [Stable] repositories
# https://downloads.mariadb.org/mariadb/repositories
# Here are the commands to run to install MariaDB 10.3 from the MariaDB repository on your Ubuntu system:
$ sudo apt-get install software-properties-common
$ sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8
$ sudo add-apt-repository 'deb [arch=amd64] https://mirrors.nxthost.com/mariadb/repo/10.3/ubuntu bionic main'

$ sudo apt update
# Install MariaDB 10.3 with APT
$ sudo apt install -y mariadb-server
โš ๏ธ โš ๏ธ โš ๏ธ
NOTE: During the installation on Debian/Ubuntu systems you will be prompted for MySQL/MariaDB "root" user password. You should choose and set strong password for MySQL/MariaDB "root" user and remember it. Also, note that this is not the case on RedHat/CentOS systems.

Check the MySQL version:

mysql --version && sudo mysqld --version
# mysql  Ver 14.14 Distrib 5.7.28, for Linux (x86_64) using  EditLine wrapper
# mysqld  Ver 5.7.28 for Linux on x86_64 (MySQL Community Server (GPL))

Check if MySQL daemon has started and is running:

sudo systemctl status mysql.service # Active: active (running)
sudo systemctl is-enabled mysql.service # enabled

Run the mysql_secure_installation utility (script) to improve the security of your MySQL/MariaDB installation:

sudo mysql_secure_installation

# Securing the MySQL server deployment.

# Enter password for user root: ******************

# Would you like to setup VALIDATE PASSWORD plugin? Y
# Change the password for root ? N
# Remove anonymous users? Y
# Disallow root login remotely? Y
# Remove test database and access to it? Y
# Reload privilege tables now? Y
# Success.

# All done!

Login (connect) to the MySQL command line as the MySQL root user:

mysql -u root -p
# Enter password: ********

Create a new MySQL database and user for Ghost installation:

mysql> CREATE DATABASE dbname;
mysql> CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
mysql> GRANT ALL ON dbname.* TO 'username'@'localhost';
mysql> FLUSH PRIVILEGES;
mysql> SHOW DATABASES; # run show databases just to confirm that database for Ghost exists
# +--------------------+
# | Database           |
# +--------------------+
# | information_schema |
# | dbname             |
# | mysql              |
# | performance_schema |
# | sys                |
# +--------------------+
# 5 rows in set (0.00 sec)

Exit (disconnect) from MySQL:

mysql> EXIT

Step 4 - Installing and configuring NGINX Open Source

NGINX (engine-x) is a high performance web server, load balancer, cache and proxy server that works well in all environments: Bare Metal, Public/Private/Hybrid Cloud and Containers. NGINX will be used as a reverse proxy for our Ghost application.

NGINX can be installed differently, depending on the operating system. For Linux, NGINX packages from nginx.org can be used. Currently, pre-built NGINX packages are available for the following distributions: Red Hat Enterprise Linux (RHEL)/Community ENTerprise Operating System (CentOS), ย Debian/Ubuntu and SUSE Linux Enterprise Server (SLES). Main NGINX package is built with all modules that do not require additional libraries to avoid extra dependencies. Since version 1.9.11, NGINX supports dynamic modules and the following modules are built as dynamic and shipped as separate packages: nginx-module-geoip, nginx-module-image-filter, nginx-module-njs, nginx-module-perl, nginx-module-xslt.

Download and install latest stable or mainline (recommended for most deployments) release of NGINX and dynamically loadable modules directly from the official NGINX repository:

# ################################################################################ #
# โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—     โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
# โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•
# โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—  
# โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ•”โ•โ•โ•  
# โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
# โ•šโ•โ•โ•โ•โ•โ•โ•   โ•šโ•โ•   โ•šโ•โ•  โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•
# ################################################################################ #
wget https://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key

apt-key list
rm nginx_signing.key

# Become root for a moment (open the root shell)
sudo -s

# Add and set up NGINX stable APT repository
printf "deb https://nginx.org/packages/ubuntu/ $(lsb_release -sc) nginx\ndeb-src https://nginx.org/packages/ubuntu/ $(lsb_release -sc) nginx\n" >> /etc/apt/sources.list.d/nginx_stable.list
exit
cat /etc/apt/sources.list.d/nginx_stable.list


sudo apt update
# Install latest stable NGINX packages
sudo apt install -y nginx nginx-module-geoip nginx-module-image-filter nginx-module-njs nginx-module-perl nginx-module-xslt nginx-nr-agent






# ################################################################################ #
# โ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—     โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
# โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•
# โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—  
# โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•  
# โ–ˆโ–ˆโ•‘ โ•šโ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
# โ•šโ•โ•     โ•šโ•โ•โ•šโ•โ•  โ•šโ•โ•โ•šโ•โ•โ•šโ•โ•  โ•šโ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•šโ•โ•  โ•šโ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•
# ################################################################################ #
wget https://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key

apt-key list
rm nginx_signing.key

# Become root for a moment (open the root shell)
sudo -s

# Add and set up NGINX mainline APT repository
printf "deb https://nginx.org/packages/mainline/ubuntu/ $(lsb_release -sc) nginx\ndeb-src https://nginx.org/packages/mainline/ubuntu/ $(lsb_release -sc) nginx\n" >> /etc/apt/sources.list.d/nginx_mainline.list
exit
cat /etc/apt/sources.list.d/nginx_mainline.list
sudo apt update
# Install latest mainline NGINX packages
sudo apt install -y nginx nginx-module-geoip nginx-module-image-filter nginx-module-njs nginx-module-perl nginx-module-xslt nginx-nr-agent

The installation of open source NGINX is now complete. To inspect files that are installed with NGINX package, you can use dpkg -L nginx command.

Verify that it is installed by checking NGINX version:

sudo nginx -v && sudo nginx -V
# nginx version: nginx/1.17.5
# nginx version: nginx/1.17.5
# built by gcc 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)
# built with OpenSSL 1.1.1  11 Sep 2018
# TLS SNI support enabled
# configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules
# . . . . . . . . . .
# . . . . . . . . . .

Create NGINX application profile for Uncomplicated Firewall (UFW):

sudo vim /etc/ufw/applications.d/nginx

# Copy/paste this
[Nginx HTTP]
title=Web Server (Nginx, HTTP)
description=Small, but very powerful and efficient web server
ports=80/tcp

[Nginx HTTPS]
title=Web Server (Nginx, HTTPS)
description=Small, but very powerful and efficient web server
ports=443/tcp

[Nginx Full]
title=Web Server (Nginx, HTTP + HTTPS)
description=Small, but very powerful and efficient web server
ports=80,443/tcp

Check status of NGINX service, activate (launch) NGINX service automatically at boot and immediately start NGINX service (daemon):

sudo systemctl status nginx.service # Active: inactive (dead)
sudo systemctl is-active nginx.service # inactive
sudo systemctl enable nginx.service
sudo systemctl start nginx.service

Add Brotli compression support to NGINX via Google ngx_brotli third-party module:

wget https://nginx.org/download/nginx-1.17.5.tar.gz && tar zxvf nginx-1.17.5.tar.gz
rm nginx-1.17.5.tar.gz
mkdir ~/.vim/
cp -r ~/nginx-1.17.5/contrib/vim/* ~/.vim/
git clone https://github.com/google/ngx_brotli.git
cd ngx_brotli && git submodule update --init && cd ~
cd ~/nginx-1.17.5
sudo apt install -y libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev
./configure --with-compat --add-dynamic-module=../ngx_brotli
make modules
sudo cp objs/*.so /etc/nginx/modules
ls /etc/nginx/modules
sudo chmod 644 /etc/nginx/modules/*.so
sudo vim /etc/nginx/nginx.conf
# load_module modules/ngx_http_brotli_filter_module.so;
# load_module modules/ngx_http_brotli_static_module.so;
sudo nginx -t
cd ~
rm -rf nginx-1.17.5 ngx_brotli
brotli compression in google chrome devtools

Create /etc/nginx/ssl, /etc/nginx/snippets, /etc/nginx/sites-available, /etc/nginx/sites-enabled, /etc/nginx/geoip and /etc/nginx/includes directories:

sudo mkdir -p /etc/nginx/{ssl,snippets,sites-available,sites-enabled,geoip,includes}

NGINX will sit in front of our Ghost application, proxy HTTP connections to application server and potentially serve and handle static files directly. So, configure NGINX as a HTTP(S) reverse proxy server or SSL/TLS termination proxy for your Ghost application:

sudo vim /etc/nginx/conf.d/example.com.conf

Copy/paste the following into the /etc/nginx/conf.d/example.com.conf:

NOTE: We are using modern SSL/TLS configuration that is recommended by Mozilla Security Team. If you want better compatibility with older clients you can use intermediate SSL/TLS configuration. You can check configuration generator at this link.
upstream ghost {

    server 127.0.0.1:2368;
    keepalive 64;

}

server {

    listen [::]:80;
    listen 80;

    server_name example.com www.example.com;
 
    location / {
        return 301 https://example.com$request_uri;
    }
    
}

server {

    listen [::]:443 ssl http2;
    listen 443 ssl http2;
    
    server_name www.example.com;
    
    # RSA
    ssl_certificate /etc/letsencrypt/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/example.com/key.pem;
    # ECDSA
    ssl_certificate /etc/letsencrypt/example.com_ecc/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/example.com_ecc/key.pem;
    
    return 301 https://example.com$request_uri;
}

server {

    listen [::]:443 ssl http2 default_server;
    listen 443 ssl http2 default_server;
    
    server_name example.com;
  
    client_max_body_size 50M;
    
    # RSA
    ssl_certificate /etc/letsencrypt/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/example.com/key.pem;
    # ECDSA
    ssl_certificate /etc/letsencrypt/example.com_ecc/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/example.com_ecc/key.pem;

    location / {
        try_files $uri $uri/index.html @proxy;
    }
    
    location @proxy {
        proxy_pass http://ghost;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_hide_header X-Powered-By;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }

}

Check if NGINX configuration is ok:

sudo nginx -t

Gracefully reload NGINX processes:

sudo systemctl reload nginx.service

Step 5 - Installing Yarn (optional)

Download and install Yarn package manager on your system:

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update && sudo apt install -y yarn

Check Yarn version:

yarn --version
# 1.19.1

Step 6 - Installing Ghost-CLI

Ghost-CLI is a command line interface (CLI) tool that help you get Ghost installed and configured and to make it super easy to keep your Ghost install up to date. It sets up the database, configures NGINX as a reverse proxy, enables TLS/SSL security using Letโ€™s Encrypt CA, automatically renews your SSL, and initializes Ghost as a systemd service. Ghost-CLI is an npm module that can be installed via either npm or yarn.

Download and install Ghost-CLI:

sudo npm install -g ghost-cli@latest
# or
sudo yarn global add ghost-cli@latest

Check your Ghost-CLI version and Ghost version:

ghost --version
# Ghost-CLI version: 1.13.1

Troubleshoot the system for any potential issues when installing or updating Ghost:

ghost doctor install
# โœ” Checking system Node.js version
# โœ” Checking logged in user
# โœ” Checking current folder permissions
# โœ” Checking operating system compatibility
# โœ” Checking for a MySQL installation
# โœ” Checking memory availability

Step 7 - Installing Ghost

โš ๏ธ โš ๏ธ โš ๏ธ
NOTE: If you want to host multiple Ghost blogs on same VPS, each Ghost instance must be running on a separate port.

Create a document root directory for your installation, then set the owner and permissions:

โš ๏ธ โš ๏ธ โš ๏ธ
NOTE: Don't install Ghost in the /root directory!
sudo mkdir -p /var/www/example.com
sudo chown -R johndoe:johndoe /var/www/example.com

Set the correct permissions:

sudo chmod 775 /var/www/example.com

Then navigate into it:

cd /var/www/example.com

Ensure that the directory is empty to avoid file conflicts:

ls -a

Run the process of Ghost installation in production mode:

ghost install --pname={{ replace_with_your_ghost_instance_name }} --mail=SMTP --mailservice=Mailgun --mailhost=smtp.mailgun.org --mailport=587 --mailuser={{ replace_with_your_mail_user_name }} --mailpass={{ replace_with_your_mail_password }}

If this command succeed, you have successfully deployed Ghost 3.0 on your server. Congrats!

Conclusion

That's it. We now have a fully functional Ghost blog. Your server is delivering content via HTTP/2 when supported by the client. If HTTP/2 connection cannot be negotiated, you downgrade gracefully to HTTP/1.1. To access Ghost admin area just append /ghost/ to your blog URL. If you want to change the default theme for Ghost called Casper to a custom one, you can just download and unzip the theme into the /var/www/example.com/content/themes folder and select it via Ghost admin interface, located at ย https://example.com/ghost. The best place to find free or premium themes is ย Ghost Marketplace. Also you can check ThemeForest, ย Envato Elements, Hauntedthemes, Aspire Themes, Biron Themes & WowThemes for more premium Ghost themes. Congratulations and Good Luck! ๐Ÿป