Deploy Ghost CMS on AWS EC2: Complete Setup Guide

Published on June 7, 2025

AWS EC2 Logo & Ghost Logo

Ghost is a powerful, open-source content management system designed specifically for modern publishing. Whether you’re building a personal blog, company publication, or professional portfolio, Ghost offers a clean, focused writing experience with robust performance capabilities.

I had a fun project to deploy and use the Ghost CMS on an AWS EC2 instance from scratch. I tried to describe the step by step guide I took during this quest.

Before we begin, ensure you have:

  • An AWS account with EC2 access
  • Basic familiarity with Linux command line
  • SSH access to your EC2 instance
  • A domain name (optional but recommended)

Server Requirements

For Ghost deployment, you’ll need:

  • Minimum Instance Type: t2.small (2 vCPUs, 2GB RAM)
  • Operating System: Ubuntu 18.04 or later
  • Storage: At least 10GB of available disk space
  • Network: Security groups allowing HTTP (80) and HTTPS (443) traffic

Step 1: Setting Up Node.js with NVM

Ghost requires Node.js to run. We’ll use NVM (Node Version Manager) for easy Node.js management:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

source ~/.bashrc

nvm install --lts
nvm use --lts

Step 2: Cloning Ghost Repository

We’ll clone Ghost from the TechilaLabs repository:

git clone --recurse-submodules https://github.com/TechilaLabs/Ghost.git
cd Ghost

git submodule update --init

Step 3: Environment Configuration

Set your Node.js environment to production:

export NODE_ENV=production

# Alternative method:

echo 'export NODE_ENV=production' >> ~/.bashrc

Step 4: Creating Production Configuration

Create a config.production.json file in your Ghost root directory:

{
  "url": "https://yourdomain.com",
  "database": {
    "client": "sqlite3",
    "connection": {
      "filename": "content/data/ghost.db"
    },
    "useNullAsDefault": true,
    "debug": false
  },
  "server": {
    "host": "0.0.0.0",
    "port": 18412
  },
  "privacy": {
    "useUpdateCheck": false,
    "useGravatar": false,
    "useRpcPing": false,
    "useStructuredData": true
  },
  "compress": true
}

Configuration Notes:

  • Replace yourdomain.com with your actual domain
  • The database uses SQLite for simplicity (consider MySQL for high-traffic sites)
  • Port 18412 is used internally; Nginx will proxy external traffic
  • Privacy settings are optimized for production environments

Step 5: Setting Up Nginx Reverse Proxy

Installing Nginx

sudo apt update
sudo apt install nginx

sudo systemctl enable nginx

sudo ufw allow 'Nginx Full'

Configuring Nginx

Create or edit /etc/nginx/nginx.conf:

user www-data;

worker_processes auto;

error_log /var/log/nginx/error.log;

pid /var/run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    # Maximum number of simultaneous connections per worker process
    worker_connections 1024;
}

### Main HTTP configuration block
http {
    # Define custom log format with detailed request information
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                   '$status $body_bytes_sent "$http_referer" '
                   '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;
    sendfile on;

    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;

    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    include /etc/nginx/conf.d/*.conf;

    # Default index files to serve
    index index.html index.htm;


    server {
        listen 80 default_server;
        listen [::]:80 default_server;

        # Domain names this server block will respond to
        server_name your-domain.com www.your-domain.com;

        # Handle static assets with caching
        location ~* \.(jpg|jpeg|png|gif|ico|css|js|eot|woff|woff2)$ {
            proxy_ignore_headers "Cache-Control";
            expires 1y;
            add_header Cache-Control "public, immutable";

            # Forward requests to Ghost application
            proxy_pass http://127.0.0.1:18412;
        }


        location ~ ^/(?:ghost|signout) {

            expires 0;
            add_header Cache-Control "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0";

            # Forward to Ghost application
            proxy_pass http://127.0.0.1:18412;


            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }


        location / {
            proxy_ignore_headers "Set-Cookie";
            proxy_hide_header "Set-Cookie";

            # Forward to Ghost application
            proxy_pass http://127.0.0.1:18412;


            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # Custom error pages
        error_page 404 /404.html;
        error_page 500 502 503 504 /50x.html;
    }
}

Test and reload Nginx configuration:

sudo nginx -t
sudo systemctl reload nginx

Step 6: Building Ghost

Build the Ghost application and client:

### Install dependencies
npm install --pure-lockfile

### Initialize database
./node_modules/knex-migrator/bin/knex-migrator init

### Build the client
cd core/client
NODE_ENV=development yarn install --pure-lockfile
./node_modules/ember-cli/bin/ember build -prod
cd ../..

Step 7: Starting Ghost

Launch Ghost in production mode:

NODE_ENV=production node index.js --name "Ghost"

For production environments, consider using a process manager like PM2:

npm install -g pm2
NODE_ENV=production pm2 start index.js --name "ghost"
pm2 save
pm2 startup

Step 8: Updating Ghost

To update your Ghost installation:

git pull

export NODE_ENV=production

npm install --pure-lockfile


./node_modules/knex-migrator/bin/knex-migrator migrate

cd core/client
NODE_ENV=development yarn install --pure-lockfile
./node_modules/ember-cli/bin/ember build -prod
cd ../..

### Restart Ghost (if using PM2)
pm2 restart ghost

Security Considerations

  1. SSL/TLS: Implement HTTPS using Let’s Encrypt or AWS Certificate Manager
  2. Firewall: Configure security groups to only allow necessary ports
  3. Regular Updates: Keep Ghost, Node.js, and system packages updated
  4. Database Security: Use strong passwords and consider MySQL for production
  5. Backup Strategy: Implement regular backups of your content and database

Performance Optimization

  1. CDN: Use CloudFront or another CDN for static assets
  2. Caching: Implement Redis for session storage in high-traffic scenarios
  3. Database: Consider MySQL or PostgreSQL for better performance at scale
  4. Monitoring: Set up CloudWatch or other monitoring solutions

Remember to implement SSL certificates, set up regular backups, and monitor your application for optimal performance. Ghost’s admin interface will be available at http://your-domain.com/ghost/ where you can complete the initial setup and start creating content.

Additional Resources