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 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..

  • → regular URL for CDN-accelerated Ghost blog
  • → the Origin for the CDN without acceleration and cache.
  • → URL for CDN-cached media, a CNAME (alias) of the CDN delivery URL
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 or something less obvious such as in your DNS administration panel.
  • Enable this site in nginx - copy the existing configuration and change the server  name to
  • Reload nginx and have Certbot generate a SSL certificate for
  • Verify that you can access your Ghost blog at

Step 2: Create a pull zone

  • Register a new account with BunnyCDN at  
  • Under CDN choose Add Pull Zone
  • Assign a meaningful Zone Name:
  • Enter the Ghost blog Origin URL created in step 1, ex.
  • 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 and point this to Bunny's Zone name
  • 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 - Local server delivery  - CDN global delivery

Step 3: Configure nginx

  • Modify the ‘live’ configuration file for not the origin
  • 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 to your domain and to the CNAME subdomain.

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


Step 4: Verify everything is OK

Open up Developer Console / Network. View 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="">

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!