Cache-Control

Also known as: Cache-Control header

On this page 7

Cache-Control is the HTTP header that carries caching directives, instructions telling browsers, CDNs, reverse proxies and other caches whether a response may be stored, for how long it stays fresh and what to do once it becomes stale. In content delivery, it is the main control surface an origin server uses to decide how much traffic edge caches absorb and how quickly changes reach users.

The header is defined in RFC 9111, the HTTP caching specification, and appears in both responses and requests. Response directives such as max-age, s-maxage, no-store and private set policy for stored copies, while request directives such as no-cache let a client ask for a fresh answer. A well-designed Cache-Control strategy can serve most static traffic from the edge without contacting the origin server at all.

How Cache-Control directives govern freshness

HTTP caching rests on the concept of freshness. A stored response is fresh while its age is below its freshness lifetime, and a cache may serve a fresh copy without asking the origin. Cache-Control sets that lifetime explicitly, taking precedence over the older Expires header.

Once a response is stale, the cache normally revalidates it by sending a conditional request with If-None-Match or If-Modified-Since. If the resource has not changed, the origin answers 304 Not Modified without a body, which saves bandwidth even though it still costs a round trip.

The table below lists the response directives that matter most in practice.

DirectiveApplies toEffect
max-age=NAll cachesFresh for N seconds after generation
s-maxage=NShared caches onlyOverrides max-age for CDNs and proxies
no-cacheAll cachesMay store, but must revalidate before every use
no-storeAll cachesMust not store the response at all
privateShared cachesOnly the browser cache may store it
publicAll cachesCacheable even when normally restricted, such as with authorization
immutableBrowsersDo not revalidate while fresh, even on reload
must-revalidateAll cachesNever serve stale without successful revalidation
Key Cache-Control response directives

When a response has no explicit lifetime, caches may apply heuristic freshness, commonly about 10 percent of the time since the Last-Modified date. The legacy Expires header and the request header Pragma: no-cache from HTTP/1.0 still appear in the wild, but Cache-Control overrides both wherever it is present, so modern configurations should rely on it alone.

Shared caches, CDNs and the s-maxage directive

HTTP distinguishes private caches, which serve a single user such as a browser, from shared caches, which serve many users, such as a CDN edge or corporate proxy. Directives can target them separately, so an origin can let the CDN keep a page for an hour while telling browsers to check again after a minute.

A header such as Cache-Control: public, max-age=60, s-maxage=3600 expresses exactly this split. Many CDNs also support CDN-specific headers such as CDN-Cache-Control or Surrogate-Control, which are consumed by the edge and stripped before the response reaches the browser, giving even finer control.

Personalized responses require particular care. Any page that contains account data must carry private or no-store, otherwise a shared cache may serve one user’s data to another, one of the most serious caching failures in practice.

Stale-while-revalidate and stale-if-error

Two extension directives from RFC 5861 let caches trade a small amount of staleness for speed and resilience. They are widely supported by CDNs and, for stale-while-revalidate, by modern browsers.

DirectiveBehaviorTypical valueBenefit
stale-while-revalidate=NServe stale copy while refreshing in the background30 to 86400 sNo user waits for revalidation
stale-if-error=NServe stale copy if the origin fails or returns 5xx3600 to 604800 sSite stays up during outages
Stale-serving extensions

Combining a short max-age with stale-while-revalidate is a common pattern for HTML pages and API responses: content stays nearly current, yet users almost always receive an instant cached answer.

Choosing Cache-Control policies by content type

There is no single correct value; the right policy follows from how the content changes and how its URL is built. The decisive question is whether a new version gets a new URL. Assets with a content hash in the file name never change at their address, so they can be cached for a year.

ResourceExample header valueReason
Fingerprinted CSS, JS, fontspublic, max-age=31536000, immutableURL changes with every release
Images without versioningpublic, max-age=86400Balance freshness and offload
HTML pagespublic, max-age=0, s-maxage=300, stale-while-revalidate=60Edge caches briefly, browsers revalidate
Logged-in pages and account APIsprivate, no-cacheNever stored in shared caches
Sensitive data such as bankingno-storeNo copy on disk anywhere
Recommended Cache-Control values by resource

Long lifetimes on unversioned URLs are risky: once a browser has a copy for a year, a cache purge on the CDN cannot remove it from the user’s device. Versioned file names are the only reliable way to make long caching safe.

Common Cache-Control mistakes

Many caching bugs come from misreading directive names, which are not intuitive. The table summarizes frequent errors and their fixes.

MistakeConsequenceCorrection
no-cache used to forbid storageCopies are still storedUse no-store
Missing private on user pagesData leaks through shared cachesAdd private or no-store
Long max-age on HTMLUsers see outdated pages after deploysShort max-age plus revalidation
No header at allCaches apply heuristic lifetimesSet explicit directives
Vary missing for negotiated contentWrong language or encoding servedAdd a matching Vary header
Cache-Control errors and corrections

Measurement completes the work: the cache hit ratio at the CDN and the Age and cache status headers in responses show whether the configured directives actually produce the intended behavior.

The short version

Every file a website sends can come with a small label saying how long it may be kept. Cache-Control is that label. A browser or a delivery network reads it and decides whether to reuse a saved copy or fetch a new one.

Some labels say keep this for a year, which suits logos and code files that never change. Others say always check first, which suits news pages. Some say never keep this, which protects private information. Choosing good labels makes sites faster and cheaper to run.

Where it makes a difference

A news website sees traffic spike tenfold whenever a major story breaks, and its origin servers struggle. The team audits its headers and finds that article pages carry no-cache while script files have no Cache-Control at all. They change article HTML to public, max-age=0, s-maxage=120, stale-while-revalidate=60, stale-if-error=86400 and rename static assets with content hashes served as immutable for one year.

During the next breaking story, the CDN answers almost all article requests from the edge, refreshing each page at most every two minutes in the background. When the origin briefly fails under a database problem, readers keep receiving the last good version thanks to stale-if-error. Origin load drops sharply, and editors still see updates live within about two minutes.