Setup Guide: Ghost CMS on Proxmox LXC (Ubuntu 24.04 Bare Metal)

Setup Guide: Ghost CMS on Proxmox LXC (Ubuntu 24.04 Bare Metal)

This guide documents how to provision and configure Ghost CMS inside a bare-metal Proxmox LXC running Ubuntu 24.04. It ensures correct Node.js/NPM version alignment and outlines routing traffic through your existing networking architecture.

Network Routing Architecture

Client Browser ──> Cloudflare (DNS/SSL) ──> Cloudflare Tunnel ──> Nginx Proxy Manager (NPM) ──> Ghost LXC (Port 2368)

Step 1: Create the Proxmox LXC Container

  1. Log into your Proxmox VE Web UI.
  2. Click Create CT in the top right.
  3. Configure the following container settings:
    • Template: Select ubuntu-24.04-standard.
    • Root Disk: Allocate at least 10 GB storage.
    • CPU: Allocate at least 2 vCPUs.
    • Memory: Allocate at least 2 GB RAM (Ghost build steps can be resource-intensive).
    • Network: Assign a Static IP address (e.g., 192.168.1.50/24) and your local gateway.
  4. Start the container and open the console.

Step 2: System Update & User Preparation

Ghost requires a non-root user with sudo privileges to run installation actions.

Run these commands inside the LXC console as root:

# Update package definitions
apt update && apt upgrade -y

# Install prerequisite tools
apt install -y curl sudo build-essential ufw git

# Create a dedicated system user for Ghost
adduser ghostuser

# Add the new user to the sudo group
usermod -aG sudo ghostuser

# Switch to the new user context
su - ghostuser

Step 3: Install Node.js (Version 22 LTS)

Ghost requires Node.js 22 LTS for production compatibility. Do not install Node.js from the stock Ubuntu repository, as the version may be unsupported.

Run these commands as ghostuser:

# Download and execute the NodeSource setup script for Node 22
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -

# Install Node.js and package manager dependencies
sudo apt-get install -y nodejs

# Verify expected operational versions
node -v  # Expected output: v22.x.x
npm -v   # Expected output: v10.x.x

Step 4: Install MySQL 8 Database Server

Ghost handles state tracking using a MySQL 8 backend database.

Run these commands as ghostuser:

# Install the core MySQL package
sudo apt install -y mysql-server

# Secure the installation and initialize root password structures
sudo mysql_secure_installation

Follow the interactive prompt instructions to set a strong root password.

Initialize the Database for Ghost

Log into your MySQL interface to create a production database asset:

sudo mysql -u root -p

Run the following SQL queries within the prompt:

CREATE DATABASE ghost_prod_db;
CREATE USER 'ghost_db_user'@'localhost' IDENTIFIED BY 'YOUR_SECURE_PASSWORD';
GRANT ALL PRIVILEGES ON ghost_prod_db.* TO 'ghost_db_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Step 5: Install Ghost-CLI & Deploy Ghost CMS

Install the global management engine tool:

sudo npm install -g ghost-cli@latest

Establish target deployment directory paths:

sudo mkdir -p /var/www/ghost
sudo chown ghostuser:ghostuser /var/www/ghost
sudo chmod 775 /var/www/ghost
cd /var/www/ghost

Initiate the interactive deployment setup loop:

ghost install

Installation Prompt Matrix

When running ghost install, provide the following inputs:

  • Blog URL: Enter your external public URL (e.g., https://blog.yourdomain.com).
  • MySQL hostname: localhost
  • MySQL username: ghost_db_user
  • MySQL password: YOUR_SECURE_PASSWORD
  • Ghost database name: ghost_prod_db
  • Set up Nginx?: No (Skip this; traffic proxy logic is handled externally by NPM).
  • Set up SSL?: No (Skip this; SSL validation is terminated at Cloudflare/NPM).
  • Set up systemd?: Yes (Generates background runtime process controllers).
  • Start Ghost?: No

Note: If the installer reports an Nginx failure warning, ignore it, as we intentionally skipped web server local configuration.

Check config.production.json's server host setting

Open /var/www/ghost/config.production.json and check the "server" block. It should be set like:

...
  "server": {
    "port": 2368,
    "host": "0.0.0.0"
  },
...

Ghost Installation Note: First-Time Boot

  • Choose "No" when prompted with "Start Ghost" at the end of ghost install.
  • Use ghost run for the very first startup.
  • Avoid ghost start on the first run.

Why you must use ghost run first:

  • Database migrations take a long time during the first boot.
  • ghost start has a short timeout limit when checking service status.
  • The service will auto-stop if the migrations take longer than this timeout.
  • A migration lock will happen if the service auto-stops midway.
  • You must reset the migration lock manually before you can try again.

Solution:

Running ghost run bypasses the system timeout. This lets the long migrations finish safely without locking up your database.


How to Reset the Migration Lock

If you accidentally used ghost start and triggered a lock, you will see a MigrationsAreLockedError. You must reset the database lock manually.

1. Log into your database

Open your MySQL prompt using your database credentials:

mysql -u ghost_db_user -p

2. Run the reset query

Select your Ghost database and update the lock table:

USE <ghost_prod_db>;

UPDATE migrations_lock SET locked=0 WHERE lock_key='km01';

(Note: If the migrations_lock table is completely empty, insert a clean unlocked record instead):

INSERT INTO migrations_lock (lock_key, locked) VALUES ('km01', 0);

3. Start Ghost correctly

Exit MySQL (exit;) and safely boot Ghost in the foreground to let the migrations finish: [6]

ghost run

Step 6: Network Ingress Configuration

1. Cloudflare DNS & Tunnel Setup

Since you already have a working Cloudflare Tunnel environment active on your network:

  1. Log into your Cloudflare Dashboard.
  2. Navigate to Networks ──> Tunnels within your account profile.
  3. Select your active tunnel architecture and edit the configuration.
  4. Add a Public Hostname record:
    • Subdomain/Domain: blog.yourdomain.com
    • Service Type: HTTP
    • URL: Input the local static network IP of your Nginx Proxy Manager instance (e.g., 192.168.1.20:80).

2. Nginx Proxy Manager Setup

  1. Open your Nginx Proxy Manager Web UI dashboard.
  2. Select Hosts ──> Proxy Hosts ──> Add Proxy Host.
  3. Fill out the configuration fields:
    • Domain Names: blog.yourdomain.com
    • Scheme: http
    • Forward Hostname / IP: Enter the Static IP assigned to your Ghost LXC container (e.g., 192.168.1.50).
    • Forward Port: 2368
    • Block Common Exploits: Enabled
    • Websockets Support: Enabled (Crucial for Ghost administrative panel interactions).
  4. Navigate to the SSL tab:
    • Choose an existing wildcard cert or select Request a new SSL Certificate from Let's Encrypt.
    • Enable Force SSL to guarantee uniform structural TLS coverage.
  5. Click Save.

Your setup is complete. You can now access your site securely by navigating to https://blog.yourdomain.com/ghost to finalize administrator profile registration.