Guide

A CDN has to answer two questions for every request. Which stored object does this request match? And how long may that object be served? The first answer is the cache key. The second comes from Cache-Control headers that your origin sends. Both decide your hit ratio, and both can leak data if they're wrong.

Concept

A cache key is the set of request parts that identify an object, usually the host, path and query string. A validator is a version tag, such as an ETag, that lets a cache ask "has this changed?" without downloading it again.

The big idea

Think of the cache key as a label on a shelf. Items with different labels are stored separately. If you put the user's name on the label, each user gets their own shelf. If you leave it off, everyone reaches for the same item. Labels that are too detailed waste shelf space. Labels that are too vague hand out the wrong item.

Walking through it

The origin tells caches what to do with the Cache-Control response header.

DirectiveMeaning
max-age=NFresh for N seconds in any cache
s-maxage=NOverrides max-age for shared caches such as CDNs
public / privateprivate allows only the user's browser to cache
no-cacheStore it, but check with the origin before each use
no-storeNever store it anywhere
immutableThis URL will never change, so don't revalidate
stale-while-revalidate=NServe stale for N seconds while fetching a fresh copy
stale-if-error=NServe stale for N seconds if the origin errors

stale-while-revalidate and stale-if-error are underused. The first hides refresh latency. The second keeps your site up while the origin is down.

Validators make revalidation cheap. The origin sends an ETag or Last-Modified. When a cached copy expires, the CDN asks, If-None-Match: "abc123". If nothing changed, the origin answers 304 Not Modified with no body.

Cache keys are where most hit ratios are lost.

  • Query strings. ?utm_source=twitter and ?utm_source=email are separate keys for the same page. Strip marketing parameters.
  • Vary headers. Vary: Accept-Encoding gives a few variants, which is fine. Vary: User-Agent gives thousands and effectively disables caching.
  • Normalize. Lowercase paths, sort query parameters, and map device types to a few values such as mobile and desktop.

The opposite mistake is worse. If the key leaves out something the response depends on, users get each other's content. Steam had an incident in December 2015, during a traffic surge, where a caching problem showed some users other people's account pages.

Warning

Don't let a CDN cache responses that set cookies or depend on the Authorization header, unless the cache key was designed for that. Most CDNs skip these by default. Overriding the default is how account pages leak.

Common mistakes

  • Vary: User-Agent. Every browser variant gets its own entry, so most requests miss.
  • Tracking parameters in the key. Each campaign splits the cache for the same page.
  • Personal responses without private. Anything per-user needs private or no-store, unless the user is part of the key.

Check yourself

  1. Why does Vary: User-Agent hurt the hit ratio? Each distinct User-Agent string gets its own cache entry, so most requests miss.
  2. What should a personalized response use? private or no-store, unless the user or segment is explicitly part of the cache key.

Key takeaways

  • The cache key defines what counts as the same object. Keep it as small as the response allows.
  • s-maxage, stale-while-revalidate and immutable control freshness at the CDN.
  • A key that leaves out a per-user detail leaks data. A key with too much detail wastes the cache.

Further learning

MDN: Cache-Control lists every directive with examples.

Next, read about purging and versioned URLs, which covers how to change content already in the cache.