From Ground Zero to Go-Live: Guide to Setting Up Ghost CMS on Linux
Learn to set up Ghost CMS on Linux from scratch. Follow step-by-step instructions, including Cloudflare integration for security and performance. Get hosting, domain, CMS setup, firewall configuration and launch your website. Let us guide you through this exhilarating journey into website creation!

Setting up Ghost CMS on a Linux server from scratch can be an exhilarating journey into website creation. This guide will walk you through the process step-by-step, including integrating Cloudflare for enhanced security and performance. We'll cover everything from getting hosting and a domain to configuring firewalls and securing your admin dashboard. Let's dive in!
Objectives
By the end of this guide, you will:
- Learn how to set up a Ghost CMS on a Linux server.
- Set up your domain with the appropriate DNS configurations.
- Integrate Cloudflare for improved security and performance.
- Configure essential security measures, including disabling root SSH, setting up nftables, ipset and a droplet firewall.
- Add swap memory to your server.
- Backup your Ghost CMS installation regularly.
- Secure the admin dashboard with Cloudflare Zero Trust.
- Monitor your website's uptime with external tools like UptimeRobot.
Prerequisites
Before proceeding, ensure you have the following:
- A Linux server (preferably Ubuntu 20.04 or later).
- Basic knowledge of using the command line.
- Basic SSH knowledge
Step 1: Setting Up Your Hosting and Domain
- Choose a Hosting Provider: Select a hosting provider that supports Linux servers. Popular options include DigitalOcean, AWS and Linode. For this guide, we'll use DigitalOcean.
- Create a Droplet: Sign in to your hosting provider and create a new droplet (virtual server) with Ubuntu 22.04. After creation, the droplet will have a public IP address.
In this case, we will create a droplet with 25GB SSD, 1GB RAM and 1 CPU Core. Choose SSH as the authentication method instead of a password.
Registering a Domain
- Domain Registration: If you don't have a domain name yet, register one with Cloudflare. Other services like Namecheap, GoDaddy and Google Domains are popular choices.

Configuring DNS Settings on Cloudflare
Once registered, point your domain to your server's IP address. This step is crucial for connecting your domain to your server and making your site accessible online.
- Configure to use DNSSEC by going at Domain Registrations > Manage Domains >
your domain
> Configuration

- Configure DNS records. Head to websites>
your domain
>DNS Records. Add A and CNAME records as shown below:

Step 2: Initial Server Configuration
After setting up your hosting and domain, it's time to configure your server for secure operation.
- Access Your Server: Use SSH to connect to your server.
ssh root@your_server_ip
- Create a New User: For security, create a new user with sudo privileges.
adduser keelan
usermod -aG sudo keelan
- Rsync SSH Configuration: Set up SSH keys and permissions for the new user.
rsync --archive --chown=keelan:keelan ~/.ssh /home/keelan
- SSH Hardening:
- Open the SSH configuration file.
nano /etc/ssh/sshd_config
- To disable root SSH login, find and change
PermitRootLogin
tono
. - To disable Password Authentication, ensure
PasswordAuthentication
is set tono
. - Change SSH Port to something else. For example
8123
.

- Restart the SSH service.
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo systemctl status ssh.socket ssh.service
- SSH using newly created user.
ssh keelan@your_server_ip -p 8123
Step 3: Add Swap Memory
Adding swap memory can help improve your server's performance by providing additional memory space, which is particularly useful for preventing Out Of Memory (OOM) issues, especially on servers with low RAM.
- Create a Swap File:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
- Make the Swap File Permanent:
Add the swap file to /etc/fstab
.
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Step 4: Install mysql, Ghost CLI and Node.js
Following the official ghost installation guide,
- Update packages
sudo apt-get update
sudo apt-get upgrade
- Install nginx
sudo apt-get install nginx
- Install and configure mysql
sudo apt-get install mysql-server
# Enter mysql
sudo mysql
# Update permissions
ALTER USER 'root'@'localhost' IDENTIFIED WITH 'mysql_native_password' BY '<your-new-root-password>';
# Reread permissions
FLUSH PRIVILEGES;
# exit mysql
exit
- Install Node.js
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt install -y nodejs
# Download and import the Nodesource GPG key
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
# Create deb repository
NODE_MAJOR=18 # Use a supported version
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
# Run update and install
sudo apt-get update
sudo apt-get install nodejs -y
- Install Ghost CLI
sudo npm install -g ghost-cli
Step 5: Secure with Cloudflare SSL/TLS
In the Overview tab, Set SSL/TLS Encryption Mode to Full (Strict)
.
Configure Edge Certificates on Cloudflare
- Go to the
Edge Certificates
tab in theSSL/TLS
section. - Ensure
Always Use HTTPS
is enabled to redirect all HTTP traffic to HTTPS. - Set
Minimum TLS Version
toTLS 1.2
.
Generate Private Key and Certificate with Cloudflare
- Go to the
Origin Server
tab in theSSL/TLS
section. - Click
create certificate
to generate private key and certificate with Cloudflare.

