Ghost speed with Bunny CDN

Ghost speed with Bunny CDN


5 min read

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.

However, BunnyCDN is not a 'simple' or 'light' CDN - it has all advanced features such as edge rules, cache control, network limits, smart caching, CORS headers, and custom error pages.

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 with a minimum $1 charge each month. They also provide free privacy-friendly web fonts - an alternative to Google Fonts.

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

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, remove certbot's SSL config, and change the server name to

  • Reload nginx and have Certbot generate an SSL certificate for Letsencrypt/Certbot can generate a wildcard certificate for all subdomains using TXT-record verification, you need a plug-in for your DNS provider.

  • 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

  • Choose any meaningful Zone Name:

  • For Origin 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 an SSL cert for in the admin panel.

  • 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

  • We 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 must be 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 the 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 the 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 of 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="">

Restrict access to your Origin URL

The origin URL should only be available to BunnyCDN. Subdomains can be sniffed easily and we don't want anyone to index or access our site on the non-accelerated origin URL.

To solve this we ask BunnyCDN to send a request header X-Pull with a random key, and have nginx check for this key in the X-Pull header.

1. BunnyCDN control panel

Create a new Edge rule with

  • Action: Set Request Header

  • Header: Name: X-Pull

  • Header Value: random12345 - use any passphrase here

  • Conditions: Request URL matching Any for*

2. nginx config for

Add this to the proxy_pass block in nginx. If the header X-Pull in the request does not contain the key we assigned in the Edge rule we return 403 Forbidden status code.

This solution was suggested by KeyCDN and they have a HTTP Header Checker to verify.