Skip to content

akora/ghost-blog-automation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Ghost Blog Automation Setup

πŸ“– Project Overview

This project provides automated Ghost 6.0 blog deployment on DigitalOcean Ubuntu 22.04 droplets using Ansible. It delivers a production-ready Ghost blog with comprehensive security, performance optimization, and modern web standards.

🎯 What This Project Does

  • Automated Ghost 6.0 Installation: Latest Ghost version with Node.js 22.x LTS support
  • Complete Infrastructure Setup: MySQL database, Nginx reverse proxy, SSL/TLS configuration
  • Production Security: SSH hardening, firewall configuration, security headers, and access controls
  • Cloudflare Integration: Flexible SSL termination and CDN optimization
  • Zero-Touch Deployment: Single-command deployment from fresh droplet to production blog

πŸš€ Key Features

  • Ghost 6.0 Features: Lexical editor, Collections, member recommendations, enhanced performance
  • Security-First: SSH key authentication, disabled root login, comprehensive firewall rules
  • Modern Stack: Node.js 22.x LTS, MySQL 8.0, Nginx with security headers
  • Scalable Architecture: Designed for production workloads with performance optimization
  • Maintenance Tools: Automated backup scripts, status monitoring, update procedures

πŸ› οΈ Technology Stack

  • CMS: Ghost 6.0 (Latest)
  • Runtime: Node.js 22.x LTS
  • Database: MySQL 8.0
  • Web Server: Nginx (Reverse Proxy)
  • OS: Ubuntu 22.04 LTS
  • Infrastructure: DigitalOcean Droplets
  • Automation: Ansible Playbooks
  • CDN/Security: Cloudflare (Optional)

πŸ“‹ Prerequisites

  • DigitalOcean account with API access (doctl configured)
  • SSH key pair for server access
  • Domain name (optional, for production setup)
  • Cloudflare account (optional, for CDN/security)

βš™οΈ Configuration Required Before Deployment

IMPORTANT: This repository uses generic placeholders that must be replaced with your actual values before deployment.

πŸ”§ Files to Configure

1. inventory - Server Connection Details

[droplets]
droplet ansible_host=YOUR_SERVER_IP ansible_python_interpreter=/usr/bin/python3 ansible_ssh_private_key_file=~/.ssh/your_private_key

Replace:

  • YOUR_SERVER_IP β†’ Your actual server IP address

2. vars.yml - Admin User & SSH Configuration

ADMIN_USER: your_admin_username
SSH_PUBLIC_KEY: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIYourPublicKeyHere your_email@example.com"
SSH_PRIVATE_KEY_PATH: "~/.ssh/your_private_key"

Replace:

  • your_admin_username β†’ Your desired admin username
  • YourPublicKeyHere β†’ Your actual SSH public key
  • your_email@example.com β†’ Your email address
  • your_private_key β†’ Your SSH private key filename

3. ghost-vars.yml - Domain & Database Configuration

GHOST_DOMAIN_NAME: "your-domain.com"
GHOST_URL: "http://your-domain.com"
GHOST_DB_PASSWORD: "your_secure_ghost_password"
GHOST_DB_ROOT_PASSWORD: "your_secure_root_password"

Replace:

  • your-domain.com β†’ Your actual domain name
  • your_secure_ghost_password β†’ Strong password for Ghost database user
  • your_secure_root_password β†’ Strong password for MySQL root user

πŸ” Placeholder Summary

Placeholder Replace With Found In
YOUR_SERVER_IP Your droplet IP address inventory
your_admin_username Your desired admin username vars.yml, all examples
your-domain.com Your actual domain ghost-vars.yml, examples
your_private_key Your SSH key filename vars.yml, inventory
YourPublicKeyHere Your SSH public key vars.yml
your_email@example.com Your email address vars.yml
your_secure_*_password Strong passwords ghost-vars.yml

⚠️ Security Notes

  • Never commit real credentials to version control
  • Use strong passwords (20+ characters) for database credentials
  • Verify SSH key permissions are correct (chmod 600 for private keys)
  • Test SSH connection before running playbooks

