Configuring Cloudflare's Cache, and Cache-Control Headers
Table of Contents
I’ve talked enough about Cloudflare caching that I’m not going to do introduce it again. This time though, we’re going in depth just a bit more, going over what’s cached, default cache times, and… more headers.
Cloudflare’s Default Cache Policy
First: Cloudflare only caches resources from proxied sites, meaning the ones with an orange cloud in your DNS panel. Resources from third-parties or grey-cloud sites don’t get anything.
Second: Only static content is cached. “Static content” is determined by file extension and not MIME type. Here’s the list of default cached extensions1:
- bmp (Images)
- class (Compiled Java)
- css (Webpage styling and layout rules)
- csv (Tabular structured data)
- doc (Microsoft Word document)
- docx (Microsoft Word document)
- ejs (Embedded JavaScript)
- eot (Fonts)
- eps (Graphics / Documents)
- gif (Images)
- ico (Images)
- jar (Compiled Java, entire app)
- jpeg (Images)
- jpg (Images)
- js (JavaScript)
- mid (MIDI audio)
- midi (MIDI audio)
- otf (Fonts)
- ps (Graphics / Documents)
- svg (Vector graphics)
- svgz (Vector graphics, compressed)
- swf (Flash app)
- tif (Images)
- tiff (Images)
- ttf (Fonts)
- webp (Images)
- woff (Fonts)
- woff2 (Fonts)
- xls (Microsoft Excel spreadsheet)
- xlsx (Microsoft Excel spreadsheet)
Also, your robots.txt
will be cached.
On top of this, there’s a few other rules:
Default Cache Times
By default, positive response codes like 200 OK
, 204 No Content
, and 206 Partial Content
, are cached for 120 minutes (two hours).
Redirects (301 Moved Permanently
, 302 Found
, etc.) are cached for 20 minutes.
404 Not Found
and 410 Gone
, and similar errors are cached for only 3 minutes.
405 Method Not Allowed
, and 500
(server) series errors only last for just 1 minute.
Note that these can be overridden with a control header, but we’ll get to that later.
Max Cache Size
512 MB per resource, or a flat 5 GB for enterprise customers. But if you’re serving files larger than that, I think there’s bigger things to worry about than CDN cache.
Cloudflare’s CF-Cache-Status
header
With every request that goes through Cloudflare, a CF-Cache-Status
header is added on the response, that details what the cache behavior was for that resource.
Here’s what the values mean:
HIT
: Resource was cached and used.MISS
: Resource is cache-eligible but was not cached, and had to be fetched from the origin server. Subsequent requests should be cached.EXPIRED
: Resource was cached past it’s TTL (Time To Live), and was served from the origin server, then updated in the cache.STALE
: Resource cached past TTL, but could not be fetched from the origin server. Instead, the potentially out-of-date cached version was used.BYPASS
: The originCache-Control
header explicitly instructed Cloudflare to not cache the resource.REVALIDATED
: Resource served from cache while stale, and was verified to still be valid by a conditionalIf-None-Match
orIf-Modified-Since
origin request.UPDATING
: Resource served from cache while expired, because the cache was busy retrieving an updated copy from the origin server.DYNAMIC
: Cloudflare is not going to cache this resource with the current configuration.
The Cache-Control
header (or Pragma
)
A server can send a Cache-Control
header with many different directives that specify who can cache that response, how long it should be cached for, and what to do when that time runs out.2
I use a relatively simple one that looks like this (on everything):
Cache-Control: public, max-age=172800, stale-if-error=604800
172,800 seconds = 2,880 minutes = 48 hours = two days.
604,800 seconds = 10,080 minutes = 168 hours = 7 days = one week.
Any cache may store the response for a maximum of two days without revalidation, and if unable to revalidate, stale responses are permitted for up to one week.
Now technically this is incorrect, as my content can change daily, meaning browsers might cache parts of the page that have indeed changed. However, because of how most browsers behave, this is fine, they have their own rules for cache eligibility.
The public
bit at the front means that a shared cache is allowed to store the resource requested, meaning that proxy caches are allowed to store it, meaning that Cloudflare is allowed to store it.
private
would be the opposite here.
It doesn’t specify anything special either, such as revalidation behavior. I’m leaving that up to the client device.. for now.
Disabling Cache
If your Cache-Control
header specifies either private
, max-age=0
, no-cache
, no-store
3, or similar mechanisms, then this is what causes a BYPASS
cache status.
Pragma
Pragma
was a rather general-purpose HTTP/1.0 header… in theory.
In reality, it only received one directive: no-cache
.
Only used for backwards-compatibility, Pragma: no-cache
has the same effect as Cache-Control: no-cache
.
Real-World Performance Test
Let’s do a quick test: How much of this site is eligible for caching? I’ll be doing two tests: homepage, and the post on Switch accessories, because it has a lot of images, and pages have slightly different resources to load.
Of this I’ll measure cache eligibility on two fronts: per-request and total size.
Cache eligible resources are those that do not have a DYNAMIC
cache status header (BYPASS
cannot be sent for now), cache ineligible resources do have DYNAMIC
as status, and external resources are ones with no CF-Cache-Control
at all.
Homepage
Requests
Cache-eligible | Cache-ineligible | External |
---|---|---|
17 | 2 | 8 |
Data
Cache-eligible | Cache-ineligible | External |
---|---|---|
1.93 MB | 20.3 kB | 65.9 kB |
Post
Requests
Cache-eligible | Cache-ineligible | External |
---|---|---|
37 | 2 | 33 |
Data
Cache-eligible | Cache-ineligible | External |
---|---|---|
2.64 MB | 25.3 kB | 263.4 kB |
Observations
The only ineligible items across both tests were the main page HTML, and the .webmanifest
file for Android phones.
While Gravatar, Ko-fi, Google’s font data, and Disqus are all (expectedly) external, I did notice that some external sites (e.g., cdnjs.cloudflare.com
, c.disquscdn.com
) are also sending CF headers, meaning that they go through Cloudflare too.
Since I’m making a point about the total effectiveness of Cloudflare, I’m including these results in the “Cache-eligible” column.
-
no-cache
means that responses must check that the copy is valid and up-to-date before using it, effectively just turning cache off.no-store
means that it cannot store the response, period, as opposed to not being able to use it without checking first. The former effectively disables caching, and the latter explicitly disallows it. ↩︎