> ## Content Index
> Fetch the complete content index at: https://technotes.petercruspe.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Setup Guide: Ghost CMS on Proxmox LXC (Ubuntu 24.04 Bare Metal)
- URL: https://technotes.petercruspe.com/setup-guide-ghost-cms-on-proxmox-lxc-ubuntu-24-04-bare-metal/
- Published: 2026-08-21T17:56:14.000Z
- Updated: 2026-08-22T13:32:41.000Z
- Author: Peterson

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

```text
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`:

```bash
# 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`:

```bash
# 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`:

```bash
# 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:

```bash
sudo mysql -u root -p

```

Run the following SQL queries within the prompt:

```sql
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:

```bash
sudo npm install -g ghost-cli@latest

```

Establish target deployment directory paths:

```bash
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:

```bash
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:

```bash
mysql -u ghost_db_user -p

```

## 2\. Run the reset query

Select your Ghost database and update the lock table:

```sql
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):*

```sql
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\]

```bash
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](https://dash.cloudflare.com/?ref=technotes.petercruspe.com).
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.