| | |

Boost Speed of a Ghost Blog massively with proper Cloudflare CDN Cache for Free

In this post, we are going to explore how to improve the performance of a Ghost blog with Cloudflare cache in Cloudflare CDN and make the blog highly available and scalable with superior performance boost free of cost (up to a limit), and ghost member features still works. Along with that I have included some test results to back up my claim. On the note of free, yes you get 100,000 requests per day for free, and depending on your site structure, that translates to about 3,000 to 10,000 page views per day (your mileage may vary).

Table Of Content

We will use cloudflare workers to implement the cache rules and differentiate between logged in and logged out users. For logged out users all content will be static and only for logged in user we will serve dynamic content.

Introduction

In this tutorial, I will guide you through the process of implementing a full-page cache, while ensuring cache bypass for logged-in users. For logged-out viewers, your ghost blog will behave like a static site and will load everything from a Cloudflare CDN’s edge server (more than 300 edge locations across the globe) completely bypassing your origin, but for logged-in users it will bypass edge cache for dynamic contents. Additionally, we’ll share some compelling test results that demonstrate the impressive scalability of a self-hosted Ghost blog. (Spoiler alert: it can efficiently handle as many as 600k requests over 3 minutes without any noticeable impact on user experience.)

💡 We are implementing a Cloudflare worker to do full-page cache with cache everything in Cloudflare CDN, while ensuring member sign-up and other dynamic contents for logged-in users remain unaffected by bypassing cache for them, also we will setup a cache rule to bypass caching for some URLs.

🤒 Only logged-in users will see the updated comments section, for logged-out users comment section will not update with new comments, until you purge the Cloudflare Cache.

I am starting with some basic cache rules to make you comfortable, and at then give you the best way to do caching for a ghost blog, and at the end will show you the real-world benefit of this approach.

Cloudflare Cache: Basic Cache rules

Cloudflare provides 10 free cache rules in their free tier option. We will be caching as much as possible, and also we will be bypassing some URLs of the site from being cached. To implement cache rules go to your website dashboard in Cloudflare > Caching > Cache rules > Create rule. Caching rules are prioritized in descending order by Cloudflare, so the higher-importance cache rule should be used first.

1. URLs to Bypass: Before we start caching anything, we should know what not to cache. We want to avoid caching the admin area and preview pages. For that, our first cache rule will be: to bypass cache for the following URLs. URI Full Starts With https://yourblog.com/ghost/ OR URI Full Starts With https://yourblog.com/p/ OR URI ends with sitemap.xml, do the same for sitemap-post.xml and sitemap-pages.xml. You can use the expression builder below to quickly add all the rules. Just remember to set cache eligibility to bypass cache.

(starts_with(http.request.full_uri, "https://example.com/ghost/")) or (ends_with(http.request.uri, "sitemap.xml")) or (starts_with(http.request.full_uri, "https://example.com/p/")) or (ends_with(http.request.uri, "sitemap-posts.xml")) or (ends_with(http.request.uri, "sitemap-pages.xml"))

Paste this expression in the expression builder in the cache rule to quickly add all the rules

2. URL to Cache: Now we can cache all the static files for both logged-in and logged-out users for which we will be caching like this- URI Start with  (also use OR) https://yourblog.com/content/,  https://yourblog.com/assets/ , https://yourblog.com/public/. For these rules, use – Eligible for cache > Edge ttl 1 month and browser ttl 1 day (your choice).

💡 One Pro Tip: While you are on the cache configuration page. Go to tiered cache and turn smart tiered cache on.

The above two can be achieved from the Cloudflare dashboard itself. What we can’t do in the free and pro plans is cache HTML contents for logged-out users and bypass html cache for logged-in users.

💡 If you do not have Members functionality enabled you can straightway set the 2nd Cache rule like URI-Full – Starts with – https://yordomain.com/, and eligible for cache edge cache TTL 1 year. Or set two-page rules to bypass yourdomain/ghost/* and cache everything with yourdomain/*

💡 If you use subscriptions or Google Adsense this should method should work perfectly. But I recently switched to Ezoic for ads, and you can’t cache between Ezoic and your user, so I don’t use it anymore. But this method overall perfectly works.

Superior Cloudflare Caching with Cloudflare Worker for Ghost blogs:

Now that we know what can be achieved with Cloudflare dashboard only, let’s implement the functionality I have been hyping about – Cloudflare Workers. Cloudflare gives around 100k free requests daily. If that is not enough their paid plan starts from 5 USD/month for 10 million requests. Let’s deploy the worker, but first, make sure you have applied the ‘URI to bypass’ cache rule in your dashboard.

Objectives:

  1. Caching everything for logged-out users
  2. Caching only static assets for logged-in users

Steps Involved:

  1. Create a Cloudflare worker: To create a Cloudflare Worker, Visit Cloudflare dashboard > Workers and Pages > Create application > Create worker > Give it a name and deploy > Edit > Clear the existing index.js and paste the below script there > then Save and deploy. If you see any errors don’t worry, we need to add a route to these workers. we will check after that.
creating a worker
creating a worker
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request, event))
})