Initial Setup (New Droplet)

Get the IP address of the current / old droplet

doctl compute droplet list --format ID,Name,PublicIPv4,Status

Remove old IP from ~/.ssh/known_hosts

ssh-keygen -R <old IP>

Get the name and ID of the SSH key

doctl compute ssh-key list

Destroy old VM

doctl compute droplet delete your-ghost-droplet

Provision new VM

doctl compute droplet create your-ghost-droplet \
  --image ubuntu-22-04-x64 \
  --size s-1vcpu-2gb \
  --region fra1 \
  --ssh-keys YOUR_SSH_KEY_FINGERPRINT \
  --wait

Check provisioned VM to get the new IP address

doctl compute droplet list --format ID,Name,PublicIPv4,Status

Add new IP to ~/.ssh/config

sed -i '' 's/<old IP>/<new IP>/' ~/.ssh/config
Host do-droplet
    HostName <new IP>  
    User your_admin_username
    IdentityFile ~/.ssh/your_private_key

Update inventory with new IP

[droplets]
do-droplet ansible_host=<new IP> ansible_python_interpreter=/usr/bin/python3 ansible_ssh_private_key_file=~/.ssh/your_private_key

πŸš€ PHASE 1: Fresh Installation (Root Access)

Note: These commands connect as root user for initial setup

Test Connection

ansible all -i inventory -m ping --private-key ~/.ssh/your_private_key

Full Deployment Sequence

1. Baseline Setup (User + System)

ansible-playbook -i inventory playbooks/baseline.yml --private-key ~/.ssh/your_private_key

2. Install MySQL Database

ansible-playbook -i inventory playbooks/mysql.yml --private-key ~/.ssh/your_private_key

3. Install Node.js 20.x LTS

ansible-playbook -i inventory playbooks/nodejs.yml --private-key ~/.ssh/your_private_key

4. Install Ghost Blog

ansible-playbook -i inventory playbooks/ghost.yml --private-key ~/.ssh/your_private_key

5. Install Nginx Reverse Proxy

ansible-playbook -i inventory playbooks/nginx.yml --private-key ~/.ssh/your_private_key

6. Configure Firewall Security

ansible-playbook -i inventory playbooks/firewall.yml --private-key ~/.ssh/your_private_key

7. Harden SSH Security (⚠️ FINAL STEP)

ansible-playbook -i inventory playbooks/ssh-security.yml --private-key ~/.ssh/your_private_key

⚠️ WARNING: After SSH hardening, root login is disabled. Use Phase 2 commands below.

🌐 Cloudflare DNS Setup Requirements

Before accessing your site, ensure Cloudflare is properly configured:

DNS Configuration

  1. A Record: Point your-domain.com to your server IP
  2. Proxy Status: Enable Cloudflare proxy (orange cloud)
  3. SSL Mode: Set to "Flexible" in Cloudflare SSL/TLS settings

DNS Propagation

  • DNS changes may take up to 24 hours to propagate globally
  • Test with different DNS servers: dig @1.1.1.1 your-domain.com
  • For immediate testing, add to /etc/hosts: 104.21.43.171 your-domain.com

SSL Configuration

  • Ghost uses HTTP backend (configured automatically)
  • Cloudflare handles HTTPS termination
  • This prevents redirect loops while maintaining security

πŸ‘€ Ghost Admin Panel Access

Accessing the Admin Panel

Once your Ghost blog is deployed, access the admin panel at:

https://your-domain.com/ghost/

First-Time Setup

Important: Ghost requires manual admin account creation for security reasons. The installation process cannot automatically create admin users.

On your first visit, Ghost will guide you through the setup wizard:

  1. Create Admin Account

    • Enter your admin email and password
    • Choose a strong password for security
    • This is the only way to create the initial admin user
  2. Configure Blog Settings

    • Set your blog title and description
    • Configure basic site settings
    • Site timezone and language
    • Publication details
  3. Choose Theme (optional)

    • Select from available themes
    • Customize site appearance
  4. Invite Team Members (optional)

    • Add additional authors or editors
    • Set user roles and permissions

