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.
| Directive | Applies to | Effect |
|---|---|---|
| max-age=N | All caches | Fresh for N seconds after generation |
| s-maxage=N | Shared caches only | Overrides max-age for CDNs and proxies |
| no-cache | All caches | May store, but must revalidate before every use |
| no-store | All caches | Must not store the response at all |
| private | Shared caches | Only the browser cache may store it |
| public | All caches | Cacheable even when normally restricted, such as with authorization |
| immutable | Browsers | Do not revalidate while fresh, even on reload |
| must-revalidate | All caches | Never serve stale without successful revalidation |
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.
| Directive | Behavior | Typical value | Benefit |
|---|---|---|---|
| stale-while-revalidate=N | Serve stale copy while refreshing in the background | 30 to 86400 s | No user waits for revalidation |
| stale-if-error=N | Serve stale copy if the origin fails or returns 5xx | 3600 to 604800 s | Site stays up during outages |
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.
| Resource | Example header value | Reason |
|---|---|---|
| Fingerprinted CSS, JS, fonts | public, max-age=31536000, immutable | URL changes with every release |
| Images without versioning | public, max-age=86400 | Balance freshness and offload |
| HTML pages | public, max-age=0, s-maxage=300, stale-while-revalidate=60 | Edge caches briefly, browsers revalidate |
| Logged-in pages and account APIs | private, no-cache | Never stored in shared caches |
| Sensitive data such as banking | no-store | No copy on disk anywhere |
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.
| Mistake | Consequence | Correction |
|---|---|---|
| no-cache used to forbid storage | Copies are still stored | Use no-store |
| Missing private on user pages | Data leaks through shared caches | Add private or no-store |
| Long max-age on HTML | Users see outdated pages after deploys | Short max-age plus revalidation |
| No header at all | Caches apply heuristic lifetimes | Set explicit directives |
| Vary missing for negotiated content | Wrong language or encoding served | Add a matching Vary header |
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.