Explain what the HTTP directive Cache-Control: s-maxage does that max-age does not, and how CDN-Cache-Control and Surrogate-Control let you give a CDN a different lifetime from the browser.
answer
- s-maxage = shared only, beats max-age there
- short browser TTL, long edge TTL, purge the edge
- proxy-revalidate = must-revalidate for shared caches
- CDN-Cache-Control / Surrogate-Control target the CDN
- surrogate header stripped before the browser sees it
basics
~20 ss-maxage sets the freshness lifetime for shared caches only and overrides max-age there; browsers ignore it. CDN-Cache-Control and Surrogate-Control go further: they target the CDN specifically and are consumed by it, so browser, CDN and origin tiers can be tuned independently.
solid answer
~50 s`max-age=N` applies to every cache. `s-maxage=N` applies only to shared caches and takes precedence over `max-age` there, so `Cache-Control: max-age=60, s-maxage=86400` means browsers hold the response for a minute while the CDN holds it for a day. That asymmetry is the point: the edge copy is one object you can purge, whereas millions of browser copies are unreachable until they expire. Keep browser TTLs short and push the long lifetime to the edge. When you need finer separation there is a header hierarchy. `CDN-Cache-Control` is read only by CDNs and overrides `Cache-Control` for them; `Surrogate-Control` is the older vendor convention doing the same job, and a CDN that honours it typically strips it before responding so downstream caches never see it. A CDN applies the most specific header it understands, falling back to `Cache-Control`. Also remember `proxy-revalidate`: like `must-revalidate` but binding on shared caches only, forcing the edge to revalidate a stale entry instead of serving it.
code
http · 5 linesHTTP/1.1 200 OK
Cache-Control: max-age=60, s-maxage=86400
CDN-Cache-Control: max-age=604800
ETag: "catalog-3f1"
Content-Type: application/jsongo deeper
Know that s-maxage applies to shared caches only and that browsers keep using max-age.
Explain the precedence, give the short-browser/long-edge pattern, and know proxy-revalidate.
Design the three-tier policy, tie long edge TTLs to a purge-on-publish path, and verify per tier with Age and cache-status headers.
Own it as a platform default - which classes of response get which tier policy, how immutable asset naming removes the problem, and what the blast radius of an un-purgeable browser TTL is.
## Why one lifetime is not enough A response typically passes through three storage tiers: the origin's own cache, a shared cache at the edge (CDN or reverse proxy), and the user's browser. These tiers have very different properties. The **edge copy is controllable**. There is one of it per POP, you own the account, and you can purge it in seconds when content changes. The **browser copy is not**. Once `max-age=86400` is on a response, that user will not ask again for a day. You cannot reach into their disk. If you shipped a bug, they keep it. So the sensible policy is asymmetric: short in the browser, long at the edge. A single `max-age` cannot express that, which is why `s-maxage` exists. ## s-maxage `s-maxage=N` gives a freshness lifetime in seconds that applies **only to shared caches**, and where present it overrides `max-age` for them. Private caches ignore it entirely and continue to use `max-age` (or `Expires`). ``` Cache-Control: max-age=60, s-maxage=86400 ``` Browser: fresh for 60 seconds. CDN: fresh for a day. Behind that, a deploy or a content change is handled by purging the edge, and stale browser copies age out within a minute. A second, less-known effect: like `public` and `must-revalidate`, the presence of `s-maxage` also authorises a shared cache to store a response that came from a request bearing an `Authorization` header. Do not sprinkle it onto authenticated per-user routes. ## proxy-revalidate `must-revalidate` forbids any cache from serving a stale entry without checking with the origin first - it turns "stale but usable in an emergency" into "must be validated". `proxy-revalidate` is the same instruction scoped to shared caches only, letting a browser be lenient while the edge stays strict. In practice it is used where serving a slightly stale shared copy to *other* users is the unacceptable case: pricing, quotas, availability. ## Targeting the CDN specifically `s-maxage` says "shared cache", but a corporate proxy is also a shared cache, and increasingly you want to say "the CDN, and only the CDN". Two headers do that. **`CDN-Cache-Control`** is the standardised targeted-header form: only a CDN reads it, and for a CDN it overrides `Cache-Control` entirely. **`Surrogate-Control`** is the older Edge Architecture convention (`Surrogate-Control: max-age=600`), supported by most CDNs and by Varnish-family caches. A surrogate that acts on it is expected to **remove it from the response** it forwards, so it never confuses the browser or an intermediate cache. `CDN-Cache-Control` is likewise not meant to be acted on by browsers. A CDN resolving a response picks the most specific directive set it understands: its own vendor-prefixed header if present, then `CDN-Cache-Control`, then `Surrogate-Control`, then plain `Cache-Control`. Exact ordering is vendor-specific, so verify with your provider rather than assuming. The result is three independently tunable tiers: ``` Cache-Control: max-age=0, must-revalidate CDN-Cache-Control: max-age=600 ``` Browsers revalidate on every use; the CDN serves from its own copy for ten minutes and absorbs the traffic. ## Practical guidance - Default shape for public content: small `max-age`, large `s-maxage` or `CDN-Cache-Control`, and a purge on publish. - For immutable, content-hashed assets the calculus inverts - a year-long `max-age` plus `immutable` is correct precisely because the URL changes when the content does. - Never rely on browser TTL as your rollback mechanism; there is none. Rely on the edge, which you can purge. - Verify what actually reached each tier. Requesting through the CDN and directly at the origin, and comparing the returned `Cache-Control`, `Age` and any vendor cache-status header, tells you which tier answered and under which lifetime.
- A response carries Cache-Control: max-age=60, s-maxage=86400. How long does a browser consider it fresh, and how long does a CDN?The browser uses max-age and treats it as fresh for 60 seconds, because s-maxage is defined only for shared caches. The CDN uses s-maxage, overriding max-age, and treats it as fresh for 86400 seconds. The asymmetry is deliberate: the edge copy can be purged on demand, the browser copies cannot.
- Why prefer CDN-Cache-Control over s-maxage when you have both available?s-maxage speaks to every shared cache, including corporate proxies and any reverse proxy in the path, so you cannot tune the CDN separately from them. CDN-Cache-Control is read only by CDNs and overrides Cache-Control for them, giving genuine three-tier control. Keep Cache-Control as the correct fallback for caches that do not understand the targeted header.
- What does proxy-revalidate add over must-revalidate?must-revalidate binds every cache: none may serve a stale entry without revalidating with the origin. proxy-revalidate applies the same rule to shared caches only, leaving private caches free to serve stale content under the usual rules. It is the right choice when stale data being served to other users is the specific risk.
saying these in an interview costs you the question
- Thinking browsers honour s-maxage
- Believing s-maxage replaces max-age everywhere rather than only in shared caches
- Using a long browser max-age and expecting a CDN purge to fix a bad deploy for existing visitors
- Assuming Surrogate-Control is forwarded to the browser, when honouring surrogates strip it
- Adding s-maxage to authenticated per-user routes without realising it also authorises shared storage of credentialed responses