- Paste Certificate and Private Key on the Origin Server.
sudo mkdir -p /etc/ssl/cloudflare
sudo nano /etc/ssl/cloudflare/your_domain.crt
sudo nano /etc/ssl/cloudflare/your_domain.key

Step 6: Set Up Ghost CMS
With the necessary tools installed, you can now set up Ghost CMS.
1. Create a Directory for Ghost
sudo mkdir -p /var/www/sitename
sudo chown newuser:newuser /var/www/sitename
sudo chmod 775 /var/www/sitename
2. Install Ghost
cd /var/www/sitename
ghost install
Follow the installation prompts to set up Nginx and SSL. The Ghost CLI will create initial Nginx and SSL configurations which we will adjust manually in the next step to use the SSL certificates from Cloudflare.
Step 7: Configure Nginx
Nginx will serve as the reverse proxy for your Ghost CMS. It's important to configure Nginx to pass the original IP address of clients to your application, use the SSL certificates obtained from Cloudflare, and secure your site by disabling direct IP access.
Block Direct IP Access
- Edit the default Nginx configuration to block direct IP access:
sudo nano /etc/nginx/sites-available/default
# Disable direct IP access
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 default_server;
listen [::]:443 default_server;
server_name _;
ssl_reject_handshake on;
return 444;
}
Configure Nginx for Ghost
- Edit Nginx Configuration for Ghost:
Replace with your own domain configuration file.
sudo nano /etc/nginx/sites-available/curiouskit.dev-ssl.conf
- Add the following configuration:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.curiouskit.dev curiouskit.dev;
# setup our access and error logs
access_log /var/log/nginx/curiouskit.dev.access.log;
error_log /var/log/nginx/curiouskit.dev.error.log;
# SSL certificate and key files
ssl_certificate /etc/ssl/cloudflare/your_domain.crt;
ssl_certificate_key /etc/ssl/cloudflare/your_domain.key;
include /etc/nginx/snippets/ssl-params.conf;
server_tokens off; # Hide Nginx version number
# Cloudflare IP ranges to log original client IP
# https://www.cloudflare.com/ips/
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/12;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
real_ip_header CF-Connecting-IP;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:2368;
}
client_max_body_size 1g;
}
SSL Parameters
Open the SSL parameters configuration file:
sudo nano /etc/nginx/snippets/ssl-params.conf
Add the following SSL parameters:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload';
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
ssl_dhparam /etc/nginx/snippets/dhparam.pem;
3. Enable the Configuration:
# Test the Nginx configuration for syntax errors
sudo nginx -t
# Restart Nginx to apply the changes
sudo systemctl restart nginx
Step 8: Configure firewall
To protect your server from unauthorized access, you should configure nftables. This setup will deny all traffic by default, allow necessary traffic, and secure your Ghost CMS server. We'll use nftables to define rules that handle traffic appropriately.
Create or update the main nftables configuration file /etc/nftables.conf
:
#!/usr/sbin/nft -f
flush ruleset
# Create the base table and chains
table inet my_table {
# Define Cloudflare IPs set
include "/etc/nftables.d/cloudflare-ips.conf"
chain input {
type filter hook input priority 0; policy drop;
# Drop invalid packets.
ct state invalid drop
# Allow loopback traffic.
iifname lo accept
# Allow all ICMP traffic, but enforce a rate limit
# to help prevent some types of flood attacks.
ip protocol icmp limit rate 4/second accept
# Allow SSH on port 8124
tcp dport 8124 ct state new,established accept
# Allow incoming HTTP/HTTPS responses
tcp sport {80, 443} ct state established accept
# Allow incoming DNS responses
udp sport 53 ct state established accept
tcp sport 53 ct state established accept
ip saddr $cloudflare_ips tcp dport 443 ct state new,established accept
# Log and drop everything else
log prefix "nftables: INPUT DROP " drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy drop;
# Allow all traffic on the localhost interface
oif lo accept
# Allow established SSH connections
tcp sport 8124 ct state established accept
# Allow outgoing HTTP/HTTPS requests
tcp dport {80, 443} ct state new,established accept
# Allow outgoing DNS requests
udp dport 53 ct state new,established accept
tcp dport 53 ct state new,established accept
# Allow outgoing ICMP (ping) traffic
ip protocol icmp accept
# Allow outgoing HTTPS traffic to established connections
tcp sport 443 ct state established accept
# Log and drop everything else
log prefix "nftables: OUTPUT DROP " drop
}
}
Cloudflare IPs Configuration
Ensure that the Cloudflare IPs configuration file /etc/nftables.d/cloudflare-ips.conf
is up-to-date:
define cloudflare_ips = {
103.21.244.0/22,
103.22.200.0/22,
103.31.4.0/22,
104.16.0.0/13,
104.24.0.0/14,
108.162.192.0/18,
141.101.64.0/18,
162.158.0.0/15,
172.64.0.0/13,
173.245.48.0/20,
188.114.96.0/20,
190.93.240.0/20,
197.234.240.0/22,
198.41.128.0/17
}
Script to Update Cloudflare IPs
#!/bin/bash
set -e
# Variables
NFTABLES_CONF="/etc/nftables.conf"
CLOUDFLARE_CONF="/etc/nftables.d/cloudflare-ips.conf"
BACKUP_DIR="/etc/nftables.d/backups"
LOG_FILE="/home/keelan/log/nftables-update.log"
TIMESTAMP=$(date +"%Y%m%d%H%M%S")
# Create backup directory and log directory if they don't exist
mkdir -p $BACKUP_DIR
mkdir -p $(dirname $LOG_FILE)
touch $CLOUDFLARE_CONF
# Function to fetch Cloudflare IP ranges
fetch_cloudflare_ips() {
curl -s https://www.cloudflare.com/ips-v4
}
# Backup existing configuration
cp $NFTABLES_CONF $BACKUP_DIR/nftables.conf.$TIMESTAMP
cp $CLOUDFLARE_CONF $BACKUP_DIR/cloudflare-ips.conf.$TIMESTAMP
# Fetch the latest Cloudflare IP ranges
CLOUDFLARE_IPS=$(fetch_cloudflare_ips)
# Generate new Cloudflare configuration file
NEW_CLOUDFLARE_CONF=$(echo $CLOUDFLARE_IPS | tr ' ' '\n' | sed 's/^/ /' | sed '$!s/$/,/' | sed '1 i\define cloudflare_ips = {' | sed '$ a\}')
# Check if the configuration has changed
if ! cmp -s <(echo "$NEW_CLOUDFLARE_CONF") $CLOUDFLARE_CONF; then
echo "Changes"
# Update Cloudflare configuration file
echo "$NEW_CLOUDFLARE_CONF" > $CLOUDFLARE_CONF
# Validate the nftables configuration
nft -c -f $NFTABLES_CONF
# Apply the updated nftables configuration
if nft -f $NFTABLES_CONF; then
echo "$(date +"%Y-%m-%d %H:%M:%S") - Updated Cloudflare IPs and reloaded nftables configuration" >> $LOG_FILE
else
echo "$(date +"%Y-%m-%d %H:%M:%S") - Failed to reload nftables configuration" >> $LOG_FILE
# Restore the previous configuration in case of failure
cp $BACKUP_DIR/nftables.conf.$TIMESTAMP $NFTABLES_CONF
cp $BACKUP_DIR/cloudflare-ips.conf.$TIMESTAMP $CLOUDFLARE_CONF
systemctl restart nftables
echo "$(date +"%Y-%m-%d %H:%M:%S") - Restored previous nftables configuration due to failure" >> $LOG_FILE
fi
else
echo "$(date +"%Y-%m-%d %H:%M:%S") - No changes in Cloudflare IPs" >> $LOG_FILE
fi
Step 9: Set Up a Droplet Firewall
1. Use Your Hosting Provider's Firewall: Configure the firewall through your hosting provider’s dashboard to allow only necessary ports (typically your SSH port, 80, 443).
Step 10: Secure Ghost Admin with Cloudflare Zero Trust
Restrict access to the Ghost admin panel using Cloudflare Zero Trust while allowing Content API.
1. Enable Zero Trust: In the Cloudflare dashboard, go to Zero Trust set up Access Policies to restrict access to the Ghost admin panel.
Create an Access Application for Ghost Admin:
- Go to Access > Applications.
- Click Add an application and choose Self-hosted.
- Name the application (e.g., "Ghost Admin").
- Set Application domain to
yourdomain.com/ghost
. - Click Next.
Access Policy
- Name the policy (e.g., "Protect Ghost Admin").
- Set Action to Allow.
- Under Include, specify authorized email addresses or groups.
- Click Next and Add application.
Create a Bypass Rule for the Content API
- Create a Bypass Application for Content API:
- Go to Access > Applications.
- Click Add an application and choose Self-hosted.
- Name the application (e.g., "Ghost Content API").
- Set Application domain to
yourdomain.com/ghost/api/content
. - Click Next.
- Create Bypass Policy:
- Name the policy (e.g., "Bypass Ghost Content API").
- Set Action to Bypass.
- Under Include, select Everyone.
- Click Next and Add application.
Verify Configuration
- Verify Access:
- Go to
https://yourdomain.com/ghost
and ensure authentication is required. - Access
https://yourdomain.com/ghost/api/content
to confirm it is publicly accessible.
- Go to
Step 11: Regular Backups
Ensuring regular backups of your Ghost CMS content is crucial to protect against data loss. For a comprehensive, step-by-step guide on setting up end-to-end encrypted backups using rclone
, please refer to my detailed blog post:

Step 12: Monitor Website Uptime with UptimeRobot
Monitoring your website's uptime helps you ensure it's always available to your visitors.
- Sign Up for UptimeRobot: Register an account at UptimeRobot.
- Add a New Monitor:
- Select “HTTP(s)” as the monitor type.
- Enter your website URL.
- Set the monitoring interval and enable notifications.
- Set up a notification to be sent when your website is back online, ensuring you're aware of both downtime and recovery events.

Conclusion
Congratulations! You have successfully set up Ghost CMS on a Linux server with enhanced security and performance using Cloudflare. Your website is now live and ready to handle visitors securely. Remember to regularly update your server and Ghost installation to keep your site secure and running smoothly.
Happy blogging!