async function handleRequest(request, event) {
  // Parse the URL from the request
  let url = new URL(request.url)

  // Check for logged in users by looking for the 'ghost-members-ssr' cookie
  let cookies = request.headers.get('Cookie') || ''
  if (cookies.includes('ghost-members-ssr')) {
    // For logged-in users, only cache static files (CSS, JS, images, etc.)
    if (
      url.pathname.endsWith('.css') ||
      url.pathname.endsWith('.js') ||
      url.pathname.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i)
    ) {
      let response = await caches.default.match(request)
      if (!response) {
        response = await fetch(request, { cf: { cacheTtl: 1209600, cacheEverything: false } })
        event.waitUntil(caches.default.put(request, response.clone()))
      }
      return response
    } else {
      // For other requests, bypass cache
      return fetch(request)
    }
  }

  // Bypass cache for URLs that should always bypass cache
  if (
    url.pathname.startsWith('https://smartgaot.me/ghost/') ||
    url.pathname.startsWith('https://smartgoat/p/') ||
    url.pathname === '/sitemap.xml'
  ) {
    return fetch(request)
  }

  // Bypass cache for POST requests
  if (request.method === 'POST') {
    return fetch(request)
  }

  // For logged-out users, cache everything
  let response = await caches.default.match(request)
  if (!response) {
    response = await fetch(request, { cf: { cacheTtl: 1209600, cacheEverything: true } })
    event.waitUntil(caches.default.put(request, response.clone()))
  }

  return response
}

With this worker, we are checking for a cookie used for user login in ghost and manage traffic accordingly.

💡 don’t forget to change the URLs in the codes.

  1. Add a Route to this Worker: Now you need to create a route to your worker so that all requests coming to your site go through this worker. After Saving and Deploy you will see an overview page for your worker where you will find the routes ‘view’ option. Go there and follow the below images to add a route. This is what I use for this blog. And that will be all.
  2. For any queries visit this discussion forum.

💡 You might need to disable the rocket loader and auto-minify, if you face any issues with some parts of the site. ( Disabling Minify and rocket-loader won’t hurt your performance )

Add Routes to your Workers
Add Routes to your Workers
format the routes like this
format the routes like this

☝️ While on the route page, go to ‘request-limit-failure’ and set it to Fail open. So that if workers limit is exhausted it does not block traffic to your site.

Benefits of Cloudflare CDN and caching: Better TTFB better load handling

(Test data provided as evidence)

Okay, so we have done all of that but for why, and exactly how beneficial it is? Let’s discuss with some real-world test data.

The tests conducted are:

  1. Global TTFB checking using KeyCDN (10 locations) and SpeedVitals (more than 30 locations) tools.
  2. Load testing by simulating real-world visitors with 250 chromium Bots from 10 data centers to continuously visit different pages of my website. By Loadster tool.

Setups tested:

  1. Full-Cache: Blog: with full cache (what we deployed here)
  2. No-Cache: Blog: bypassing all off Cloudflare ( No CDN at all )
  3. Dynamic Cache: Blog with dynamic cache (what can be done from the dashboard, i.e. caching all static content no dynamic content)

My server Configuration:

  1. Free Tier Oracle cloud Ampere A1 Instance 2 CPU core 16 GB RAM, 50GB NVMe SSD, and 2 Gbps bandwidth.
  2. CloudPanel Dashboard for Server management
  3. Two Ghost Blogs running on the same server ( smartgoat.me and www.medblogs.org )
  4. On the home page, all images are served from the server itself as Ghost does not allow images from URLs in featured images.
  5. Images inside the post are serving from Gumlet Image CDN. ( It does not affect the Load test results and TTFB tests)

Results:

TTFB Test: (best results I got for each setup from multiple tests):

Setup (SpeedVital Data) TTFB (Avg from 30 Locations) Performance Summary
Full Cache 104 Ms 96%
Dynamic Cache 668 Ms 43%
No Cache 752 Ms 40%

Images of test results are added Below

Load Test Results: (Loadster App)

I used 250 Chromium bots To literally ddos my site with thousands of requests per second simulating a real-world load of many users visiting my blog simultaneously (still a dream for me). Bots will gradually increase from 1 to 250 over 1 minute, then stay at 25O count for 1 minute and gradually decrease to 0 in the last 1 minute. In the process every bot will continuously load some pages of my site, I mostly used the home page https://smartgoat.me because I did not want to hit Gumlet CDN much to exhaust my free tier data. Anyway here is the result summary, and below link to every test given:

  • Each test 3 minutes long ( with gradual Increase and gradual tapering )
Setup Request Served Avg Response Time Max Bandwidth Data Downloaded
Full Cache 613,530 0.747 S 717 Mbps 11.3 GB
Dynamic Cache 79,308 11.33 S 171.74 Mbps 2.7 GB
No Cache 63,163 17.26 S 156.83 Mbps 2.1 GB

Test Links: For you to deep dive

  1. Full-Cache: Test Result here
  2. Dynamic Cache: Test Result Here
  3. No Cache: Test Result here

My Observations:

  1. Full cache massively decreased TTFB, which means faster loading time, better web vitals and better SEO.
  2. Even with huge requests coming through (600k over 3 minutes) it barely increased the site load time for any user. I visited the site during testing, only this time I didn’t even notice anything, all the other times there was huge loading time.
  3. It didn’t even touch my origin server. My CPU was chilling at 2-3% usage the whole time and RAM was at 11% which is baseline with no load. In the other 2 scenarios CPU reached 45% and above and RAM never crossed 20% but still site was throttled
  4. It served overall more data, 12 GB at max rate of 700 Mbps, other 2 scenarios barely crossed 2 GB and 100 Mbps (my server bandwidth is 2Gbps). Basically there was not enough bots and free tier stuff for me to test.
  5. Bonus: Even when my origin server is down my site stayed up, because all the requests were getting served from Cloudflare CDN edge locations without even hitting my server. It works for logged out users only though.

Conclusion:

To conclude Cloudflare CDN with proper cache setup definitely improves the performance of a self-hosted ghost blog, and yes we can definitely self-host a highly scalable Ghost Blog. Hope you liked it, discuss any queries on our subreddit. Share if you find this post helpful. Here is how you can purge Cloudflare cache automatically when you publish a new post.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.