Innovate | Connect | Transform
ContactService statusUSD $

GICT knowledge base

Run Blog & Newsletters with Ghost: Complete Ubuntu 24.04 Setup Guide

Published

Self Hosted
photo_5858586063675264723_y

Every time a Substack writer collects a paid subscription, Substack keeps 10%. A writer earning $500/month from their newsletter pays Substack $600/year. Forever. That number grows with your success. Ghost does not take a cut. Ghost is open source software you run on your own server. You pay once for the VPS and nothing else, ever.

The self-hosted version of Ghost has every feature Substack offers. Paid subscriptions, email newsletters, member management, premium content gating, built-in analytics. The difference is that your subscriber list belongs to you. If Substack changes its policy tomorrow, you have no recourse except to leave. If you self-host Ghost and the worst happens, your list is still on your server in a MySQL dump.

Here is how to deploy Ghost on Ubuntu 24.04. It takes about 30 minutes if you follow the steps in order.

What You Need Before Starting

  • Ubuntu 24.04 VPS with at least 1GB RAM (2GB recommended) – Get it on InterServer at $3 per month!
  • A registered domain with an A record pointing at your VPS IP
  • SSH access to the server

Ghost needs a domain for its SSL certificate setup. Certbot will verify domain ownership automatically during installation, so DNS must be active before you begin. If you do not have a domain yet, use Porkbun or Cloudflare to register one and create the A record before continuing.

If you are testing without a domain, nip.io works as a free wildcard DNS service. Any subdomain like ghost.YOUR_VPS_IP.nip.io resolves to your VPS IP. Replace YOUR_VPS_IP with your actual IP using dashes, not dots. For example, if your IP is 49.13.74.134, use ghost.49-13-74-134.nip.io. Note that SSL certificates from Let’s Encrypt do not work with nip.io, so you will need to accept a browser SSL warning or use a real domain for production.

Step 1: Install Node.js v22

Ghost runs on Node.js. The Ubuntu default repository has an old version. Use NodeSource to get the current LTS release:

curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --batch --yes --dearmor -o /etc/apt/keyrings/nodesource.gpg
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt update
sudo apt install -y nodejs
node --version
npm --version

You should see Node.js 22.x and npm 10.x in the output. Keep these numbers in mind.

Step 2: Install MySQL

Ghost stores all your content, members, and subscription data in MySQL. Install it with:

sudo apt install -y mysql-server

And execute the following command to secure your mysql installation.

mysql_secure_installation

Answer the questions and enter the root password if asked. Once done, You are ready to move forward to step 3.

Step 3: Create the Database and User

Create a dedicated MySQL database and user for Ghost. Run each line separately:

sudo mysql -e "CREATE DATABASE ghost_prod CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -e "CREATE USER 'ghost'@'localhost' IDENTIFIED BY 'GhostUserPassword456';"
sudo mysql -e "GRANT ALL PRIVILEGES ON ghost_prod.* TO 'ghost'@'localhost'; FLUSH PRIVILEGES;"

Replace GhostUserPassword456 with a strong password and record it. Ghost will ask for the MySQL username and password during setup.

Step 4: Install nginx

Ghost uses nginx as its reverse proxy web server. Install it and open the necessary ports:

sudo apt install -y nginx
sudo ufw allow 'Nginx Full'

Verify nginx is running:

sudo systemctl status nginx

Look for “active (running)” in the output.

Step 5: Create the Ghost System User

Run Ghost under its own system user, not as root. Ghost-CLI refuses to run as root. Create the user with this command:

sudo useradd -m -s /bin/bash -G sudo ghost
echo "ghost:GhostLoginPassword789" | sudo chpasswd

Do not use adduser here. adduser is interactive and will hang when you run it over SSH without a TTY.

Immediately after creating the user, run this:

sudo chmod 755 /home/ghost

This step is required. Ghost-CLI checks that the ghost user home directory is readable before installation begins. Without this permission set, ghost install fails with “The directory /home/ghost is not readable by other users on the system.”

Step 6: Install Ghost-CLI

Install the Ghost command line tool globally via npm:

sudo npm install -g ghost-cli@latest

Here is the part that trips up almost everyone. When Node.js is installed via NodeSource, npm lives in a non-standard location that sudo does not inherit. sudo ghost fails with “command not found” even though the install succeeded. Fix it with a symlink:

sudo ln -sf $(npm bin -g)/ghost /usr/local/bin/ghost
sudo ghost --version

The version number should print without errors.

Step 7: Fix Node Version Conflicts

Check if your system already has an older version of Node installed:

node --version

If you see a version below 22 (for example, v18.x from Ubuntu’s default repository), ghost-cli may fail later with a pnpm error. Hide the system node so only the newer version is active:

