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!

From Ground Zero to Go-Live: Guide to Setting Up Ghost CMS on Linux

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

  1. 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.
  2. 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

  1. 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.

  1. Configure to use DNSSEC by going at Domain Registrations > Manage Domains > your domain > Configuration
  1. 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.

  1. Access Your Server: Use SSH to connect to your server.
ssh root@your_server_ip
  1. Create a New User: For security, create a new user with sudo privileges.
adduser keelan
usermod -aG sudo keelan
  1. Rsync SSH Configuration: Set up SSH keys and permissions for the new user.
rsync --archive --chown=keelan:keelan ~/.ssh /home/keelan
  1. SSH Hardening:
    • Open the SSH configuration file.
nano /etc/ssh/sshd_config
    • To disable root SSH login, find and change PermitRootLogin to no.
    • To disable Password Authentication, ensure PasswordAuthentication is set to no.
    • Change SSH Port to something else. For example 8123.
  1. Restart the SSH service.
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo systemctl status ssh.socket ssh.service
đź’ˇ
SSHd now uses socket-based activation (Ubuntu 22.10 and later).
  1. 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.

  1. Create a Swap File:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
  1. 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,

  1. Update packages
sudo apt-get update
sudo apt-get upgrade
  1. Install nginx
sudo apt-get install nginx
  1. 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
  1. 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
  1. 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 the SSL/TLS section.
    • Ensure Always Use HTTPS is enabled to redirect all HTTP traffic to HTTPS.
    • Set Minimum TLS Version to TLS 1.2.

Generate Private Key and Certificate with Cloudflare

  1. Go to the Origin Server tab in the SSL/TLS section.
  2. Click create certificate to generate private key and certificate with Cloudflare.
  1. 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

  1. 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

  1. Edit Nginx Configuration for Ghost:

Replace with your own domain configuration file.

sudo nano /etc/nginx/sites-available/curiouskit.dev-ssl.conf 
  1. 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

  1. 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.
  2. 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

  1. 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.

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:

Implementing End-to-End Encrypted Backups with Rclone
Learn how to set up a robust, end-to-end encrypted backup strategy for your data using Rclone. This tutorial ensures your valuable information remains secure and your hard work preserved by leveraging the power of cloud storage and encryption.

Step 12: Monitor Website Uptime with UptimeRobot

Monitoring your website's uptime helps you ensure it's always available to your visitors.

  1. Sign Up for UptimeRobot: Register an account at UptimeRobot.
  2. Add a New Monitor:
    • Select “HTTP(s)” as the monitor type.
    • Enter your website URL.
    • Set the monitoring interval and enable notifications.
  3. 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!