Admin Panel Features

  • πŸ“ Posts & Pages: Create and manage content
  • 🎨 Design: Customize themes and site appearance
  • βš™οΈ Settings: Configure site settings and integrations
  • πŸ‘₯ Members: Manage subscribers and memberships
  • πŸ“Š Analytics: View site statistics and performance
  • πŸ”§ Labs: Enable experimental features

πŸ”’ Security Features

Built-in Security Hardening

This Ghost installation includes comprehensive security measures:

1. Enhanced Admin Panel Protection

  • Rate limiting: 5 requests/min for admin API endpoints
  • Sensitive endpoint protection: 3 requests/min for authentication/session/users
  • Separate rate limiting zones for different API types

2. Content Security Policy (CSP)

  • XSS protection: Comprehensive CSP headers prevent code injection
  • Resource restrictions: Controls script, style, image, and font sources
  • Applied to all server blocks for complete coverage

3. Ghost Configuration Security

  • Proxy trust: Proper IP handling behind reverse proxy
  • Spam protection: 10-minute minimum wait between failed login attempts
  • Built-in CSP: Additional Ghost-level content security

4. Infrastructure Security

  • Cloudflare protection: DDoS mitigation and WAF
  • SSH hardening: Key-based auth, disabled root login
  • Firewall (UFW): Only necessary ports open
  • Database isolation: Separate Ghost user with minimal privileges
  • Service isolation: Ghost runs as non-root user

Security Headers Applied

X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:

πŸš€ Future Security Enhancements

The following security measures are planned for future implementation:

High Priority

1. File System Security
# Restrict Ghost directory permissions
chown -R ghost:ghost /var/www/ghost
chmod -R 750 /var/www/ghost
chmod 640 /var/www/ghost/config.production.json
2. Enhanced Logging & Monitoring
# Enhanced security logging in Nginx
log_format ghost_security '$remote_addr - $remote_user [$time_local] '
                         '"$request" $status $body_bytes_sent '
                         '"$http_referer" "$http_user_agent" '
                         '$request_time $upstream_response_time';

access_log /var/log/nginx/ghost_security.log ghost_security;
3. Backup Security
  • Encrypted backups of Ghost content and database
  • Offsite storage (separate from main server)
  • Regular restore testing
  • Automated backup rotation

Medium Priority

4. Advanced Rate Limiting
# Geographic rate limiting
map $geoip_country_code $limit_country {
    default "";
    CN "country";
    RU "country";
}

limit_req_zone $limit_country zone=per_country:1m rate=10r/m;
5. Intrusion Detection
  • Fail2ban configuration for Ghost-specific attacks
  • Log monitoring for suspicious patterns
  • Automated IP blocking for repeated violations
6. SSL/TLS Hardening
# Advanced SSL configuration (when not using Cloudflare)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

Low Priority

7. Content Validation
  • File upload restrictions (if uploads enabled)
  • Content scanning for malicious code
  • Theme/plugin security auditing
8. Advanced Monitoring
  • Performance monitoring with alerts
  • Security event correlation
  • Automated threat response
9. Compliance Features
  • GDPR compliance tools
  • Audit logging for administrative actions
  • Data retention policies

πŸ“ Implementation Notes

  • Priority order: Implement high-priority items first
  • Testing required: All changes should be tested in staging
  • Documentation: Update this README when implementing
  • Automation: Consider Ansible playbooks for repeatable deployment

πŸ”— Security Resources


πŸ”’ PHASE 2: Post-Hardening Operations (User Access)

Note: After SSH hardening, all commands must use -u your_admin_username (root login disabled)

Test Connection (Post-Hardening)

ansible all -i inventory -m ping --private-key ~/.ssh/your_private_key -u your_admin_username

Site Management Commands

Get Ghost Status

