Page speed is critical for user experience, bounce rates and SEO ranking. Using a Content Delivery Network we get faster page load globally and a reduced load on the server. Here's my solution for a self-hosted Ghost to serve images, javascript and stylesheets over BunnyCDN.

I've been working with streaming media professionally for 20+ years and CDN's used to be no end of pain. BunnyCDN has managed to take something extremely complex and make it both user-friendly and lightning fast - this page loaded in [wait...] seconds.

Bunny offers DNS services, video streaming with DRM, file delivery with access tokens, and logs. Highly recommended and a steal at $0.01/GB transfer.  

Bunny also host free privacy-friendly web fonts as an alternative to Google fonts at fonts.bunny.net. The Bunny Optimizer with WebP image compression, minify js/CSS and watermarks is $10 / month.  

When we're done there will be three URL’s to keep track of..

  • www.mysite.com → regular URL for CDN-accelerated Ghost blog
  • nocdn.mysite.com → the Origin for the CDN without acceleration and cache.
  • cdn.mysite.com → URL for CDN-cached media, a CNAME (alias) of the CDN delivery URL mysite.b-cdn.net
ℹ️
Basic Linux sysadmin skills recommended, I'm using Ubuntu 22.04 with nginx version 1.18.0. 

Step 1: Create a CDN origin URL

The origin URL is your Ghost blog without acceleration. The CDN will pull assets to be served from this URL - images, JavaScript and stylesheets.  

BunnyCDN can not fetch images from your live blog URL  - the image links are pointing back to the CDN and Bunny will be unable to pull and cache assets. Angry Bunny in a loop, not good. So, we need an origin.

  • Create a subdomain nocache.mysite.com or something less obvious such as g534k9.mysite.com in your DNS administration panel.
  • Enable this site in nginx - copy the existing configuration and change the server  name to nocache.mysite.com
  • Reload nginx and have Certbot generate a SSL certificate for nocache.mysite.com
  • Verify that you can access your Ghost blog at https://nocache.mysite.com

Step 2: Create a pull zone

  • Register a new account with BunnyCDN at bunny.net  
  • Under CDN choose Add Pull Zone
  • Assign a meaningful Zone Name: mysite.b-cdn.net
  • Enter the Ghost blog Origin URL created in step 1, ex. nocache.mysite.com
  • Choose Standard Tier and disable Zones you have no visitors from.
  • Done. Bunny now pulls all html, images, scripts and css from your Ghost blog.
  • Create a CNAME sub-domain in your DNS admin panel, such as cdn.mysite.com and point this to Bunny's Zone name myblog.b-cdn.net
  • Wait for DNS-propagation of this CNAME and have Bunny create a SSL cert for this subdomain.
  • Verify that images from your Ghost blog are available on Bunny CDN
    https://www.mysite.com/content/images/carrot.jpg - Local server delivery
    https://cdn.mysite.com/content/images/carrot.jpg  - CDN global delivery

Step 3: Configure nginx

  • Modify the ‘live’ configuration file for https://www.mysite.com not the origin nocache.mysite.com
  • Use nginx  sub_filter  to re-direct image requests to our CDN. It’s basically search-and-replace sub_filter 'replace this' 'with this';
  • gzip compression is disabled between nginx and Ghost when using  sub_filter - gzip is still enabled for nginx external requests.

Add this in the nginx proxy_pass block - change www.mysite.com to your domain and cdn.mysite.com to the CNAME subdomain.

Restart nginx and you're done. nginx will rewrite URL's to fetch globally cached assets on BunnyCDN.

https://www.mysite.se/content/images/size/w1000/2022/11/carrot.png

becomes...

https://cdn.mysite.se/content/images/size/w1000/2022/11/carrot.png

Step 4: Verify everything is OK

Open up Developer Console / Network. View mwww.mysite.com with a shift-reload in browser to reload all elements on the page. Inspect any image file, you should see Server: BunnyCDN

A note on caching in Ghost CMS

In the support section Bunny writes

BunnyCDN does not monitor the files on your origin server for changes, this means that if a file is already cached on our servers, it will remain cached until the Cache-Control expires or it gets deleted to make space for more popular content.

All assets from Ghost using the {{asset}} tag are served with a ?v=####### query string which changes when Ghost is restarted, aka cache-busting. To have Bunny pull latest versions of javascript libraries and stylesheets after making changes, restart Ghost.

.js and .css files will get a new ?v=####### string and Bunny will pull the updated file from your origin. You can also purge individual files in the admin interface, and there's always the nuclear option to hit Purge Cache and pull fresh copies all your assets.

And, insert a pre-connect to your CDN in <HEAD>, this reduces the number of network round-trips needed when the browser loads a resource from the CDN.

<link rel="preconnect" href="https://cdn.mysite.com">

Stuck and need some help? I'm on Telegram at @kmhelander

I only recommend services I use myself and all opinions epressed are my own. This post contains affiliate links that credit my BunnyCDN account if you decide to sign up. Thank you!