Deploy Ghost CMS on AWS EC2: Complete Setup Guide
Published on June 7, 2025

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.comwith 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
- SSL/TLS: Implement HTTPS using Let’s Encrypt or AWS Certificate Manager
- Firewall: Configure security groups to only allow necessary ports
- Regular Updates: Keep Ghost, Node.js, and system packages updated
- Database Security: Use strong passwords and consider MySQL for production
- Backup Strategy: Implement regular backups of your content and database
Performance Optimization
- CDN: Use CloudFront or another CDN for static assets
- Caching: Implement Redis for session storage in high-traffic scenarios
- Database: Consider MySQL or PostgreSQL for better performance at scale
- 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.