Ghost is a publishing platform built specifically for content creators, journalists, and newsletter operators who need professional publishing tools without the complexity and plugin bloat of WordPress. Where WordPress tries to be everything — e-commerce platform, forum, LMS, portfolio site — Ghost focuses on one thing: writing and distributing content. The result is a CMS that loads fast, handles newsletters natively, supports paid memberships out of the box, and doesn't require 47 plugins to feel modern.

Self-hosting Ghost on a VPS gives you the performance and control that Ghost's managed hosting (Ghost Pro) charges $31-249/month for. You get the same software, the same features, and full ownership of your content and subscriber data. This guide walks through the complete installation on Ubuntu 24.04, from Node.js setup through production-ready configuration with email delivery, membership support, and performance tuning.

MassiveGRID Ubuntu VPS includes: Ubuntu 24.04 LTS pre-installed · Proxmox HA cluster with automatic failover · Ceph 3x replicated NVMe storage · Independent CPU/RAM/storage scaling · 12 Tbps DDoS protection · 4 global datacenter locations · 100% uptime SLA · 24/7 human support rated 9.5/10

Deploy a self-managed VPS — from $1.99/mo
Need dedicated resources? — from $19.80/mo
Want fully managed hosting? — we handle everything

Ghost vs WordPress — When Ghost Makes More Sense

Both Ghost and WordPress are content management systems, but they serve different needs. If you've already set up WordPress on a VPS (see our WordPress hosting guide), understanding when Ghost is the better choice helps you pick the right tool.

Feature Ghost WordPress
Primary focus Publishing, newsletters, memberships General-purpose CMS
Built-in newsletters Yes (native, included) No (requires plugins like Mailchimp, MailPoet)
Paid subscriptions Yes (native Stripe integration) No (requires WooCommerce or membership plugins)
Plugin ecosystem Minimal (by design) 60,000+ plugins
Performance Fast out of the box (Node.js) Varies widely based on plugins/theme
Security surface Small (no plugin system) Large (plugin vulnerabilities are common)
E-commerce Not suitable WooCommerce is excellent
Editor Modern block editor (Mobiledoc/Lexical) Gutenberg block editor
SEO Built-in (structured data, sitemaps, meta) Requires Yoast or similar plugin
Technology Node.js PHP
Self-host complexity Moderate (Node.js, CLI tool) Easy (PHP, well-documented)

Choose Ghost when: Your primary goal is publishing articles, building a newsletter audience, or monetizing through paid subscriptions. Ghost excels as a blog, magazine, or newsletter platform.

Choose WordPress when: You need extensive customization, e-commerce, complex page layouts, or features that depend on the WordPress plugin ecosystem.

Prerequisites

Ghost has specific requirements that differ from typical PHP-based CMS deployments:

If you need to set up your server from scratch, start with our Ubuntu VPS setup guide. For MySQL installation, our LEMP stack guide covers MySQL setup as part of the stack.

Resource requirements: Ghost runs efficiently on a Cloud VPS with 2 vCPU / 2GB RAM for sites with up to 50K monthly visitors. For sites with newsletters going to thousands of subscribers, consider upgrading RAM to 4GB.

Installing MySQL 8.0

If you don't already have MySQL installed, set it up:

sudo apt update
sudo apt install -y mysql-server

Secure the installation:

sudo mysql_secure_installation

Follow the prompts to set a root password, remove anonymous users, disable remote root login, and remove the test database.

Create a dedicated database and user for Ghost:

sudo mysql -u root -p
CREATE DATABASE ghost_production CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'ghost'@'localhost' IDENTIFIED BY 'your_secure_password_here';
GRANT ALL PRIVILEGES ON ghost_production.* TO 'ghost'@'localhost';
FLUSH PRIVILEGES;
EXIT;

For MySQL performance tuning (especially important if Ghost shares the server with other services), see our MySQL tuning guide.

Installing Node.js

