Scaling Content Delivery: How to Deploy Ghost CMS on a VPS Cluster with Automated Cloudflare Cache Purging
Introduction
In the digital publishing landscape, speed, reliability, and seamless content delivery are paramount. Ghost CMS has emerged as a premier choice for modern media platforms due to its clean design, headless capabilities, and exceptional raw performance. However, as your traffic grows, a single virtual private server (VPS) can become a single point of failure.
Scaling your infrastructure by deploying Ghost CMS on a distributed VPS cluster paired with Cloudflare provides high availability and global acceleration. Yet, caching dynamic content at Cloudflare's edge introduces a classic synchronization challenge: how do you ensure readers instantly see new articles or edits without waiting for cache expiration? This comprehensive guide walks you through deploying Ghost CMS on a robust VPS cluster and configuring automated Cloudflare cache purging via webhooks.
The Architecture Overview
Before diving into the configuration, it is essential to understand the architectural design of a high-availability Ghost CMS deployment. A standard single-server setup bundles the Node.js application, the MySQL database, and the media storage together. For a clustered environment, we must decouple these components:
- Load Balancer / Reverse Proxy: Nginx or HAProxy distributes incoming traffic across multiple web server nodes.
- Ghost Application Nodes: Multiple identical VPS instances running the Ghost CMS core.
- Shared Database: A centralized, managed MySQL instance (or a primary-replica cluster) accessible by all application nodes.
- Shared Storage: A centralized repository for media assets (images, videos) using AWS S3, DigitalOcean Spaces, or a shared Network File System (NFS), ensuring all nodes serve the same assets.
- Edge Network (Cloudflare): Acts as the primary CDN, handling SSL/TLS termination, DDoS protection, and aggressive page caching.
Step 1: Setting Up the Shared Infrastructure
1. Centralizing the Database
Ghost relies heavily on its database. Deploy a standalone VPS or use a managed database service running MySQL 8.0. Ensure that the database configuration allows incoming connections from the IP addresses of your web application nodes. Secure this connection using strong credentials and firewall rules (UFW or Cloudflare Tunnels).
2. Configuring Shared Media Storage
Because local file uploads on Node A will not automatically exist on Node B, you must use a shared storage adapter. The most robust approach is using an S3-compatible storage provider combined with the ghost-storage-adapter-s3 plugin. Modify your Ghost config.production.json on all nodes to include:
"storage": { "active": "s3", "s3": { "accessKeyId": "YOUR_ACCESS_KEY", "secretAccessKey": "YOUR_SECRET_KEY", "bucket": "your-bucket-name", "region": "your-region", "assetHost": "[https://cdn.yourdomain.com](https://cdn.yourdomain.com)" } }
Step 2: Deploying Ghost CMS Nodes
With the shared resources ready, provision your individual VPS instances. On each node, install Node.js (ensure compatibility with the current Ghost LTS version) and the Ghost-CLI tool. Run the installation command, ensuring you point the configuration to your centralized MySQL instance:
ghost install --db mysql --dbhost centralized-db-ip --dbuser ghost_user --dbname ghost_prod --no-setup-ssl
Note: We omit SSL generation on the individual nodes because SSL termination will be handled globally at the Cloudflare edge or load balancer level.
Step 3: Integrating Cloudflare for Advanced Caching
Cloudflare dramatically reduces server load by caching HTML pages at its edge data centers. To cache everything, including the dynamic frontend of Ghost, navigate to your Cloudflare Dashboard and configure a Page Rule or Cache Rule:
- Match URL:
[example.com/](https://example.com/)* - Cache Level: Cache Everything
- Edge Cache TTL: 7 days (or longer, as we will automate the purging mechanism)
Critical Exception: You must bypass caching for the administration panel. Create a higher-priority rule for [example.com/ghost/](https://example.com/ghost/)* with Cache Level: Bypass and Security Level: High to ensure editors can interact with the backend in real-time.
Step 4: Automating Cloudflare Cache Purging via Webhooks
While aggressive caching guarantees sub-millisecond load times, it means updates will not appear immediately to external visitors. To fix this, we leverage Ghost's native webhook system coupled with Cloudflare's API to purge the cache automatically upon content mutation.
1. Generating Cloudflare API Credentials
Log into Cloudflare, navigate to My Profile > API Tokens, and create a custom token with the following permissions:
- Zone - Zone - Read
- Zone - Cache Purge - Edit
Save this token along with your Zone ID, found on your domain's main overview page.
2. Creating the Purge Intermediary (Serverless Worker or Script)
Ghost webhooks send a POST request containing JSON data about the event. However, Cloudflare requires a specific JSON payload format to trigger a cache purge. To bridge this gap, you can deploy a lightweight Cloudflare Worker or a small Node.js microservice. Below is the logic required for the purge function:
async function purgeCloudflareCache(zoneId, apiToken) { const response = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`, { method: 'POST', headers: { 'Authorization': `Bearer ${apiToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ purge_everything: true }) }); return response.ok; }For enterprise setups, instead of purging everything, you can refine the logic to parse the incoming Ghost webhook payload and only purge specific URLs (e.g., the homepage and the updated article's slug) to maximize your overall cache hit ratio.
3. Configuring Webhooks in Ghost Admin
Navigate to your Ghost Admin dashboard ([example.com/ghost](https://example.com/ghost)) and go to Settings > Integrations > Custom Integrations. Click "Add custom integration", name it "Cloudflare Auto-Purge", and add webhooks for the following events:
- Post published
- Post updated
- Post deleted
- Page published
- Page updated
Set the Target URL to point to your Cloudflare Worker or microservice endpoint. Now, the exact millisecond a writer hits "Publish" or modifies an existing article, Ghost sends an alert, and Cloudflare flushes the old content instantly.
Best Practices and Maintenance
Operating a clustered application environment requires continuous monitoring and adherence to strict operational protocols. Consider the following best practices:
- Log Centralization: Implement a tool like Logstash, Fluentd, or Datadog to aggregate Ghost logs across all cluster nodes, simplifying debugging.
- Health Checks: Configure your load balancer to ping a specific lightweight route (like
/ghost/api/v4/content/settings/) every few seconds. If a node fails, the load balancer gracefully removes it from the rotation without downtime. - Database Optimization: Regularly monitor connection pools. Since multiple application nodes connect to a single database, adjust the
max_connectionsparameter in MySQL to prevent resource exhaustion.
Conclusion
Deploying Ghost CMS on a scaled VPS cluster combined with Cloudflare ensures your media site remains highly performant, secure, and resilient under immense traffic spikes. By introducing automated cache purging through webhooks, you effectively eliminate the trade-off between performance and real-time updates. Your infrastructure remains completely optimized, serving static edge-cached content to global visitors while immediately reflecting editorial changes the moment they happen.
