How to Deploy Ghost on Ubuntu with Nginx and SSL in 15 Minutes

How to Deploy Ghost on Ubuntu with Nginx and SSL in 15 Minutes

This detailed tutorial will take you through the steps of installing Ghost CMS on Ubuntu 20.04/22.04 using Nginx as a reverse proxy and an SSL certificate to the same to give you secure access to HTTPS.

Prerequisites

  • Ubuntu 20.04/ 22.04 server
  • A server with at least 1GB memory (2GB is recommended)
  • NodeJS 20
  • MySQL 8
  • Systemd
  • Root/sudo access
  • Domain name that will refer to your server IP address

Step 1: Update System and Install Dependencies

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install Node.js 20.x (Ghost requirement)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs

# Install additional dependencies
sudo apt install -y nginx mysql-server build-essential

Step 2: Create Ghost User

Ghost requires a non-root user for security. Create a dedicated user:

# Create ghost user with home directory
sudo adduser ghost --disabled-password --gecos "Ghost CMS"

# Add ghost user to sudo group (needed for Ghost CLI)
sudo usermod -aG sudo ghost

# Switch to ghost user
sudo su - ghost
Important: All further Ghost commands must be executed by the ghost user and not by root

Step 3: Install Ghost CLI

# Install ghost CLI as a global (as ghost user)
sudo npm install ghost-cli@latest -g

# Create directory for Ghost installation
sudo mkdir -p /var/www/your_sitename
sudo chown ghost:ghost /var/www/your_sitename
sudo chmod 775 /var/www/your_sitename

Step 4: Configure MySQL Database

# Secure MySQL installation (run as root user)
exit  # Exit ghost user back to root
sudo mysql_secure_installation

# Create Ghost database and user
sudo mysql -u root -p

In the MySQL prompt, run:

CREATE DATABASE your_sitename_prod;
CREATE USER 'ghost'@'localhost' IDENTIFIED BY 'your_strong_password';
GRANT ALL PRIVILEGES ON your_sitename_prod.* TO 'ghost'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Switch back to ghost user:

sudo su - ghost

Step 5: Install and Configure Ghost

# Navigate to Ghost directory (as ghost user)
cd /var/www/your_sitename

# Install Ghost
ghost install

# Follow the prompts:
# - Blog URL: https://yourdomain.com
# - MySQL hostname: localhost
# - MySQL username: ghost
# - MySQL password: your_strong_password
# - Database name: your_sitename_prod
# - Set up Nginx: Yes
# - Set up SSL: Yes
# - Set up systemd: Yes
# - Start Ghost: Yes

Note: With a user that has sudo privileges, a Ghost CLI will configure automatically Nginx and set it up with SSL.

Step 5: Configure Nginx (Manual Setup, if Needed)

If you need to manually configure Nginx, first create a server block:

sudo nano /etc/nginx/sites-available/your_sitename

Add the following configuration:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    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;
    }

    location ~ /.well-known {
        allow all;
    }

    client_max_body_size 50m;
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/your_sitename /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 6: Configure SSL with Let's Encrypt

If SSL wasn't set up automatically during Ghost installation:

# Install Certbot
sudo apt install certbot python3-certbot-nginx

# Obtain SSL certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Test automatic renewal
sudo certbot renew --dry-run

Step 7: Configure Firewall

# Enable UFW firewall
sudo ufw enable

# Allow necessary ports
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw status

Step 8: Ghost Configuration and Management

Basic Ghost Commands

# Navigate to Ghost directory (as ghost user)
cd /var/www/your_sitename

# Start Ghost
ghost start

# Stop Ghost
ghost stop

# Restart Ghost
ghost restart

# Check Ghost status
ghost status

# Update Ghost
ghost update

# View logs
ghost log

Configuration File

Edit Ghost configuration if needed:

nano /var/www/your_sitename/config.production.json

Example configuration:

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

Step 9: Post-Installation Setup

Access Ghost Admin

  1. Navigate to https://yourdomain.com/ghost
  2. Create your admin account
  3. Configure your site settings

Performance Optimization

# Enable Nginx gzip compression
sudo nano /etc/nginx/nginx.conf

Add to the http block:

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss;

Set up Automatic Backups

Create a backup script:

sudo nano /usr/local/bin/your_sitename_backup.sh
#!/bin/bash
BACKUP_DIR="/backup/your_sitename"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

# Backup Ghost content
tar -czf $BACKUP_DIR/your_sitename-content-$DATE.tar.gz /var/www/your_sitename/content

# Backup database
mysqldump -u ghost -p your_sitename_backup_prod > $BACKUP_DIR/your_sitename_db_$DATE.sql

# Keep only last 7 days of backups
find $BACKUP_DIR -name "your_sitename-*" -mtime +7 -delete

Make it executable and add to crontab:

sudo chmod +x /usr/local/bin/your_sitename_backup.sh
sudo crontab -e

# Add this line for daily backups at 2 AM
0 2 * * * /usr/local/bin/your_sitename_backup.sh

Troubleshooting

Common Commands

# Check Ghost status
sudo systemctl status ghost_yourdomain-com

# View Ghost logs
sudo journalctl -u ghost_yourdomain-com -f

# Check Nginx status
sudo systemctl status nginx

# Test Nginx configuration
sudo nginx -t

# Check SSL certificate
sudo certbot certificates

Common Issues

  1. Ghost won't start: Check database connectivity and file permissions
  2. 502 Bad Gateway: Ensure Ghost is running on port 2368
  3. SSL issues: Verify certificate paths in Nginx configuration
  4. Performance issues: Consider upgrading server resources or enabling caching

Security Best Practices

  1. User Security: Ghost runs as a non-root user for security isolation
  2. Regular Updates: Keep Ghost, Ubuntu, and dependencies updated
  3. Strong Passwords: Select complex passwords of all accounts
  4. Firewall: restrict only the needed ports
  5. Backups: Apply automated frequent data backup
  6. Monitoring: Configure server check up and notifications
  7. SSL: Keep SSL certificates renewed automatically

Important Security Notes

  • Never run Ghost as root: Ghost CLI will refuse to install as root user
  • Ghost user permissions: The ghost user needs sudo access only for initial setup and updates
  • File permissions: Ghost files should be owned by the ghost user
  • Service isolation: Running Ghost as a separate user isolates it from system processes

Maintenance

  • Monthly: Update Ghost and system packages
  • Weekly: Check backup integrity
  • Daily: Monitor server resources and logs
  • As needed: Review and update security configurations

Your Ghost blog is now successfully deployed with Nginx and SSL! Access your admin panel at https://yourdomain.com/ghost to start creating content.