sudo mv /usr/bin/node /usr/bin/node.system.bak
node --version

After running this, node --version should show v22.x. If it does not, restart your SSH session and try again.

Step 8: Run Ghost Install

Switch to the ghost user and navigate to the install directory:

sudo -u ghost -i
cd /var/www/ghost

Run the installer:

ghost install

The installer asks a series of questions. Here is the full list with the correct answers:

  • Blog URL: Enter your full domain with https, for example https://yourblog.com
  • MySQL hostname: Press Enter to accept localhost
  • MySQL username: ghost
  • MySQL password: The password you set in Step 3
  • Ghost database name: ghost_prod
  • Set up a Ghost MySQL user: Yes
  • Configure nginx: Yes
  • Set up SSL: Yes (provide your email for certificate renewals)
  • Set up systemd: Yes
  • Start Ghost: Yes

Installation takes 2-5 minutes depending on your server speed.

Important: ghost install runs internal commands that require a TTY. If you are connected over SSH without a pseudo-terminal (for example, using ssh user@host 'command' without -t), the install fails with “Prompts have been disabled.” To work around this, use ssh -t user@host to get a proper terminal, or use the manual setup method described below.

Ghost instance installed successfully!
Url: https://yourblog.com
Admin: https://yourblog.com/ghost

Save the admin URL. The first time you visit it, Ghost prompts you to create your owner account.

If SSL fails with a DNS error, your domain A record may not have propagated yet. Run ghost setup ssl after DNS confirms working, or test with curl -k https://your-domain.com to bypass the certificate warning temporarily.

If ghost install fails partway through with “Prompts have been disabled,” create the config file manually and start Ghost directly:

sudo -u ghost cat > /var/www/ghost/config.production.json << 'EOF'
{
  "url": "https://yourblog.com",
  "server": { "port": 2368, "host": "127.0.0.1" },
  "database": {
    "client": "mysql",
    "connection": {
      "host": "localhost",
      "user": "ghost",
      "password": "GhostUserPassword456",
      "database": "ghost_prod"
    }
  },
  "mail": { "transport": "Direct" },
  "logging": { "level": "info" },
  "process": "systemd",
  "paths": { "contentPath": "/var/www/ghost/content" }
}
EOF
sudo chown -R ghost:ghost /var/www/ghost/config.production.json
sudo -u ghost NODE_ENV=production node /var/www/ghost/current/index.js &

Step 9: Complete Your Publication Setup

Open your browser and go to https://yourdomain.com/ghost. Create your owner account with your name, email, site title, and a password.

Once logged in, the Ghost admin panel has these key sections:

  • Posts: Write and publish articles using the Markdown editor
  • Members: View and manage your subscriber list, paid members, and free subscribers
  • Tiers: Create paid membership levels with different price points
  • Email newsletters: Draft an email, preview it, and send it directly to your full list
  • Analytics: See traffic, subscriber growth, and paid conversion rates

Ghost automatically generates an RSS feed, XML sitemap, and JSON schema markup for SEO on every post.

Configure Email Sending

Ghost needs an email service to send newsletters and member notifications. Without this, new signups do not receive confirmation emails and you cannot send bulk newsletters.

Mailgun offers 5,000 free emails per month. Register at Mailgun, add your domain, and note your SMTP credentials. In the Ghost admin panel, go to Settings, then Email newsletters, and enter:

  • SMTP hostname: smtp.mailgun.org
  • SMTP username: [email protected]
  • SMTP password: your Mailgun SMTP password
  • Port: 587

Save and send a test email to verify delivery.

Set Up Paid Subscriptions

Navigate to Settings, then Memberships, and enable paid memberships. Ghost has Stripe built in for payment processing. Connect your Stripe account, create at least one tier (for example, $7/month for premium posts), and publish your publication.

Ghost handles the entire subscription lifecycle. Stripe payments, failed payment retries, member upgrades and cancellations, and delivery of premium content. You never touch a payment spreadsheet.

The key difference from Substack is your revenue. Substack charges 10% of paid subscriptions. Ghost takes nothing. Your Stripe fees (2.9% plus 30 cents per transaction) go to Stripe, not to a platform.

Keep Ghost Updated

Update Ghost to the latest version with one command:

cd /var/www/ghost
ghost update

Ghost CLI checks the current version, downloads the update, runs database migrations if needed, and restarts the service. Back up your content regularly by exporting from the admin panel under Settings, then Labs, then Export.

Ready to stop paying platform fees? InterServer VPS plans start at $6/month with the resources needed to run Ghost. Full root access, unmetered bandwidth, and Ubuntu 24.04 ready to go.

Related guides

Account services are unavailable until the production backend is configured.