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 origin Cache-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 conditional If-None-Match or If-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-store3, 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.


  1. Cloudflare help center CDN page ↩︎

  2. MDN docs: Cache-Control HTTP header ↩︎

  3. 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. ↩︎