HTTP Caching Explained

Cache-Control, ETag/If-None-Match, Last-Modified/If-Modified-Since, 304, CDN vs browser caches, cache busting, and Vary.

HTTP caching is the mechanism for storing responses so the same resource isn't downloaded again and again. Done well, it speeds up responses, cuts server load and bandwidth, and even helps offline. Two ideas make it click: how long a response stays fresh (freshness) and how to check whether a stale copy is still valid (validation).

Cache-Control: controlling freshness

The Cache-Control response header is the heart of caching policy. The common directives are:

HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
ETag: "a1b2c3d4"
Content-Type: text/css

Revalidation: ETag and Last-Modified

Once the fresh period passes, a cache doesn't throw the copy away — it asks the server 'is this still valid?' There are two conditional-request styles. ETag is a fingerprint (version identifier) of the resource's content; Last-Modified is its last change time. The browser sends the stored ETag back in If-None-Match and the Last-Modified value in If-Modified-Since.

GET /style.css HTTP/1.1
If-None-Match: "a1b2c3d4"

HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4"
Cache-Control: public, max-age=3600

If nothing changed, the server replies with just 304 Not Modified and no body. The client reuses the copy it already had, and the transfer shrinks to a few header lines. If the content did change, the server returns 200 with the full body and a new ETag.

Browser cache, CDNs, and cache busting

Caches live in several layers: the user's browser cache and, in front of it, shared caches like CDNs and reverse proxies. A response marked public can be stored by a CDN and served quickly from a nearby edge to users worldwide. The catch: a long max-age means users keep seeing an old version even after you ship new content.

The standard remedy is cache busting: embed a content hash in the filename or query string (app.9f3a2b.js) so the URL itself changes whenever the content does. A new URL is treated as uncached, so the fresh file is fetched, while unchanged files can be cached for a year with max-age=31536000, immutable.

Vary: same URL, different responses

The same URL can produce different responses depending on request headers — gzip vs identity based on Accept-Encoding, or different languages based on Accept-Language. The Vary response header tells caches 'this response depends on the listed request headers,' so a cache won't serve the wrong variant to another user. Forgetting Vary: Accept-Encoding can cause a gzip response to be handed to a client that can't decompress it.