Cache-Control
Browser and CDN caching behavior is controlled through the Cache-Control header. Both requests and responses carry directives that determine how browsers, proxies, and CDNs store and serve cached responses across the entire delivery chain.
Usage
Origins, intermediaries, and clients all rely on Cache-Control to agree on when a stored response remains usable and when a fresh copy is needed. A single response often combines several directives to express a complete caching policy: how long to store, who is allowed to store, and what to do once freshness expires.
The table below shows which directives apply to requests, responses, or both.
| Directive | Request | Response |
|---|---|---|
| max-age | X | X |
| max-stale | X | |
| min-fresh | X | |
| s-maxage | X | |
| no-cache | X | X |
| no-store | X | X |
| no-transform | X | X |
| only-if-cached | X | |
| must-revalidate | X | |
| proxy-revalidate | X | |
| must-understand | X | |
| private | X | |
| public | X | |
| immutable | X | |
| stale-while-revalidate | X | |
| stale-if-error | X | X |
Multiple directives are separated by commas in a single header value or split across multiple Cache-Control headers. When conflicting directives appear, the most restrictive combination applies.
Note
Unrecognized directives are ignored by caches. This allows new directives to be introduced without breaking older implementations.
Note
When no Cache-Control header is present, caches apply heuristic freshness. A common heuristic is 10% of the time since Last-Modified. This means responses without Cache-Control are cached by browsers and CDNs. Explicitly setting Cache-Control prevents unexpected caching behavior.
Note
Cache-Control governs when to revalidate
(freshness lifetime). ETag governs
how to revalidate (content identity). Both
work together: Cache-Control determines if
revalidation is needed, ETag determines
if the content changed. The Pragma
header is an HTTP/1.0 equivalent of
Cache-Control: no-cache and is effectively
deprecated.
Request directives
max-age
The max-age directive tells caches to return a stored
response only if the response is no older than the
specified number of seconds. Invalid values such as
negative numbers are treated as 0.
Cache-Control: max-age=<seconds>
Setting max-age=0 forces end-to-end revalidation.
This pattern originated in HTTP/1.0
implementations lacking no-cache support.
max-stale
The max-stale directive signals acceptance of a
response whose Age has exceeded the freshness
lifetime by up to the given number of seconds. The
tolerance window starts once max-age expires.
Cache-Control: max-stale=<seconds>
This directive is useful when an origin server is temporarily unreachable and a slightly stale response is acceptable.
min-fresh
The min-fresh directive requests a stored response
whose remaining freshness lifetime is at least the
specified number of seconds. The cache returns the
stored copy only if the response will stay fresh for
the additional period.
Cache-Control: min-fresh=<seconds>
no-cache
A request carrying no-cache requires the cache to
validate the stored response with the
origin before serving the copy. This
forces revalidation without discarding the stored
entry.
Cache-Control: no-cache
Note
To prevent a cache from storing a response at
all, use no-store instead.
no-store
The no-store directive asks caches not to store
the request or the corresponding response.
Cache-Control: no-store
no-transform
The no-transform directive forbids intermediaries
from modifying the response body, such as recompressing
images or converting media formats. The restriction
applies whether the intermediary is a
Caching proxy or a forwarding gateway.
Cache-Control: no-transform
only-if-cached
The only-if-cached directive tells the cache to
return a stored response without contacting the origin.
If no suitable stored response exists, the cache
returns a 504 status.
Cache-Control: only-if-cached
Response directives
max-age
The max-age directive declares the number of seconds
the response remains fresh after generation. The timer
starts from the moment the origin creates the response,
so transit time and time spent in intermediate caches
count against the budget.
Cache-Control: max-age=<seconds>
A response with max-age=3600 stays fresh for one
hour. After the hour elapses, caches treat the
response as stale and either revalidate or fetch a
new copy depending on other directives present.
s-maxage
The s-maxage (shared max-age) directive overrides
max-age for shared caches such as CDNs and proxy
servers. Private browser caches ignore s-maxage and
fall back to max-age.
Cache-Control: s-maxage=<seconds>
no-cache
The response no-cache directive requires caches to
revalidate the stored response with the
origin before every reuse. The cache
still stores the response, enabling
conditional requests with
ETag or Last-Modified.
Cache-Control: no-cache
When no-cache includes a list of field names,
only those specific headers require revalidation.
The rest of the stored response is served without
contacting the origin.
Cache-Control: no-cache="Set-Cookie"
Note
To prevent storage entirely, use no-store.
no-store
The no-store directive prevents any cache from
storing the response. Every subsequent request goes
to the origin.
Cache-Control: no-store
Note
no-cache allows storage but requires
revalidation before every use. The cache checks
with the origin via
If-None-Match or
If-Modified-Since and
receives a 304 if the content is unchanged.
no-store prevents storage entirely, so no copy
is kept and every request fetches the full
response. A common mistake is using no-cache
when no-store is intended for sensitive data
such as banking or medical records. no-cache
is efficient for frequently updated content.
no-store is for content that must never persist
on disk.
no-transform
The response no-transform directive prevents
intermediaries from altering the response body before
forwarding, whether the intermediary caches the
content or not.
Cache-Control: no-transform
must-revalidate
The must-revalidate directive allows caches to serve
the response while fresh. Once stale, the cache
contacts the origin to revalidate before serving the
response again. If the origin is unreachable, the
cache returns a 504 instead of serving stale
content.
Cache-Control: must-revalidate
proxy-revalidate
The proxy-revalidate directive works identically to
must-revalidate, except the requirement applies only
to shared caches. Private browser caches are not
affected.
Cache-Control: proxy-revalidate
must-understand
The must-understand directive instructs a cache to
store the response only when the cache recognizes the
status code and understands the associated caching
requirements.
Cache-Control: must-understand, no-store
Pairing must-understand with no-store provides a
fallback: caches lacking support for must-understand
ignore the unknown directive and honor no-store
instead.
private
The private directive restricts storage to private
caches, typically the end user's browser. Shared
caches such as CDNs and proxy servers discard the
response.
Cache-Control: private
public
The public directive marks a response as eligible
for storage in shared caches. Responses carrying an
Authorization header are not stored
by shared caches unless the public directive is
present.
Cache-Control: public
Note
The public directive is unnecessary when
must-revalidate or s-maxage is already present.
immutable
The immutable directive guarantees the response body
will not change during the freshness lifetime. Caches
skip conditional requests for
immutable resources, eliminating revalidation round
trips for assets like versioned JavaScript bundles or
fingerprinted images.
Cache-Control: public, max-age=31536000, immutable
stale-while-revalidate
The stale-while-revalidate directive extends the
usability window of a stale response. After freshness
expires, caches serve the stale copy while
revalidating in the background. The parameter defines
how many additional seconds the stale response remains
acceptable.
Cache-Control: max-age=600, stale-while-revalidate=30
Once the background revalidation completes, the cache replaces the stale entry with the fresh response. This pattern hides revalidation latency from end users.
stale-if-error
The stale-if-error directive allows caches to serve
a stale response when the origin returns an error
status (500, 502, 503, or 504) or is
unreachable. The parameter sets the number of seconds
beyond the freshness lifetime during which the stale
response remains usable as a fallback.
Cache-Control: max-age=600, stale-if-error=86400
Example
A static asset served with a long freshness lifetime and the immutable flag. The browser and any shared cache store this response for one year without revalidation.
Cache-Control: public, max-age=31536000, immutable
An API response intended only for the requesting browser. The cache stores the response for five minutes and revalidates before reuse once stale.
Cache-Control: private, max-age=300, must-revalidate
A CDN-targeted policy giving the shared cache a ten-minute window while the browser cache gets one minute. Stale responses are served for up to 30 seconds while the CDN revalidates in the background.
Cache-Control: s-maxage=600, max-age=60, stale-while-revalidate=30
Googlebot and max-age
Google's crawling infrastructure implements
heuristic HTTP caching.
The max-age directive helps Googlebot
determine recrawl frequency. A page with a long
max-age is re-fetched less often, while
no-cache signals frequent content changes and
warrants more frequent crawling. Responses
without any cache headers are heuristically
cacheable by default for certain status codes,
so origins wanting to prevent caching entirely
need to send no-store explicitly. Bingbot
sends Cache-Control: no-cache on requests
and relies on crawl-delay in robots.txt and
IndexNow for crawl pacing.
Note
For SEO and caching assistance, contact ex-Google SEO consultants Search Brothers.
Troubleshooting
Unexpected caching behavior stems from directive misunderstandings, CDN overrides, or conflicting headers.
Stale content served despite
no-cachedirective. Theno-cachedirective does not prevent storage. Caches store the response and revalidate on every reuse. To prevent storage entirely, useno-store. Combine both when sensitive data is involved:Cache-Control: no-cache, no-store.CDN ignores Cache-Control directives. Many CDNs override origin Cache-Control with their own policies. Cloudflare respects origin headers by default but overrides them when a Page Rule or Cache Rule is active. AWS CloudFront uses the origin header unless a cache policy with custom TTLs is attached. Check the CDN dashboard for override rules before debugging the origin configuration.
Stale content persists after lowering
max-age. CDN edge nodes and browser caches retain responses until the originalmax-ageexpires. Lowering the value on the origin does not purge already-cached copies. Issue a CDN purge after changing cache lifetimes. In Cloudflare, purge by URL or use a full zone purge. In CloudFront, create an invalidation for the affected paths.Browser shows old page on back/forward navigation despite
no-store. The back/forward cache (bfcache) in Chrome, Firefox, and Safari restores the full page state from memory, bypassingno-store. This behavior is by design and does not represent a cache violation. Inspect theCache-Controlheader in DevTools Network tab with "Disable cache" unchecked to confirm the header reaches the browser. Addingunloadevent listeners disables bfcache in some browsers but degrades performance.Expires header conflicts with Cache-Control. When both headers are present,
Cache-Control: max-agetakes precedence overExpires. Remove theExpiresheader to avoid confusion. In nginx:expires off; add_header Cache-Control "max-age=3600";In Apache:
Header unset Expires Header set Cache-Control "max-age=3600"Diagnosing cache headers with curl and DevTools. Run
curl -I https://example.reto inspect response headers from the origin. Add-H "Cache-Control: no-cache"to bypass CDN caches and hit the origin directly. In browser DevTools, open the Network tab, select the request, and check the Response Headers section. Look forAge,X-Cache, andCF-Cache-Statusheaders to determine whether the response came from a CDN edge node or the origin.
See also
- RFC 9111: HTTP Caching
- RFC 5861: Cache-Control Extensions for Stale Content
- Google: HTTP caching for crawlers
- Google Search Blog: Crawling December, HTTP caching
- Caching
- Conditional requests
- ETag
- Last-Modified
- Expires
- Age
- Vary
- HTTP headers