HTTP Cache Headers: The Freshness Control Panel

March 16, 2026


If cache key design decides which responses get grouped together, cache headers decide how long each one stays valid. Both have to be right. The headers live on every response and quietly tell every cache between you and the user how to behave.

Cache-Control is the main control surface. The directives compose, and the combinations matter.

max-age=N says the response is fresh for N seconds in any cache. s-maxage=N overrides that, but only for shared caches like a CDN. A common pattern is max-age=60, s-maxage=3600: browsers re-check every minute, the CDN serves the same response for an hour. That split keeps your origin quiet while letting individual users see updates quickly.

public invites every cache to store the response. private restricts it to the end user's browser. Use private for anything personalized.

no-cache is the directive engineers misread most often. It does not mean "do not cache." It means "store it, but revalidate with origin before serving." The cache holds onto the bytes and sends a conditional request every time. no-store is the one that actually means do not store anything anywhere. Reach for it on tokens, payment data, and health records.

stale-while-revalidate=N lets the cache serve a stale response for up to N seconds while a background refresh runs. Users get instant responses, the origin gets one well timed request, and freshness drifts by a bounded amount. must-revalidate is the opposite stance: once stale, the cache must not serve it without checking origin.

ETag and If-None-Match close the loop. The origin tags each response with an opaque version string. When the cache wants to refresh, it sends the tag back. If nothing changed, the origin returns 304 Not Modified with no body, and the cache resets the TTL on the bytes it already has. Bandwidth saved, freshness preserved.

Vary lists request headers that should be folded into the cache key. Vary: Accept-Encoding is essential. Vary: User-Agent will shred your hit rate the same way unfiltered query strings do.

The production failure I want you to remember: a team deploys a new API response shape on a JSON endpoint that was already returning Cache-Control: max-age=31536000. The CDN locks onto the old shape for the URLs already in cache and serves it for the full year that header promised. New clients call the same URLs, get last week's schema, and crash. Discovery happens through a support ticket. The fix is a manual purge across every POP, plus the realization that max-age of one year only belongs on content addressed assets, never on API responses.

Headers are not metadata. They are a contract.

Key takeaway

Cache key design decides what gets cached together. Cache headers decide how long it stays valid. They are different controls, and you need both right.

Originally posted on LinkedIn. View original.


All Rights Reserved.