{"id":104734,"date":"2022-02-08T08:54:32","date_gmt":"2022-02-08T16:54:32","guid":{"rendered":"https:\/\/www.backblaze.com\/blog\/?p=104734"},"modified":"2026-05-14T13:48:12","modified_gmt":"2026-05-14T20:48:12","slug":"free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2","status":"publish","type":"post","link":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/","title":{"rendered":"Free Image Hosting With Cloudflare Transform Rules and Backblaze B2"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104739 size-full\" title=\"Free Image Hosting With Cloudflare Transform Rules and Backblaze B2\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D.png\" alt=\"diagram of Browser secure to Cloudflare secure to Backblaze B2 Cloud Storage\" width=\"1440\" height=\"820\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D.png 1440w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D-300x171.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D-1024x583.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D-768x437.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D-560x319.png 560w\" sizes=\"auto, (max-width: 1440px) 100vw, 1440px\" \/><\/p>\n<p id=\"bzdropcap\">Before I dive into using Cloudflare Transform Rules to implement image hosting on Backblaze B2 Cloud Storage, I\u2019d like to take a moment to introduce myself. I\u2019m Pat Patterson, recently hired by Backblaze as chief developer evangelist. I\u2019ve been working with technology and technical communities for close to two decades, at companies such as Sun Microsystems and Salesforce. I\u2019ll be creating and delivering technical content for you, our Backblaze B2 community, and advocating on your behalf within Backblaze. Feel free to follow my journey and reach out to me via <a href=\"https:\/\/twitter.com\/metadaddy\" target=\"_blank\" rel=\"noopener\">Twitter<\/a> or <a href=\"https:\/\/www.linkedin.com\/in\/metadaddy\/\" target=\"_blank\" rel=\"noopener\">LinkedIn<\/a>.<\/p>\n<h2><strong>Cloudflare Transform Rules<\/strong><\/h2>\n<p>Now, on with the show! <a href=\"https:\/\/developers.cloudflare.com\/rules\/transform\" target=\"_blank\" rel=\"noopener\">Cloudflare Transform Rules<\/a> give you access to HTTP traffic at the CDN edge server, allowing you to manipulate the URI path, query string, and HTTP headers of incoming requests and outgoing responses. Where Cloudflare Workers allow you to write JavaScript code that executes in the same environment, Transform Rules give you much of the same power without the semi-colons and curly braces.<\/p>\n<p>Let\u2019s look at a specific use case: implementing image hosting on top of a cloud object store. Backblaze power user <a href=\"https:\/\/twitter.com\/CherryJimbo\" target=\"_blank\" rel=\"noopener\">James Ross<\/a> wrote an excellent blog post back in August 2019, long before the introduction of Transform Rules, explaining <a href=\"https:\/\/jross.me\/free-personal-image-hosting-with-backblaze-b2-and-cloudflare-workers\/\" target=\"_blank\" rel=\"noopener\">how to do this with Cloudflare Workers and Backblaze B2<\/a>. We\u2019ll see how much of James\u2019 solution we can recreate with Transform Rules, without writing <em>any<\/em> code. We\u2019ll also discover how the combination of Cloudflare and Backblaze allows you to create your own, personal 10GB image hosting site for free.<\/p>\n<h2><strong>Implementing Image Hosting on a Cloud Object Store<\/strong><\/h2>\n<p>James\u2019 requirements were simple:<\/p>\n<ul>\n<li>Serve image files from a custom domain, such as <code>files.example.com<\/code>, rather than the cloud storage provider\u2019s domain.<\/li>\n<li>Remove the bucket name, and any other extraneous information, from the URL.<\/li>\n<li>Remove extraneous headers, such as the object ID, from the HTTP response.<\/li>\n<li>Improve caching (both browser and edge cache) for images.<\/li>\n<li>Add basic CORS headers to <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTML\/CORS_enabled_image\" target=\"_blank\" rel=\"noopener\">allow embedding of images on external sites<\/a>.<\/li>\n<\/ul>\n<p>I\u2019ll work through each of these requirements in this blog post, and wrap up by explaining why Backblaze B2 might be a better long term provider for this and many other cloud object storage use cases than other cloud object stores.<\/p>\n<p>It\u2019s worth noting that nothing here is Backblaze B2-specific\u2014the user\u2019s browser is requesting objects from a B2 Cloud Storage public bucket via their URLs, just as it would with any other cloud object store. The techniques are exactly the same on Amazon S3, for example.<\/p>\n<h3><strong>Prerequisites<\/strong><\/h3>\n<p>You\u2019ll need accounts with both Cloudflare and Backblaze. You can get started for free with both:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.cloudflare.com\/plans\/free\/\" target=\"_blank\" rel=\"noopener\">Cloudflare<\/a><\/li>\n<li><a href=\"https:\/\/www.backblaze.com\/b2\/sign-up.html?utm_source=bzwebsite&amp;utm_medium=blog&amp;utm_campaign=cloudflare-b2\" target=\"_blank\" rel=\"noopener\">Backblaze B2 Cloud Storage<\/a><\/li>\n<\/ul>\n<p>You\u2019ll also need your own DNS domain, which I\u2019ll call <code>example.com<\/code> in this article, on which you can create subdomains such as <code>files.example.com<\/code>. If you\u2019ve read this far, you likely already have at least one. Otherwise, you can register a new domain at Cloudflare for a few dollars a year, or your local equivalent.<\/p>\n<h3><strong>Create a Bucket for Your Images<\/strong><\/h3>\n<p>If you already have a B2 Cloud Storage bucket you want to use for your image store, you can skip this section. Note: It doesn\u2019t matter whether you created the bucket and its objects via the <a href=\"https:\/\/www.backblaze.com\/b2\/docs\/quick_account.html\" target=\"_blank\" rel=\"noopener\">B2 Native API<\/a>, the <a href=\"https:\/\/www.backblaze.comhttps:\/\/www.backblaze.com\/docs\/en\/cloud-storage-s3-compatible-api\" target=\"_blank\" rel=\"noopener\">Backblaze S3 Compatible API<\/a>, or any other mechanism\u2014your objects are accessible to Cloudflare via their friendly URLs.<\/p>\n<p>Log in to Backblaze, and click <strong>Buckets<\/strong> on the left under <strong>B2 Cloud Storage<\/strong>, then <strong>Create a Bucket<\/strong>. You will need to give your bucket a unique name, and make it public. Leave the other settings with their default values.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104745 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/1-717x1024.png\" alt=\"B2 Cloud Storage Create a Bucket screenshot\" width=\"717\" height=\"1024\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/1-717x1024.png 717w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/1-210x300.png 210w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/1-768x1097.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/1-1076x1536.png 1076w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/1-560x800.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/1.png 1234w\" sizes=\"auto, (max-width: 717px) 100vw, 717px\" \/><\/p>\n<p>Note that the bucket name must be <em>globally unique<\/em> within Backblaze B2, so you can\u2019t just call it something like \u201cmyfiles.\u201d You\u2019ll hide the bucket name from public view, so you can call it literally anything, as long as there isn\u2019t already a Backblaze B2 bucket with that name.<\/p>\n<p>Finally, click <strong>Upload\/Download<\/strong> and upload a test file to your new bucket.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104746 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/2-1024x464.png\" alt=\"B2 Cloud Storage Browse Files screenshot\" width=\"1024\" height=\"464\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/2-1024x464.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/2-300x136.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/2-768x348.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/2-560x254.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/2.png 1394w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Click the file to see its details, including its various URLs.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104747 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/3-1024x865.png\" alt=\"B2 Cloud Storage Details screenshot\" width=\"1024\" height=\"865\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/3-1024x865.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/3-300x253.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/3-768x648.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/3-1536x1297.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/3-560x473.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/3.png 1824w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>In the next step, you\u2019ll rewrite requests that use your custom subdomain, for example, <code>https:\/\/files.example.com\/smiley.png<\/code>, to the friendly URL of the form, <code>https:\/\/f004.backblazeb2.com\/file\/metadaddy-public\/smiley.png<\/code>.<\/p>\n<p>Make a note of the <strong>hostname<\/strong> in the friendly URL. As you can see in the previous paragraph, mine is <code>f004.backblazeb2.com<\/code>.<\/p>\n<h3><strong>Create a DNS Subdomain for Your Image Host<\/strong><\/h3>\n<p>You will need to activate your domain (<code>example.com<\/code>, rather than <code>files.example.com<\/code>) in your Cloudflare account, if you have not already done so.<\/p>\n<p>Now, in the Cloudflare dashboard, create your subdomain by adding a DNS CNAME record pointing to the bucket hostname you made a note of earlier.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104748 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/4-1024x340.png\" alt=\"Cloudflare DNS screenshot\" width=\"1024\" height=\"340\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/4-1024x340.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/4-300x100.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/4-768x255.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/4-1536x510.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/4-1600x533.png 1600w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/4-560x186.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/4.png 1999w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>I created <code>files.superpat.com<\/code>, which points to my bucket\u2019s hostname, <code>f004.backblazeb2.com<\/code>.<\/p>\n<p>If you test this right now by going to your test file\u2019s URL in your custom subdomain, for example, <code>https:\/\/files.example.com\/file\/my-unique-bucket-name\/smiley.png<\/code>, after a few seconds you will see a 522 \u201cconnection timed out\u201d error from Cloudflare:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104749 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/5-1024x765.png\" alt=\"Error 522 message\" width=\"1024\" height=\"765\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/5-1024x765.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/5-300x224.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/5-768x574.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/5-1536x1148.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/5-260x195.png 260w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/5-560x419.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/5.png 1999w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>This is because, by default, Cloudflare accesses the upstream server via plain HTTP, rather than HTTPS. Backblaze only supports secure HTTPS connections, so the HTTP request fails. To remedy this, in the SSL\/TLS section of the Cloudflare dashboard, change the encryption mode from \u201cFlexible\u201d to \u201cFull (strict),\u201d so that Cloudflare connects to Backblaze via HTTPS, and requires a CA-issued certificate. Plus, although Full (strict) works, the TSL setting can be `Full` too.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104750 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/6-1024x681.png\" alt=\"Cloudflare SSL\/TLS screenshot\" width=\"1024\" height=\"681\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/6-1024x681.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/6-300x199.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/6-768x511.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/6-1536x1021.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/6-560x372.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/6.png 1999w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Now you should be able to access your test file in your custom subdomain via a URL of the form <code>https:\/\/files.example.com\/file\/my-unique-bucket-name\/smiley.png<\/code>. The next task is to create the first Transform Rule to remove <code>\/file\/my-unique-bucket-name<\/code> from the URL.<\/p>\n<p>You must also ensure that Automatic Signed Exchanges (SXGs) are not enabled in your Cloudflare domain configuration, as this feature is not compatible with Backblaze B2. See <a href=\"https:\/\/community.cloudflare.com\/t\/backblaze-b2-with-transform-rule-fails-on-some-browsers-not-others\/362599\/4?u=metadaddy\" target=\"_blank\" rel=\"noopener\">this Cloudflare forum post<\/a> for a deeper explanation.<\/p>\n<h3><strong>Rewrite the URL Path on Incoming Requests<\/strong><\/h3>\n<p>There are three varieties of Cloudflare Transform Rules:<\/p>\n<ul>\n<li>URL Rewrite Rules: Rewrite the URL path and query string of an HTTP request.<\/li>\n<li>HTTP Request Header Modification Rules: Set the value of an HTTP request header or remove a request header.<\/li>\n<li>HTTP Response Header Modification Rules: Set the value of an HTTP response header or remove a response header.<\/li>\n<\/ul>\n<p>Click <strong>Rules<\/strong> on the left of the Cloudflare dashboard, then <strong>Transform Rules<\/strong>. You\u2019ll see that the Cloudflare free plan includes 10 Transform Rules\u2014plenty for our purposes. Click <strong>Create Transform Rule<\/strong>, then <strong>Rewrite URL<\/strong>.<\/p>\n<p>It\u2019s useful to pause for a moment and think about what we need to ask Cloudflare to do. Users will be requesting URLs of the form <code>https:\/\/files.example.com\/smiley.png<\/code>, and we want the request to Backblaze B2 to be like <code>https:\/\/f004.backblazeb2.com\/file\/metadaddy-public\/smiley.png<\/code>. We\u2019ve already taken care of the domain part of the URL, so it becomes clear that all we need to do is prefix the outgoing URL with <code>\/file\/&lt;bucket name&gt;<\/code>.<\/p>\n<p>Give your rule a descriptive name such as \u201cAdd file and bucket name.\u201d<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104752 size-full\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/7.png\" alt=\"Edit Transform Rule\" width=\"812\" height=\"312\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/7.png 812w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/7-300x115.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/7-768x295.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/7-560x215.png 560w\" sizes=\"auto, (max-width: 812px) 100vw, 812px\" \/><\/p>\n<p>There is an opportunity to set a condition that incoming requests must match to fire the trigger. In James\u2019 article, he tested that the path did not already begin with the <code>\/file\/&lt;bucket name&gt;<\/code> prefix, so that you can refer to a file with either the short or long URL.<\/p>\n<p>At first glance, the Cloudflare dashboard doesn\u2019t offer \u201cdoes not start with\u201d as an operator.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104753 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/8-1024x396.png\" alt=\"When incoming requests match... screenshot\" width=\"1024\" height=\"396\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/8-1024x396.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/8-300x116.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/8-768x297.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/8-1536x594.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/8-560x216.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/8.png 1708w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>However, clicking <strong>Edit expression<\/strong> reveals a more powerful way of specifying the condition:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104754 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/9-1024x229.png\" alt=\"(http.request.uri.path eq &quot; &quot;)\" width=\"1024\" height=\"229\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/9-1024x229.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/9-300x67.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/9-768x172.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/9-1536x344.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/9-560x125.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/9.png 1708w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>The Cloudflare Rules language allows us to express our condition precisely:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104755 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/10-1024x225.png\" alt=\"not starts_with(http.request.uri.path, &quot;\/file\/metadaddy-public&quot;)\" width=\"1024\" height=\"225\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/10-1024x225.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/10-300x66.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/10-768x169.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/10-1536x338.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/10-560x123.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/10.png 1702w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Moving on, Cloudflare offers static and dynamic options for rewriting the path. A static rewrite would apply the same value to the URL path of every request. This use case requires a dynamic rewrite, where, for each request, Cloudflare evaluates the value as an expression which yields the path.<\/p>\n<p>Your expression would prepend the existing path with <code>\/file\/&lt;bucket name&gt;<\/code>, like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104756 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/11-1024x320.png\" alt=\"Cloudflare rewrite \" width=\"1024\" height=\"320\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/11-1024x320.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/11-300x94.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/11-768x240.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/11-560x175.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/11.png 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Save the Transform Rule, and try to access your test file again, this time without the <code>\/file\/&lt;bucket name&gt;<\/code> prefix in the URL path, for example: <code>https:\/\/files.example.com\/smiley.png<\/code>.<\/p>\n<p>You should see your test file, as expected:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104757 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/12-1024x474.png\" alt=\"smiley face\" width=\"1024\" height=\"474\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/12-1024x474.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/12-300x139.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/12-768x356.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/12-1536x711.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/12-560x259.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/12.png 1590w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Great! Now, let\u2019s take a look at those HTTP headers in the response.<\/p>\n<h3><strong>Remove HTTP Headers From the Response<\/strong><\/h3>\n<p>You could use Chrome Developer Tools to view the response headers, but I prefer the <a href=\"https:\/\/curl.se\/\" target=\"_blank\" rel=\"noopener\">curl command line tool<\/a>. I used the <code>--head<\/code> argument to show the HTTP headers without the response body, since my terminal would not be happy with binary image data!<\/p>\n<p>Note: I\u2019ve removed some extraneous headers from this and subsequent HTTP responses for clarity and length.<\/p>\n<p><code>% curl --head https:\/\/files.superpat.com\/smiley.png<br \/>\nHTTP\/2 200<br \/>\n<strong>date:<\/strong> Thu, 20 Jan 2022 01:26:10 GMT<br \/>\n<strong>content-type:<\/strong> image\/png<br \/>\n<strong>content-length:<\/strong> 23889<br \/>\n<strong>x-bz-file-name:<\/strong> smiley.png<br \/>\n<strong>x-bz-file-id:<\/strong> 4_zf1f51fb913357c4f74ed0c1b_f1163cc3f37a60613_d20220119_m204457_c004_v0402000_t0044<br \/>\n<strong>x-bz-content-sha1:<\/strong> 3cea1118fbaab607a7afd930480670970b278586<br \/>\n<strong>x-bz-upload-timestamp:<\/strong> 1642625097000<br \/>\n<strong>x-bz-info-src_last_modified_millis:<\/strong> 1642192830529<br \/>\n<strong>cache-control:<\/strong> max-age=14400<br \/>\n<strong>cf-cache-status:<\/strong> MISS<br \/>\n<strong>last-modified:<\/strong> Thu, 20 Jan 2022 01:26:10 GMT<\/code><\/p>\n<p>Our goal is to remove all the <code>x-bz<\/code> headers. Create a Modify Response Header rule and set its name to something like \u201cRemove Backbaze B2 Headers.\u201d We want this rule to apply to <em>all<\/em> traffic, so the match expression is simple:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104758 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/13-1024x213.png\" alt=\"Use expression builder\" width=\"1024\" height=\"213\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/13-1024x213.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/13-300x62.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/13-768x160.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/13-1536x320.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/13-560x117.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/13.png 1710w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Unfortunately there isn\u2019t a way to tell Cloudflare to remove all the headers that are prefixed <code>x-bz<\/code>, so we just have to list them all:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104759 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/14-1024x437.png\" alt=\"Modify response header\" width=\"1024\" height=\"437\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/14-1024x437.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/14-300x128.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/14-768x328.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/14-1536x656.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/14-560x239.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/14.png 1738w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Save the rule, and request your test file again. You should see fewer headers:<\/p>\n<p><code>% curl --head https:\/\/files.superpat.com\/smiley.png<br \/>\nHTTP\/2 200<br \/>\n<strong>date:<\/strong> Thu, 20 Jan 2022 01:57:01 GMT<br \/>\n<strong>content-type:<\/strong> image\/png<br \/>\n<strong>content-length:<\/strong> 23889<br \/>\n<strong>cache-control:<\/strong> max-age=14400<br \/>\n<strong>cf-cache-status:<\/strong> HIT<br \/>\n<strong>age:<\/strong> 1851<br \/>\n<strong>last-modified:<\/strong> Thu, 20 Jan 2022 01:26:10 GMT<\/code><\/p>\n<h3><strong>Optimize Cache Efficiency via the ETag and Cache-Control HTTP Headers<\/strong><\/h3>\n<p>We can follow James\u2019 lead in making caching more efficient by leveraging the <code>ETag<\/code> header. As explained in the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/ETag\" target=\"_blank\" rel=\"noopener\">MDN Web Docs for ETag<\/a>:<\/p>\n<blockquote><p>The <strong><code>ETag<\/code><\/strong> (or <strong>entity tag<\/strong>) HTTP response header is an identifier for a specific version of a resource. It lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content was not changed.<\/p><\/blockquote>\n<p>Essentially, a cache can just request the HTTP headers for a resource and only proceed to fetch the resource body if the <code>ETag<\/code> has changed.<\/p>\n<p>James constructed the ETag by using one of <code>x-bz-content-sha1<\/code>, <code>x-bz-info-src_last_modified_millis<\/code>, or <code>x-bz-file-id<\/code>, in that order. If none of those headers are set, then neither is <code>ETag<\/code>. James wanted to be sure that, in case <code>x-bz-content-sha1<\/code> was not present in the Backblaze B2 response, he could set a value for ETag. In fact, <code>x-bz-content-sha1<\/code> is present as an HTTP header in every Backblaze B2 response, so it\u2019s safe to use it directly as the value for <code>ETag<\/code>. It\u2019s an interesting challenge to try to implement James\u2019 Cloudflare Worker logic in a Transform Rule, though!<\/p>\n<p>It\u2019s not possible to express this level of complexity in a Transform Rule, but we can apply a little lateral thinking to the problem. We can easily concatenate the three headers to create a result that will change when any one or more of them changes:<\/p>\n<p><code>concat(http.response.headers[\"x-bz-content-sha1\"][0],<br \/>\nhttp.response.headers[\"x-bz-info-src_last_modified_millis\"][0],<br \/>\nhttp.response.headers[\"x-bz-file-id\"][0])<\/code><\/p>\n<p>Note that it\u2019s possible for there to be multiple values of a given HTTP header, so <code>http.response.headers[\"&lt;header-name&gt;\"]<\/code> is an array. <code>http.response.headers[\"&lt;header-name&gt;\"][0]<\/code> yields the first, and in most cases only, element of the array.<\/p>\n<p>Edit the Transform Rule you just created, update its name to something like \u201cRemove Backblaze B2 Headers, set ETag,\u201d and add a header with a dynamic value:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104760 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/15-1024x423.png\" alt=\"You can modify up to 30 response headers\" width=\"1024\" height=\"423\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/15-1024x423.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/15-300x124.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/15-768x317.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/15-1536x634.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/15-560x231.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/15.png 1999w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Don\u2019t worry about the ordering; Cloudflare will reorder the operations so that \u201cset\u201d occurs before \u201cremove.\u201d Also, if none of those headers are present in the response, resulting in an empty value for the <code>ETag<\/code> header, Cloudflare will not set that header at all. Exactly the behavior we need!<\/p>\n<p>Another test shows the result. Note that HTTP headers are not case-sensitive, so <code>etag<\/code> has just the same meaning as <code>ETag<\/code>:<\/p>\n<p><code>% curl --head https:\/\/files.superpat.com\/smiley.png<br \/>\nHTTP\/2 200<br \/>\n<strong>date:<\/strong> Thu, 20 Jan 2022 02:01:19 GMT<br \/>\n<strong>content-type:<\/strong> image\/png<br \/>\n<strong>content-length:<\/strong> 23889<br \/>\n<strong>cache-control:<\/strong> max-age=14400<br \/>\n<strong>cf-cache-status:<\/strong> HIT<br \/>\n<strong>age:<\/strong> 2198<br \/>\n<strong>last-modified:<\/strong> Thu, 20 Jan 2022 01:24:41 GMT<br \/>\n<strong>etag:<\/strong> 3cea1118fbaab607a7afd930480670970b27858616421928305294_zf1f51fb913357c4f74ed0c1b_f1163cc3f37a60613_d20220119_m204457_c004_v0402000_t0044<\/code><\/p>\n<p>The other cache-related header is <code>Cache-Control<\/code>, which tells the browser how to cache the resource. As you can see in the above responses, Cloudflare sets <code>Cache-Control<\/code> to a <code>max-age<\/code> of <code>14400 seconds<\/code>, or four hours.<\/p>\n<p>James\u2019 code, on the other hand, sets <code>Cache-Control<\/code> according to whether or not the request to B2 Cloud Storage is successful. For an HTTP status code of 200, <code>Cache-Control<\/code> is set to <code>public<\/code>, <code>max-age=31536000<\/code>, instructing the browser to cache the response for 31,536,000 seconds; in other words, a year. For any other HTTP status, <code>Cache-Control<\/code> is set to <code>public<\/code>, <code>max-age=300<\/code>, so the browser only caches the response for five minutes. In both cases, the <code>public<\/code> directive indicates that the response can be cached in a shared cache, even if the request contained an <code>Authorization<\/code> header field.<\/p>\n<p>Note: We\u2019re effectively assuming that once created, files on the image host are immutable. This is often true for this use case, but you should think carefully about cache policy when you build your own solutions.<\/p>\n<p>At present, Cloudflare Transform Rules do not give access to the HTTP status code, but, again, we can satisfy the requirement with a little thought and investigation. As mentioned above, for successful operations, Cloudflare sets <code>Cache-Control<\/code> to <code>max-age=14400<\/code>, or four hours. For failed operations, for example, requesting a non-existent object, Cloudflare passes back the <code>Cache-Control<\/code> header from Backblaze B2 of <code>max-age=0<\/code>, <code>no-cache<\/code>, <code>no-store<\/code>. With this information, it\u2019s straightforward to construct a Transform Rule to increase <code>max-age<\/code> from <code>14400<\/code> to <code>31536000<\/code> for the successful case:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104761 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/16-1024x567.png\" alt=\"HTTP Response Header Modification rule\" width=\"1024\" height=\"567\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/16-1024x567.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/16-300x166.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/16-768x425.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/16-1536x850.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/16-560x310.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/16.png 1999w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Again, we need to use <code>[0]<\/code> to select the first matching HTTP header. Notice that this rule uses a <em>static<\/em> value for the header\u2014it\u2019s the same for every matching response.<\/p>\n<p>We\u2019ll leave the header as it\u2019s set by B2 Cloud Storage for failure cases, though it would be just as easy to override it.<\/p>\n<p>Another test shows the results of our efforts:<\/p>\n<p><code>% curl --head https:\/\/files.superpat.com\/smiley.png<br \/>\nHTTP\/2 200<br \/>\n<strong>date:<\/strong> Thu, 20 Jan 2022 02:31:38 GMT<br \/>\n<strong>content-type:<\/strong> image\/png<br \/>\n<strong>content-length:<\/strong> 23889<br \/>\n<strong>cache-control:<\/strong> public, max-age=31536000<br \/>\n<strong>cf-cache-status:<\/strong> HIT<br \/>\n<strong>age:<\/strong> 4017<br \/>\n<strong>last-modified:<\/strong> Thu, 20 Jan 2022 01:24:41 GMT<br \/>\n<strong>etag:<\/strong> 3cea1118fbaab607a7afd930480670970b27858616421928305294_zf1f51fb913357c4f74ed0c1b_f1163cc3f37a60613_d20220119_m204457_c004_v0402000_t0044<\/code><\/p>\n<p>Checking the failure case. As expected, Backblaze B2\u2019s default <code>cache-control<\/code> header is passed through. Also, notice that there is no <code>ETag<\/code> header, since B2 Cloud Storage did not return any <code>x-bz<\/code> headers:<\/p>\n<p><code>% curl --head https:\/\/files.superpat.com\/badname.png<br \/>\nHTTP\/2 404<br \/>\n<strong>date:<\/strong> Thu, 20 Jan 2022 02:32:35 GMT<br \/>\n<strong>content-type:<\/strong> application\/json;charset=utf-8<br \/>\n<strong>content-length:<\/strong> 94<br \/>\n<strong>cache-control:<\/strong> max-age=0, no-cache, no-store<br \/>\n<strong>cf-cache-status:<\/strong> BYPASS<\/code><\/p>\n<p>Success! Browsers and caches will aggressively cache responses, reducing the burden on Cloudflare and Backblaze B2.<\/p>\n<h3><strong>Set a CORS Header for Image Files<\/strong><\/h3>\n<p>We\u2019re almost done! Our final requirement is to set a <a href=\"\/blog\/crash-cors-a-guide-for-using-cors\/\" target=\"_blank\" rel=\"noopener\">cross-origin resource sharing (CORS)<\/a> header for images so that they can be manipulated in web pages from any domain on the web.<\/p>\n<p>The Transform Rule must match a range of file extensions, and set the <code>Access-Control-Allow-Origin<\/code> HTTP response header to allow <em>any<\/em> webpage to access resources:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104762 size-large\" src=\"https:\/\/www.backblaze.com\/blog\/wp-content\/uploads\/2022\/02\/17-1024x565.png\" alt=\"CORS for images\" width=\"1024\" height=\"565\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/17-1024x565.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/17-300x165.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/17-768x423.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/17-1536x847.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/17-560x309.png 560w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/17.png 1999w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Upload a text file and run a final couple of tests to see the results. First, the image:<\/p>\n<p><code>% curl --head https:\/\/files.superpat.com\/smiley.png<br \/>\nHTTP\/2 200<br \/>\n<strong>date:<\/strong> Thu, 20 Jan 2022 02:50:52 GMT<br \/>\n<strong>content-type:<\/strong> image\/png<br \/>\n<strong>content-length:<\/strong> 23889<br \/>\n<strong>cache-control:<\/strong> public, max-age=31536000<br \/>\n<strong>cf-cache-status:<\/strong> HIT<br \/>\n<strong>age:<\/strong> 4459<br \/>\n<strong>last-modified:<\/strong> Thu, 20 Jan 2022 01:36:33 GMT<br \/>\n<strong>etag:<\/strong> 3cea1118fbaab607a7afd930480670970b27858616421928305294_zf1f51fb913357c4f74ed0c1b_f1163cc3f37a60613_d20220119_m204457_c004_v0402000_t0044<br \/>\n<strong>access-control-allow-origin:<\/strong> *<\/code><\/p>\n<p>The <code>Access-Control-Allow-Origin<\/code> header is present, as expected.<\/p>\n<p>Finally, the text file, without an <code>Access-Control-Allow-Origin<\/code> header. You can use the <code>--include<\/code> argument rather than <code>--head<\/code> to see the file content as well as the headers:<\/p>\n<p><code>% curl --include https:\/\/files.superpat.com\/hello.txt<br \/>\nHTTP\/2 200<br \/>\n<strong>date:<\/strong> Thu, 20 Jan 2022 02:48:51 GMT<br \/>\n<strong>content-type:<\/strong> text\/plain<br \/>\n<strong>content-length:<\/strong> 14<br \/>\n<strong>accept-ranges:<\/strong> bytes<br \/>\n<strong>cf-cache-status:<\/strong> DYNAMIC<br \/>\n<strong>etag:<\/strong> 60fde9c2310b0d4cad4dab8d126b04387efba28916426467400754_zf1f51fb913357c4f74ed0c1b_f1092902424a40504_d20220120_m024635_c004_v0402003_t0000<\/code><\/p>\n<p>Hello, World!<\/p>\n<h3><strong>Troubleshooting<\/strong><\/h3>\n<p>The most frequent issue I encountered while getting all this working was mixing up request and response when referencing HTTP headers. If things are not working as expected, double check that you don\u2019t have <code>http.response.headers[\"&lt;header-name&gt;\"]<\/code> where you need <code>http.request.headers[\"&lt;header-name&gt;\"]<\/code> or vice versa.<\/p>\n<h2><strong>Can I Really Do This Free of Charge?<\/strong><\/h2>\n<p><a href=\"https:\/\/www.backblaze.com\/cloud-storage\/pricing\" target=\"_blank\" rel=\"noopener\">Backblaze B2 pricing<\/a> is very simple:<\/p>\n<h5><strong>Storage<\/strong><\/h5>\n<ul>\n<li>The first 10GB of storage is free of charge.<\/li>\n<li>Above 10GB, we charge $6\/TB per month, around one-fifth of the cost of hyperscalers.<\/li>\n<li>Storage cost is calculated hourly, with no minimum retention requirement, and billed monthly.<\/li>\n<\/ul>\n<h5><strong>Downloaded Data<\/strong><\/h5>\n<ul>\n<li>Egress is free up to three times the data stored per month, and&#8230;<\/li>\n<li>Downloads are free through our <a href=\"https:\/\/www.backblaze.com\/cloud-storage\/solutions\/cdn\" target=\"_blank\" rel=\"noopener\">CDN and compute partners<\/a>, of which Cloudflare is one.<\/li>\n<\/ul>\n<h5><strong>Transactions<\/strong><\/h5>\n<ul>\n<li>Each download operation counts as one <a href=\"https:\/\/www.backblaze.com\/cloud-storage\/transaction-pricing\" target=\"_blank\" rel=\"noopener\">class B transaction<\/a>.<\/li>\n<li>The first 2,500 class B transactions each day are free.<\/li>\n<li>Beyond 2,500 class B transactions, they are charged at a rate of $0.004 per 10,000.<\/li>\n<\/ul>\n<h5><strong>No Surprise Bills<\/strong><\/h5>\n<ul>\n<li>If you already signed up for Backblaze B2, you might have noticed that you didn\u2019t have to provide a credit card number. Your 10GB of free storage never expires, and there is no chance of you unexpectedly incurring any charges.<\/li>\n<\/ul>\n<p>By serving your images via Cloudflare\u2019s global CDN and optimizing your cache configuration as described above, you will incur no download costs from B2 Cloud Storage, and likely stay well within the 2,500 free download operations per day. Similarly, Cloudflare\u2019s free plan does not require a credit card for activation, and there are no data or transaction limits.<\/p>\n<p>Sign up for Backblaze B2 today, deploy your own personal image host, <a href=\"https:\/\/www.backblaze.com\/cloud-storage\/integrations\" target=\"_blank\" rel=\"noopener\">explore our off-the-shelf integrations<\/a>, and consider what you can create with an affordable, S3-compatible cloud object storage platform.<\/p>\n\n\n<p><br \/>Editor\u2019s Note: This post refers to past pricing; please refer to <a href=\"https:\/\/www.backblaze.com\/cloud-storage\/pricing\" target=\"_blank\" rel=\"noreferrer noopener\">Backblaze Pricing Comparison<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to use Cloudflare Transform Rules with Backblaze B2 to create your own, personal 10GB image hosting site for free.<\/p>\n","protected":false},"author":174,"featured_media":104739,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[481,7,483,479],"tags":[468],"class_list":["post-104734","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cdn","category-cloud-storage","category-tech-lab","category-technology","tag-b2cloud","entry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Free Image Hosting With Cloudflare Transform Rules and Backblaze B2<\/title>\n<meta name=\"description\" content=\"By serving your images via Cloudflare\u2019s global CDN and optimizing your cache configuration you will incur no download costs.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Free Image Hosting With Cloudflare Transform Rules and Backblaze B2\" \/>\n<meta property=\"og:description\" content=\"By serving your images via Cloudflare\u2019s global CDN and optimizing your cache configuration you will incur no download costs.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/\" \/>\n<meta property=\"og:site_name\" content=\"Backblaze Blog | Cloud Storage &amp; Cloud Backup\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/backblaze\" \/>\n<meta property=\"article:published_time\" content=\"2022-02-08T16:54:32+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-05-14T20:48:12+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1440\" \/>\n\t<meta property=\"og:image:height\" content=\"820\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Pat Patterson\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@backblaze\" \/>\n<meta name=\"twitter:site\" content=\"@backblaze\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Pat Patterson\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"16 minutes\" \/>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Free Image Hosting With Cloudflare Transform Rules and Backblaze B2","description":"By serving your images via Cloudflare\u2019s global CDN and optimizing your cache configuration you will incur no download costs.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/","og_locale":"en_US","og_type":"article","og_title":"Free Image Hosting With Cloudflare Transform Rules and Backblaze B2","og_description":"By serving your images via Cloudflare\u2019s global CDN and optimizing your cache configuration you will incur no download costs.","og_url":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/","og_site_name":"Backblaze Blog | Cloud Storage &amp; Cloud Backup","article_publisher":"https:\/\/www.facebook.com\/backblaze","article_published_time":"2022-02-08T16:54:32+00:00","article_modified_time":"2026-05-14T20:48:12+00:00","og_image":[{"width":1440,"height":820,"url":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D.png","type":"image\/png"}],"author":"Pat Patterson","twitter_card":"summary_large_image","twitter_creator":"@backblaze","twitter_site":"@backblaze","twitter_misc":{"Written by":"Pat Patterson","Est. reading time":"16 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"TechArticle","@id":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/#article","isPartOf":{"@id":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/"},"author":{"name":"Pat Patterson","@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#\/schema\/person\/a724a8aee97b6451107442747cd101a4"},"headline":"Free Image Hosting With Cloudflare Transform Rules and Backblaze B2","datePublished":"2022-02-08T16:54:32+00:00","dateModified":"2026-05-14T20:48:12+00:00","mainEntityOfPage":{"@id":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/"},"wordCount":2604,"commentCount":7,"publisher":{"@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/#primaryimage"},"thumbnailUrl":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D.png","keywords":["B2Cloud"],"articleSection":["CDN","Cloud Storage","Tech Lab","Technology"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/","url":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/","name":"Free Image Hosting With Cloudflare Transform Rules and Backblaze B2","isPartOf":{"@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/#primaryimage"},"image":{"@id":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/#primaryimage"},"thumbnailUrl":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D.png","datePublished":"2022-02-08T16:54:32+00:00","dateModified":"2026-05-14T20:48:12+00:00","description":"By serving your images via Cloudflare\u2019s global CDN and optimizing your cache configuration you will incur no download costs.","breadcrumb":{"@id":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/#primaryimage","url":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D.png","contentUrl":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D.png","width":1440,"height":820,"caption":"diagram of Browser secure to Cloudflare secure to Backblaze B2 Cloud Storage"},{"@type":"BreadcrumbList","@id":"https:\/\/www.backblaze.com\/blog\/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Free Image Hosting With Cloudflare Transform Rules and Backblaze B2"}]},{"@type":"WebSite","@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#website","url":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/","name":"Backblaze Cloud Solutions Blog","description":"Cloud Storage &amp; Cloud Backup","publisher":{"@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#organization","name":"Backblaze","url":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/i0.wp.com\/www.backblaze.com\/blog\/wp-content\/uploads\/2017\/12\/backblaze_icon_transparent.png?fit=512%2C512&ssl=1","contentUrl":"https:\/\/i0.wp.com\/www.backblaze.com\/blog\/wp-content\/uploads\/2017\/12\/backblaze_icon_transparent.png?fit=512%2C512&ssl=1","width":512,"height":512,"caption":"Backblaze"},"image":{"@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/backblaze","https:\/\/x.com\/backblaze","https:\/\/www.youtube.com\/user\/Backblaze","https:\/\/en.wikipedia.org\/wiki\/Backblaze"]},{"@type":"Person","@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#\/schema\/person\/a724a8aee97b6451107442747cd101a4","name":"Pat Patterson","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/01\/PatPatterson1920px-150x150.png","url":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/01\/PatPatterson1920px-150x150.png","contentUrl":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/01\/PatPatterson1920px-150x150.png","caption":"Pat Patterson"},"description":"Pat Patterson is the former chief technical evangelist at Backblaze. Over his three decades in the industry, Pat has built software and communities at Sun Microsystems, Salesforce, StreamSets, and Citrix. In his role at Backblaze, he creates and delivers content tailored to the needs of the hands-on technical professional, acts as the \u201cvoice of the developer\u201d on the Product team, and actively participates in the wider technical community. Outside the office, Pat runs far, having completed ultramarathons up to the 50 mile distance. Catch up with Pat via Bluesky or LinkedIn.","url":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/author\/pat\/"}]}},"jetpack_featured_media_url":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/02\/bb-bh-Free-Image-Hosting-with-Cloudflare-Transform-Rules-and-Backblaze-B2_Design-D.png","_links":{"self":[{"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/posts\/104734","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/users\/174"}],"replies":[{"embeddable":true,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/comments?post=104734"}],"version-history":[{"count":0,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/posts\/104734\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/media\/104739"}],"wp:attachment":[{"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/media?parent=104734"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/categories?post=104734"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/tags?post=104734"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}