How-to: Offloading Ghost image delivery to Bunny CDN

I found a simple solution for offloading images from my self-hosted Ghost and serve them over a CDN for… no money, really. It’s a little bit of a hack, rewriting the image URL’s with nginx, but it works like a dream. We will have three URL’s to keep track of when done…

If anyone has suggestions on how to make the set-up even better pls share. Using Ubuntu 22.04, nginx 1.18.0 & Bunny CDN. I am not affiliated with Bunny

Update 25/5: Ghost does not send referrer header when previewing a blog post, if you have Hotlinking protection enabled on Bunny CDN images will not display in Preview.

I also added how to restrict access to our Origin URL to Bunny in the end of this post.

Also published this as a (CDN-accelerated) blog post with additional info re caching

1. Create a CDN origin URL

That’s your Ghost blog without CDN acceleration. The CDN will not be able to fetch images from your regular blog URL when enabled - the image links are pointing back to the CDN and Bunny will be unable to pull and cache the images. Angry Bunny in a loop, not good.

2. Set up an account with Bunny CDN at and create a pull zone.

3. Configure nginx

  • Edit the ‘live’ configuration file for - not the origin -

  • I use nginx sub_filter to rewrite image-requests to our CDN. It’s basically search-and-replace
    sub_filter 'replace this' 'with this';

  • Add this in the nginx proxy_pass block - change to your domain and to the CNAME subdomain

    # Enable multiple replacements in one request
    sub_filter_once off;
    # sub_filter does not work with gzip compression, so disable. 
    proxy_set_header Accept-Encoding "";
    # Replace all image links to /content/images with CDN-url
    sub_filter '' '';
    # The same for poster images loaded as background
    sub_filter 'background-image:url(/content' 'background-image:url(';
    # Optional: javascript libraries
    sub_filter '/assets/js/' '';

Done. Restart nginx. Verify in Developer Console / Network with a shift-reload in browser.

Restricting access to origin

We only want Bunny to be able to access pull assets from, so we send a header X-Pull in our requests, and have nginx check for this header.

1. Create new Edge rule

Create a new Edge rule in BunnyCDN control panel

  • 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 origin URL/host

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.

     if ($http_x_pull != "random12345") {
         return 403;

This solution was suggested by KeyCDN and they have a HTTP Header Checker to verify that requests with X-Pull are accepted.

PageSpeed with Bunny CDN enabled is 97, pretty good…



Thanks for sharing! I tried something before but I get stucked on Nginx step.

Could be good to exclude /ghost?

I was confident this hack would wreck the editor and admin interface behind /ghost - surprisingly it was not affected at all. Turns out all requests are made to the content-API and never triggers /content/images etc sub_filters.

Updating Ghost on origin URL is an option, to make sure we’re looking at the non-CDN un-cached origin. Edit: This is not a good idea - better to lock down this URL so that only the CDN can access and pull assets, a subdomain can be sniffed and gets indexed. Original post updated.