ansible all -i inventory -m shell -a "systemctl status ghost_your-domain-com" --private-key ~/.ssh/your_private_key -u your_admin_username

Restart Ghost Service

ansible all -i inventory -m shell -a "sudo systemctl restart ghost_your-domain-com" --private-key ~/.ssh/your_private_key -u your_admin_username

View Ghost Logs

ansible all -i inventory -m shell -a "sudo journalctl -u ghost_your-domain-com -f --lines=50" --private-key ~/.ssh/your_private_key -u your_admin_username

Check Ghost Version

ansible all -i inventory -m shell -a "cd /var/www/ghost && ghost version" --private-key ~/.ssh/your_private_key -u your_admin_username

Ghost Management

Update Ghost to Latest Version

# Stop Ghost service
ansible all -i inventory -m shell -a "sudo systemctl stop ghost_your-domain-com" --private-key ~/.ssh/your_private_key -u your_admin_username

# Update Ghost
ansible all -i inventory -m shell -a "cd /var/www/ghost && ghost update" --private-key ~/.ssh/your_private_key -u your_admin_username

# Start Ghost service
ansible all -i inventory -m shell -a "sudo systemctl start ghost_your-domain-com" --private-key ~/.ssh/your_private_key -u your_admin_username

Backup Ghost Content

# Export Ghost data
ansible all -i inventory -m shell -a "cd /var/www/ghost && ghost export" --private-key ~/.ssh/your_private_key -u your_admin_username

# Backup content directory
ansible all -i inventory -m shell -a "sudo tar -czf /tmp/ghost-content-backup-$(date +%Y%m%d).tar.gz /var/www/ghost/content" --private-key ~/.ssh/your_private_key -u your_admin_username

Theme Management

Install a Custom Theme

# Download theme to themes directory
ansible all -i inventory -m shell -a "cd /var/www/ghost/content/themes && wget https://github.com/TryGhost/Casper/archive/main.zip" --private-key ~/.ssh/your_private_key -u your_admin_username

# Extract and set permissions
ansible all -i inventory -m shell -a "cd /var/www/ghost/content/themes && unzip main.zip && chown -R your_admin_username:your_admin_username Casper-main" --private-key ~/.ssh/your_private_key -u your_admin_username

# Restart Ghost to recognize new theme
ansible all -i inventory -m shell -a "sudo systemctl restart ghost_your-domain-com" --private-key ~/.ssh/your_private_key -u your_admin_username

Security Enhancements

Security Measures (Already Implemented)

VM Level Security

  • βœ… SSH Key Authentication: Password authentication disabled (VM level)
  • βœ… Firewall Protection: UFW configured with minimal open ports (VM level)
  • βœ… Domain-Only Access: IP access blocked, redirects to domain (VM level - Nginx)

Ghost Application Security

  • βœ… Admin Rate Limiting: Ghost admin interface protected by Nginx rate limiting (Application level)
  • βœ… Secure Configuration: Ghost config file permissions restricted to owner only (Application level)
  • βœ… Process Isolation: Ghost runs as non-root user with systemd security features (Application level)
  • βœ… Database Security: Dedicated Ghost database user with limited privileges (Application level)
  • βœ… Content Security: Ghost content directory properly secured (Application level)

External Service Security

  • βœ… HTTPS Termination: Cloudflare provides SSL/TLS encryption (External service)

Additional Security Recommendations

  1. Strong Admin Password: Use a password manager with 20+ character passwords
  2. Limited Admin Access: Only log in when necessary, log out immediately
  3. Regular Updates: Monitor and apply security updates promptly
  4. Access Logs: Monitor /var/log/nginx/ghost_access.log for suspicious activity
  5. Backup Strategy: Regular automated backups of database and content

System Maintenance

Update Ghost Core

# Stop Ghost service
ansible all -i inventory -m shell -a "sudo systemctl stop ghost_your-domain-com" --private-key ~/.ssh/your_private_key -u your_admin_username

