How to use the cache-control header explainer
- Paste the value of a Cache-Control header, such as public, max-age=3600. Leave it empty if you only have Expires or Last-Modified headers.
- Choose whether you mean a shared cache such as a CDN or proxy, or a private browser cache, and enter the Age header if you have one.
- Open the extra fields to add the Date, Expires and Last-Modified headers when a response relies on them.
- Read the time left, the freshness lifetime and where it came from, the table of directives and any conflicts the page found.
Formula
lifetime = s-maxage (shared caches only), else max-age, else Expires - Date, else 10% of (Date - Last-Modified)
time left = lifetime - Age; the response is fresh while this is above zero
an invalid or negative freshness value counts as stale
These are the freshness rules of RFC 9111. Expires is ignored when max-age is present, and the heuristic is only a fallback that caches may apply differently.
Browser against CDN
With Cache-Control: public, max-age=600, s-maxage=60 a shared cache keeps the response fresh for 1 minute, because s-maxage replaces max-age there. A browser ignores s-maxage and keeps it fresh for 10 minutes.
With no Cache-Control header, a Date of Mon, 05 Oct 2026 12:00:00 GMT and an Expires of one hour later, the lifetime is 1 hour. An Age of 300 seconds leaves 55 minutes. A value such as Expires: 0 is not a valid date, so caches treat the response as already expired.
Directives that people confuse
No-cache does not mean do not cache. It lets a cache store the response but requires it to check with the origin before every reuse. No-store is the directive that forbids storing at all. Private keeps a response out of shared caches, while public allows storing even where a cache would not by default.
Must-revalidate only matters once a response is stale, and immutable only helps when a lifetime is set, which is why the checker points out those directives when no lifetime is given.
Conflicts the page looks for
It flags a no-store with a max-age, which can have no effect, public together with private, private together with s-maxage, no-cache together with immutable, a quoted or non-numeric max-age, a repeated directive, and Expires combined with max-age. It also lists directives it does not know.
The result describes the rules, not a particular product. A CDN or browser can add its own settings. Look up what a response code means with the HTTP Status Codes Reference.
Assumptions and limits
- Freshness follows RFC 9111 for a stored response. Request directives such as max-stale and min-fresh are explained but not applied.
- Dates must be in the standard HTTP format, for example Mon, 05 Oct 2026 12:00:00 GMT.
- The heuristic lifetime assumes a response that may be stored by default and is only one way a cache may choose.
- Age is the number of seconds in the Age header. The page cannot know how long a response has actually been stored.
- Nothing typed here leaves your browser.
Frequently asked questions
What is the difference between max-age and s-maxage?
Max-age sets the freshness lifetime for every cache. S-maxage sets it only for shared caches such as a CDN, and replaces max-age there, so the same response can live for different times in different places.
Why does an invalid max-age make a response stale?
A cache that cannot read the lifetime has no safe number to use, so it must treat the response as stale and ask the origin again. That is why a value such as max-age=1h does not work.
Does stale-while-revalidate extend the lifetime?
It adds a window after the response goes stale in which the cache may still serve it while it fetches a fresh copy in the background. The lifetime itself does not change.
What does the Age header add?
It says how long the response has already sat in caches along the way. The time left is the lifetime minus that age, so a response that was cached for a minute upstream has a minute less to live here.