SyncTrix logoSyncTrix
All articles
Platform10 min read

HTTP caching headers: what each one does and the combinations that break

Cache-Control, ETag and Vary interact in ways that produce stale content or no caching at all. Here's what each does and how to combine them.

By Priya Iyer
HTTP caching headers: what each one does and the combinations that break

HTTP caching is one of the highest-leverage performance tools available and one of the easiest to misconfigure. The headers interact, some combinations cancel each other out, and the failure modes are asymmetric: too aggressive and users see stale content you cannot recall, too conservative and you cache nothing. Getting the edge and origin layers to agree is part of our cloud and infrastructure work.

01The directives that matter

Most caching behaviour comes from a small number of Cache-Control directives. The distinction between freshness and revalidation is the one that causes the most confusion - one controls how long a response may be used without asking, the other controls what happens afterwards.

DirectiveMeaningUse for
max-age=NFresh for N secondsAlmost everything cacheable
s-maxage=NFreshness for shared caches onlyDifferent CDN and browser lifetimes
no-cacheStore, but revalidate before useContent that changes unpredictably
no-storeDo not store at allGenuinely sensitive responses
immutableWill never change - skip revalidationContent-hashed assets
stale-while-revalidate=NServe stale, refresh in backgroundAvoiding latency on refresh
Cache-Control directives in practice

02no-cache does not mean do not cache

This naming is the single most common source of confusion. no-cache permits storage and requires revalidation before reuse - the response is cached, it is just checked first. no-store is the directive that means do not keep it.

Using no-store where no-cache was intended eliminates caching entirely and produces a full transfer on every request. Using no-cache where no-store was required means sensitive responses are being written to disk in intermediaries, which is a different and more serious problem.

03Immutable assets are the pattern to reach for

The hardest caching problem - invalidation - disappears when content never changes. Content-hashed filenames mean the URL changes whenever the bytes change, so the old URL can be cached effectively forever and the new one is simply a different resource.

This is why build tools hash asset filenames. Apply the same thinking beyond static assets wherever you can: version the cache key rather than mutating the value, and invalidation becomes a write to a new key instead of a coordinated purge that may not propagate.

04ETag and the revalidation round trip

An ETag lets a client ask whether its copy is still current. If it is, the server returns 304 with no body - saving bandwidth but not the round trip. That distinction matters: revalidation is cheaper than a full transfer and considerably more expensive than a cache hit with no request at all.

There is a subtle trap when a CDN sits in front. If the origin returns 304 on revalidation, that response carries no body and no fresh headers, so any header the CDN was caching stays as it was. We have seen a correctly configured response header silently missing in production for exactly this reason - the origin was right and the edge was serving an older header set.

  • ETags save bandwidth, not round trips.
  • Weak ETags are fine for most content and cheaper to generate.
  • A 304 carries no headers - anything header-based can go stale at the edge.
  • Set headers at the edge when they must survive revalidation.

05Vary is easy to get expensive

Vary tells caches that responses differ by a request header, so a separate entry is stored per distinct value. Vary on something with many possible values - a full User-Agent string being the classic mistake - and your hit rate approaches zero because almost every request is a unique cache key.

Keep the varied dimensions few and low-cardinality. Accept-Encoding is fine. Vary on Cookie is usually a mistake for anything you actually want cached, because cookie values are effectively unique per user.

06Stale-while-revalidate for the best of both

This directive lets a cache serve a stale response immediately while fetching a fresh one in the background. The user never waits for a revalidation, and the cache converges to current within a short window.

It suits content where being a few seconds behind is harmless - listings, feeds, dashboards, most marketing pages. It also removes the latency spike that otherwise occurs whenever a popular entry expires, which is the same thundering-herd shape that causes cache stampedes.

Topics

http caching headerscache-control explainedetag vs last-modifiedstale-while-revalidatecdn caching strategyvary header

Priya Iyer

Staff Engineer · SyncTrix

Writes about the engineering decisions behind production systems - architecture, delivery and the trade-offs that only show up at scale.

Building something like this?

SyncTrix engineers AI, SaaS, platform and cloud systems for enterprises and high-growth teams. Tell us what you're shipping and we'll scope it with you.