Fix B2 and Cloudflare CDN Caching
Most services like Vercel (the platform this website is hosted on) have a Content Delivery Network (CDN) built-in. One of my goals was to keep the project's deploys as light as possible and offload resources to an external bucket on Backblaze's B2 cloud storage service.
Unfortunately, buckets are limited to a singular geographical location and don't have any caching layer to reduce bandwidth consumption. Backblaze already has this covered though, with a complete tutorial on how to connect their storage service with Cloudflare's CDN.
It missed one thing, though: Setting proper caching headers on the B2 bucket.
Without those headers, every time I loaded resources through the Cloudflare-proxied URLs, they wouldn't be included in Cloudflare's cache and still load directly from the B2 bucket. Useful for development mode, but missing my ultimate goal.
I'll go over the basic steps to set up B2 and Cloudflare properly, with that ✨cache header config spice✨ at the end.
Step 1: Add Domain to Cloudflare
I recommend following Cloudflare's official guide for this, they cover the most up-to-date steps to make sure your domain is properly set up with their service.
Ensure that the "SSL/TLS encryption mode" for the domain is at least set to
Full. I've had success with
Full (strict) as well.
Step 2: Determine B2 Bucket URL
- Make sure you've got a bucket created in B2 that you're ready to use publicly. Mine is called
- Upload a file to the bucket. We'll need it to determine the bucket's public URL.
- Once the file is uploaded, click its name to open the file detail modal.
- Find the domain for the "Friendly URL" value. It may be similar to
https://f000.backblazeb2.com. Hold on to this for the next part.
Step 3: Add CNAME record to Cloudflare
- In the Cloudflare "DNS" settings tab, click "Add Record".
- Change the record type to CNAME and use the domain you just gathered from the previous step as the "target". The protocol isn't needed, in this case it could be
- Use any subdomain value for the "name". I'm using
cdn, which will result in
- Make sure the "Proxy status" is enabled, with the orange "Proxied" cloud icon.
Step 4: Lock CDN to only your bucket
One gotcha with this setup is that any B2 bucket is now accessible through this new CDN URL. Let's lock that down.
- Navigate to the "Page Rules" settings page in Cloudflare.
- Add a new page rule, with the URL set to
https://DOMAIN/file/BUCKET_NAME/*, where DOMAIN is your custom domain (mine is
cdn.ansonlichtfuss.com), and BUCKET_NAME is the name of your B2 bucket (mine is
public-cdn). The setting should be "Cache Level" and set to "Standard".
- Finally, add a wildcard rule to redirect users trying to access any other bucket:
- Set URL to
https://DOMAIN/file/*/*, where DOMAIN is your domain name.
- Select "Forwarding URL" with a
302 - Temporary Redirectoption selected, and forwards users to
- Set URL to
Important: Once both rules are created, make sure the first rule with the "Cache Level" setting is above the wildcard rule, to ensure it is executed first.
Step 5: Set bucket cache headers
This is the one missing piece that ties it all together. Without setting a proper caching header, the Cloudflare CDN will never cache any of the files it serves and instead will pull from the B2 bucket every time. Once the cache header is set, Cloudflare reads the header and will immediately start caching the files to be served more efficiently from its geographically optimized CDN.
- Find your CDN bucket in B2, created in Step 2.
- Click "Bucket Settings".
- In the "Bucket Info" field, enter:
- This requests that all bucket contents be cached for two hours. Adjust the time to fit your use case.
- Click "Update Bucket" to save these changes and close the modal.
Once these settings are saved and propagated, the new header will appear on responses from the bucket and will trigger Cloudflare to begin caching your files.
Using that file you previously uploaded in the bucket in Step 2, navigate to it at your new custom CDN domain URL. After a couple of refreshes, you should see the response header "cf-cache-status" value set to
HIT, signaling the global cache is now working properly.
Written by Anson Lichtfuss, a frontend engineer building useful, beautiful interfaces in Seattle.