Ghost requires a specific Node.js LTS version. As of Ghost 5.x, Node.js 18.x and 20.x are supported. We'll install Node.js 20.x using the NodeSource repository:

# Install the NodeSource repository
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -

# Install Node.js
sudo apt install -y nodejs

Verify the installation:

node --version
npm --version

You should see Node.js v20.x.x and npm 10.x.x.

Important: Do not use the Node.js version from Ubuntu's default repositories — it's often outdated and won't meet Ghost's version requirements. Always use the NodeSource repository or a version manager like nvm.

Installing Nginx

If Nginx isn't already installed:

sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx

Verify it's running:

sudo systemctl status nginx

Ghost CLI will automatically create and configure the Nginx server block during installation, so you don't need to create one manually. If you want to understand how Nginx reverse proxying works in detail, our Nginx reverse proxy guide covers the concepts.

Installing Ghost CLI

Ghost provides an official CLI tool that handles installation, configuration, updates, and maintenance. Install it globally via npm:

sudo npm install -g ghost-cli@latest

Verify the installation:

ghost --version

Creating the Ghost User and Directory

Ghost CLI requires a non-root user. If you're currently logged in as root, create a dedicated user:

sudo adduser ghostadmin
sudo usermod -aG sudo ghostadmin

Create the Ghost installation directory with proper ownership:

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

Switch to the ghost admin user:

su - ghostadmin

Installing Ghost

Navigate to the installation directory and run the Ghost installer:

cd /var/www/ghost
ghost install