# Update Ghost
ansible all -i inventory -m shell -a "cd /var/www/ghost && ghost update" --private-key ~/.ssh/your_private_key -u your_admin_username

# Start Ghost service
ansible all -i inventory -m shell -a "sudo systemctl start ghost_your-domain-com" --private-key ~/.ssh/your_private_key -u your_admin_username

Database Backup

# MySQL backup
ansible all -i inventory -m shell -a "mysqldump -u root -pyour_secure_root_password ghost_production | gzip > /tmp/ghost-db-backup-$(date +%Y%m%d-%H%M%S).sql.gz" --private-key ~/.ssh/your_private_key -u your_admin_username

# Content backup
ansible all -i inventory -m shell -a "sudo tar -czf /tmp/ghost-content-backup-$(date +%Y%m%d-%H%M%S).tar.gz /var/www/ghost/content" --private-key ~/.ssh/your_private_key -u your_admin_username

πŸ“‹ Command Pattern Summary

Phase 1 (Fresh Install): ansible-playbook ... --private-key ~/.ssh/your_private_key

Phase 2 (Post-Hardening): ansible ... --private-key ~/.ssh/your_private_key -u your_admin_username

For Ghost operations: ansible ... --private-key ~/.ssh/your_private_key -u your_admin_username

⚠️ Critical Ghost Notes

Ghost CLI: Always run Ghost CLI commands from the /var/www/ghost directory as the your_admin_username user.

Service Management: Use sudo systemctl commands for managing the Ghost service.

Example:

# βœ… CORRECT - Ghost CLI commands
ansible ... -u your_admin_username -m shell -a "cd /var/www/ghost && ghost version"

# βœ… CORRECT - Service management
ansible ... -u your_admin_username -m shell -a "sudo systemctl restart ghost_your-domain-com"

πŸ§ͺ Testing & Verification

Verification

Re-run playbooks for verification (Ansible idempotency ensures safe re-execution):

# Verify Node.js installation
ansible-playbook -i inventory playbooks/nodejs.yml --private-key ~/.ssh/your_private_key

# Verify MySQL database
ansible-playbook -i inventory playbooks/mysql.yml --private-key ~/.ssh/your_private_key

# Verify Nginx reverse proxy
ansible-playbook -i inventory playbooks/nginx.yml --private-key ~/.ssh/your_private_key

# Verify complete Ghost setup
ansible-playbook -i inventory playbooks/ghost.yml --private-key ~/.ssh/your_private_key

Manual Verification

  1. Ghost Admin Setup: Visit https://your-domain.com/ghost/ to create your admin account (required manual step)
  2. Blog Frontend: Visit http://YOUR_SERVER_IP to see your blog
  3. Service Status: Check systemctl status ghost_your-domain-com
  4. Logs: Monitor with journalctl -u ghost_your-domain-com -f

πŸš€ Quick Start Summary

# 1. Initial setup
ansible-playbook -i inventory playbooks/baseline.yml --private-key ~/.ssh/your_private_key

# 2. Install stack
ansible-playbook -i inventory playbooks/mysql.yml --private-key ~/.ssh/your_private_key
ansible-playbook -i inventory playbooks/nodejs.yml --private-key ~/.ssh/your_private_key
ansible-playbook -i inventory playbooks/ghost.yml --private-key ~/.ssh/your_private_key
ansible-playbook -i inventory playbooks/nginx.yml --private-key ~/.ssh/your_private_key

# 3. Security hardening
ansible-playbook -i inventory playbooks/firewall.yml --private-key ~/.ssh/your_private_key
ansible-playbook -i inventory playbooks/ssh-security.yml --private-key ~/.ssh/your_private_key

# 4. Verify deployment (optional)
ansible-playbook -i inventory playbooks/ghost.yml --private-key ~/.ssh/your_private_key

πŸŽ‰ Your Ghost blog is ready at http://YOUR_SERVER_IP!


About

Automated Ghost 6.0 blog deployment on DigitalOcean using Ansible

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published