The installer will ask you a series of questions:

  1. Blog URL: Enter your full URL with HTTPS (e.g., https://blog.example.com)
  2. MySQL hostname: localhost
  3. MySQL username: ghost
  4. MySQL password: the password you set earlier
  5. Ghost database name: ghost_production
  6. Set up a Ghost MySQL user? No (we already created one)
  7. Set up Nginx? Yes
  8. Set up SSL? Yes (requires domain already pointing to the server)
  9. Set up systemd? Yes
  10. Start Ghost? Yes

The installer will:

If the SSL setup fails (usually because DNS hasn't propagated yet), you can run it later:

cd /var/www/ghost
ghost setup ssl

Once installation completes, Ghost is accessible at your configured URL.

Understanding What Ghost CLI Created

The installation process creates several important files and configurations. Understanding them helps with troubleshooting and customization.

Ghost Configuration File

The main configuration lives at /var/www/ghost/config.production.json:

{
    "url": "https://blog.example.com",
    "server": {
        "port": 2368,
        "host": "127.0.0.1"
    },
    "database": {
        "client": "mysql2",
        "connection": {
            "host": "localhost",
            "user": "ghost",
            "password": "your_password",
            "database": "ghost_production"
        }
    },
    "mail": {
        "transport": "Direct"
    },
    "logging": {
        "transports": ["file", "stdout"]
    },
    "process": "systemd",
    "paths": {
        "contentPath": "/var/www/ghost/content"
    }
}

Nginx Configuration

Ghost CLI creates an Nginx configuration at /etc/nginx/sites-available/blog.example.com.conf. It sets up:

You can view it:

cat /etc/nginx/sites-available/blog.example.com.conf

Systemd Service

Ghost runs as a systemd service. Manage it with:

# Check status
ghost status

# Or using systemd directly
sudo systemctl status ghost_blog-example-com

# Restart Ghost
ghost restart

# Stop Ghost
ghost stop

# Start Ghost
ghost start

Content Directory

All your Ghost content lives in /var/www/ghost/content/:

/var/www/ghost/content/
├── data/          # SQLite database (development mode only)
├── files/         # Uploaded files (PDFs, etc.)
├── images/        # Uploaded images
├── logs/          # Ghost application logs
├── media/         # Uploaded audio/video
├── settings/      # Routes and redirects configuration
└── themes/        # Installed themes

Initial Setup — Creating Your Admin Account

Navigate to https://blog.example.com/ghost/ in your browser. This is the Ghost admin panel. On first visit, you'll see the setup wizard:

  1. Enter your site title
  2. Enter your name, email, and password for the owner account
  3. Optionally invite team members (you can skip this)

After setup, you'll land in the Ghost admin dashboard. Bookmark https://blog.example.com/ghost/ — this is your content management interface.

Email Delivery Setup — Critical for Newsletters

The default mail configuration ("transport": "Direct") attempts to send email directly from your server. This almost never works reliably — emails land in spam or get rejected entirely. For a publishing platform, reliable email is non-negotiable.

Ghost uses email for two purposes:

Configuring Mailgun (Recommended for Newsletters)

Ghost has first-class Mailgun integration for newsletter delivery. Mailgun's free tier includes a generous number of emails per month for verified domains.

After creating a Mailgun account and verifying your domain, update Ghost's configuration:

cd /var/www/ghost
nano config.production.json

Replace the "mail" section:

{
    "mail": {
        "transport": "SMTP",
        "options": {
            "service": "Mailgun",
            "host": "smtp.mailgun.org",
            "port": 587,
            "secure": false,
            "auth": {
                "user": "postmaster@mg.example.com",
                "pass": "your-mailgun-smtp-password"
            }
        }
    },
    "bulk_email": {
        "provider": "mailgun",
        "mailgun": {
            "apiKey": "your-mailgun-api-key",
            "domain": "mg.example.com",
            "baseUrl": "https://api.mailgun.net/v3"
        }
    }
}

The mail section handles transactional emails via SMTP. The bulk_email section handles newsletter delivery via Mailgun's API (which is faster and more reliable for bulk sends than SMTP).

If you're using the EU Mailgun region, change baseUrl to https://api.eu.mailgun.net/v3.

Restart Ghost to apply changes:

ghost restart

Configuring SendGrid (Alternative)

If you prefer SendGrid:

{
    "mail": {
        "transport": "SMTP",
        "options": {
            "host": "smtp.sendgrid.net",
            "port": 587,
            "secure": false,
            "auth": {
                "user": "apikey",
                "pass": "your-sendgrid-api-key"
            }
        }
    }
}

Note that SendGrid doesn't have Ghost's native bulk email integration — newsletter sends will use SMTP, which is slower for large lists but works fine for smaller audiences (under 5,000 subscribers).

Testing Email Delivery

After configuring mail, test it by going to Ghost admin → SettingsEmail newsletter and sending a test newsletter to yourself. Also test transactional email by using the "Forgot password" flow on the Ghost login page.

Configuring Memberships and Subscriptions

Ghost's membership system is one of its strongest differentiators. It supports free members (email subscribers), paid members (via Stripe), and complimentary members (manually granted access).

Enabling Memberships

In Ghost admin, go to SettingsMembership:

  1. Subscription access: Choose "Anyone can sign up" for public publications
  2. Default post access: Set who can read posts by default (Public, Members only, Paid members only)
  3. Membership tiers: Configure your free and paid tiers

Connecting Stripe for Paid Subscriptions

To accept payments:

  1. Go to SettingsMembershipStripe
  2. Click "Connect with Stripe"
  3. Authorize Ghost to connect to your Stripe account
  4. Configure pricing tiers (monthly and annual options)

Ghost handles the complete subscription lifecycle — signup, payment processing, access control, cancellation, and receipt emails — without any plugins or third-party membership tools.

Content Gating

When writing a post, you can set its visibility:

You can also use content gates within a post — the first part is public, and a "Subscribe to continue reading" prompt appears mid-article.

Ghost Themes and Customization

Ghost uses a Handlebars-based theming system. The default theme is Casper, which is clean and responsive. Themes are installed via the admin panel.

Installing a Theme

  1. Download a theme (from Ghost's theme marketplace or GitHub)
  2. In Ghost admin, go to SettingsDesign & brandingChange theme
  3. Upload the theme zip file
  4. Activate it

Customizing the Default Theme

For minor customizations without editing theme files, Ghost supports code injection. Go to SettingsCode injection:

Example — changing the accent color and adding custom fonts:

<style>
:root {
    --ghost-accent-color: #F5A623;
}

body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}

.post-content {
    font-size: 1.1rem;
    line-height: 1.8;
}
</style>

Custom Theme Development

If you need deeper customization, Ghost themes are straightforward to develop. The theme files live in /var/www/ghost/content/themes/. A Ghost theme consists of:

my-theme/
├── package.json         # Theme metadata
├── index.hbs            # Homepage template
├── post.hbs             # Single post template
├── page.hbs             # Static page template
├── tag.hbs              # Tag archive template
├── author.hbs           # Author page template
├── default.hbs          # Base layout (header/footer wrapper)
├── partials/            # Reusable template components
│   ├── navigation.hbs
│   └── post-card.hbs
└── assets/
    ├── css/
    └── js/

After editing theme files, restart Ghost or re-upload the theme to see changes.

Backup Strategy for Ghost

A complete Ghost backup covers three components: the MySQL database, the content directory (images, themes, files), and the configuration file.

Automated Backup Script

Create a comprehensive backup script:

sudo nano /opt/scripts/ghost-backup.sh
#!/bin/bash
# Ghost Backup Script
# Backs up database, content, and configuration

GHOST_DIR="/var/www/ghost"
BACKUP_DIR="/opt/backups/ghost"
TIMESTAMP=$(date +%Y%m%d-%H%M)
RETENTION_DAYS=30

# MySQL credentials
DB_NAME="ghost_production"
DB_USER="ghost"
DB_PASS="your_secure_password_here"

mkdir -p "$BACKUP_DIR/$TIMESTAMP"

# 1. Database backup
echo "Backing up MySQL database..."
mysqldump -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" \
  --single-transaction \
  --quick \
  --lock-tables=false \
  | gzip > "$BACKUP_DIR/$TIMESTAMP/database.sql.gz"

# 2. Content directory backup (images, themes, files, settings)
echo "Backing up content directory..."
tar czf "$BACKUP_DIR/$TIMESTAMP/content.tar.gz" \
  -C "$GHOST_DIR" content/ \
  --exclude="content/logs"

# 3. Configuration backup
echo "Backing up configuration..."
cp "$GHOST_DIR/config.production.json" "$BACKUP_DIR/$TIMESTAMP/"

# 4. Create a single archive
echo "Creating combined archive..."
cd "$BACKUP_DIR"
tar czf "ghost-backup-$TIMESTAMP.tar.gz" "$TIMESTAMP/"
rm -rf "$TIMESTAMP/"

# 5. Remove old backups
find "$BACKUP_DIR" -name "ghost-backup-*.tar.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup completed: ghost-backup-$TIMESTAMP.tar.gz"
echo "Size: $(du -h "$BACKUP_DIR/ghost-backup-$TIMESTAMP.tar.gz" | cut -f1)"
sudo chmod +x /opt/scripts/ghost-backup.sh

Schedule it in cron:

sudo crontab -e
0 2 * * * /opt/scripts/ghost-backup.sh >> /var/log/ghost-backup.log 2>&1

For more sophisticated backup strategies including offsite storage, see our automatic backup guide.

Ghost's Built-in Export

Ghost also provides a JSON export from the admin panel: SettingsAdvancedExport your content. This exports posts, pages, tags, and settings as JSON — useful for migrating between Ghost instances — but does not include images, themes, or the database itself. Always use the file-level backup approach above for disaster recovery.

Restoring from Backup

# Stop Ghost
cd /var/www/ghost
ghost stop

# Extract the backup
cd /opt/backups/ghost
tar xzf ghost-backup-20260228-0200.tar.gz

# Restore database
gunzip -c 20260228-0200/database.sql.gz | mysql -u ghost -p ghost_production

# Restore content directory
tar xzf 20260228-0200/content.tar.gz -C /var/www/ghost/

# Restore configuration
cp 20260228-0200/config.production.json /var/www/ghost/

# Fix ownership
sudo chown -R ghostadmin:ghostadmin /var/www/ghost/content

# Start Ghost
ghost start

Performance Optimization

Ghost is fast by default, but these optimizations ensure it stays fast under load.

Node.js Memory Configuration

Ghost's Node.js process defaults to a reasonable memory limit, but you can tune it. Edit the systemd service:

sudo systemctl edit ghost_blog-example-com

Add:

[Service]
Environment="NODE_OPTIONS=--max-old-space-size=512"

This sets the V8 heap limit to 512MB. For a 2GB RAM VPS, this leaves ample memory for MySQL, Nginx, and the OS. Increase to 768MB or 1024MB on larger instances.

sudo systemctl daemon-reload
ghost restart

MySQL Configuration for Ghost

Ghost's database queries are relatively simple — mostly read-heavy with occasional writes. Optimize MySQL for this workload by editing /etc/mysql/mysql.conf.d/mysqld.cnf:

[mysqld]
# InnoDB settings optimized for Ghost on a 2GB VPS
innodb_buffer_pool_size = 256M
innodb_log_file_size = 64M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

# Query cache (useful for Ghost's read-heavy workload)
# Note: Query cache is deprecated in MySQL 8.0 but still functional
# Use performance_schema instead for monitoring

# Connection settings
max_connections = 50
wait_timeout = 600
interactive_timeout = 600

# Temporary tables
tmp_table_size = 32M
max_heap_table_size = 32M

# Sort and join buffers
sort_buffer_size = 2M
join_buffer_size = 2M
sudo systemctl restart mysql

Image Optimization

Ghost serves uploaded images directly through Nginx. Images are often the largest component of page load. Ghost 5.x includes built-in image resizing and generates responsive image srcsets, but you can further optimize by:

  1. Using WebP format — Ghost automatically converts uploaded images to WebP in modern versions
  2. Setting appropriate Nginx caching headers — Ghost CLI configures this, but verify in the Nginx config:
location ~ ^/content/images/ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Using a CDN

For global audiences, a CDN dramatically improves load times. Cloudflare (free tier) is the most common choice:

  1. Add your domain to Cloudflare
  2. Update your domain's nameservers to Cloudflare's
  3. Enable "Proxied" (orange cloud) on the DNS record pointing to your VPS
  4. In Cloudflare settings, set caching to "Standard"
  5. Enable "Auto Minify" for JavaScript, CSS, and HTML

In Ghost admin, you don't need to change anything — Cloudflare sits transparently between users and your origin server.

Managing Ghost — Day-to-Day Operations

Updating Ghost

Ghost CLI makes updates straightforward:

cd /var/www/ghost
ghost update

This downloads the latest version, runs database migrations, and restarts Ghost. Always take a backup before updating.

To update to a specific version:

ghost update 5.100.0

Checking Logs

Ghost logs are stored in /var/www/ghost/content/logs/. View recent entries:

ghost log

Or directly:

tail -f /var/www/ghost/content/logs/blog-example-com.log

For Nginx access and error logs:

sudo tail -f /var/log/nginx/blog.example.com-access.log
sudo tail -f /var/log/nginx/blog.example.com-error.log

Ghost CLI Commands Reference

Command Purpose
ghost start Start Ghost
ghost stop Stop Ghost
ghost restart Restart Ghost
ghost status Show running status
ghost update Update to latest version
ghost log View recent logs
ghost config View/edit configuration
ghost setup ssl Set up or renew SSL
ghost doctor Diagnose common issues
ghost uninstall Completely remove Ghost

Integrations and the Ghost API

Ghost provides a Content API (read-only, for building custom frontends) and an Admin API (read-write, for programmatic content management). These enable powerful integrations.

Setting Up a Custom Integration

In Ghost admin, go to SettingsIntegrationsAdd custom integration. Give it a name (e.g., "Backup Script" or "External Frontend"). Ghost generates:

Using the Content API

Fetch published posts via HTTP:

curl "https://blog.example.com/ghost/api/content/posts/?key=YOUR_CONTENT_API_KEY&limit=5"

This returns JSON with your latest 5 posts, including title, HTML content, featured image, and metadata. You can use this to display Ghost content on other websites, build a custom frontend with Next.js or Gatsby, or syndicate content to other platforms.

Zapier and Webhooks

Ghost supports webhooks for content events. Go to SettingsIntegrationsAdd custom integrationAdd webhook. Available events include:

Use webhooks to trigger external workflows — post to social media when a new article is published, sync members to a CRM, or notify a Slack channel on new signups.

Newsletter Sending and Performance Impact

Ghost's newsletter feature sends emails to all members (or specific segments) when you publish a post. For small lists (under 1,000 subscribers), this is barely noticeable. For larger lists, the email sending process can temporarily increase CPU and memory usage.

How Ghost sends newsletters:

  1. You publish a post with the "Email" toggle enabled
  2. Ghost queues the email send operation
  3. Ghost renders the email template for each subscriber (personalized with name, unsubscribe link)
  4. Emails are sent in batches via the configured email provider (Mailgun API or SMTP)

For lists over 5,000 subscribers, the rendering and queuing process can spike CPU usage significantly. On a shared VPS, this might temporarily slow your website for visitors.

Newsletter performance: Newsletter sends to thousands of subscribers compete with web serving for CPU. Dedicated CPU on a VDS ensures your website stays responsive during bulk email operations. Dedicated resources mean the newsletter process and web serving don't contend for the same CPU cycles.

Security Considerations

Ghost has a smaller attack surface than WordPress (no plugins, no PHP), but basic security practices still apply:

For comprehensive server-level security, see our Ubuntu VPS security hardening guide.

Troubleshooting Common Issues

Ghost Won't Start — "Error: EACCES: permission denied"

This usually means Ghost is being run as root, or file permissions are wrong:

sudo chown -R ghostadmin:ghostadmin /var/www/ghost/content
sudo find /var/www/ghost -type d -exec chmod 775 {} \;
sudo find /var/www/ghost -type f -exec chmod 664 {} \;

"Error: Cannot find module" After Update

The Node.js modules may be corrupted. Reinstall them:

cd /var/www/ghost
rm -rf node_modules
ghost install --no-setup

SSL Certificate Renewal Fails

Ghost uses acme.sh for SSL, not Certbot. Check the renewal setup:

~/.acme.sh/acme.sh --list

If auto-renewal isn't working, renew manually:

cd /var/www/ghost
ghost setup ssl

High Memory Usage

Check what's consuming memory:

ghost status
free -h
sudo systemctl status ghost_blog-example-com

If Ghost is consuming too much memory, reduce the Node.js heap size and consider optimizing MySQL's buffer pool. See the performance optimization section above.

Prefer Managed Ghost Hosting?

Self-hosting Ghost gives you maximum control and cost efficiency, but it requires maintaining the server, handling updates, and managing backups yourself. If you'd rather focus entirely on content creation, MassiveGRID's fully managed dedicated cloud servers give you the same self-hosted Ghost experience with infrastructure management handled by the MassiveGRID team — OS updates, security patches, automated backups, and 24/7 monitoring included.

Summary

Ghost on a self-hosted VPS gives you a professional publishing platform with newsletters, memberships, and SEO built in — for a fraction of Ghost Pro's pricing. Here's what we covered:

  1. Install Node.js 20.x, MySQL 8.0, and Nginx as prerequisites
  2. Use Ghost CLI for a guided, production-ready installation
  3. Configure email delivery via Mailgun for reliable newsletter sending
  4. Set up memberships and Stripe integration for paid subscriptions
  5. Implement automated backups covering the database, content, and configuration
  6. Optimize performance with Node.js memory tuning, MySQL configuration, and CDN integration

For monitoring your Ghost installation, set up server monitoring to track memory usage, CPU load, and disk space. And if you're running Ghost alongside other services on the same VPS, our disk space management guide helps you keep storage usage in check.