HTTP Cache-Control tells browsers, shared proxies, and content delivery networks how they may store and reuse a response. Correct caching reduces latency, bandwidth, and origin load. Incorrect caching can expose private information or leave users with stale content.
Cache-Control uses one or more directives in request or response headers. A cache also considers the request method, response status, validators, the Vary header, and other HTTP caching rules.
Caching is specific to a response and its representation. Do not apply one global policy to HTML pages, versioned assets, APIs, and personalized account data.
Fresh and Stale Responses
A fresh cached response can normally be reused without contacting the origin. When it becomes stale, the cache must revalidate it or fetch a new response unless another directive permits stale reuse.
Example response:
# Store this response for one hour
HTTP/1.1 200 OK
Content-Type: text/css
Cache-Control: public, max-age=3600
Date: Thu, 13 Aug 2026 06:00:00 GMT
max-age is the number of seconds after the response was generated. An Age response header from a shared cache indicates how long the stored response has already spent in caches.
Common Response Directives
| Directive | Meaning |
|---|---|
| max-age=N | Response stays fresh for N seconds |
| s-maxage=N | Freshness for shared caches, overriding max-age there |
| public | Shared caches may store the response |
| private | Only a private cache may store it |
| no-cache | Store it, but validate before reuse |
| no-store | Do not store the response |
| must-revalidate | Do not reuse a stale response without validation |
| immutable | Fresh content will not change at its URL |
no-cache and no-store Are Different
no-cache permits storage but requires successful validation before reuse. no-store instructs caches not to store the response. Use no-store for highly sensitive responses, while no-cache works well when a reusable representation must always be checked.
# Store the page but revalidate it on every reuse
Cache-Control: no-cache
# Do not store sensitive account data
Cache-Control: no-store
Private and Shared Caches
A browser cache is private to one user. A CDN or forward proxy is shared by many users. Mark personalized responses private unless they are already uncacheable under other rules. Use s-maxage when a CDN needs a different freshness period from the browser.
# Browser: 60 seconds; shared cache: 10 minutes
Cache-Control: public, max-age=60, s-maxage=600
Revalidate with ETag
An ETag identifies a representation version. When a stale response has an ETag, the client sends If-None-Match. A matching server version returns 304 Not Modified without a response body.
# Initial response includes a validator
HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "article-91f3"
Content-Type: text/html
# Later request asks whether that representation changed
GET /guide HTTP/1.1
If-None-Match: "article-91f3"
# Matching validator reuses the cached body
HTTP/1.1 304 Not Modified
ETag: "article-91f3"
A strong ETag represents byte-for-byte equality. A weak ETag starts with W/ and represents semantic equivalence, which is less suitable for range operations.
Use Last-Modified When Appropriate
Last-Modified works with If-Modified-Since. It is easier to generate but has lower precision than ETag. If a conditional request supplies both relevant validators, If-None-Match takes precedence for cache validation.
# Date-based validator for a static resource
Last-Modified: Wed, 12 Aug 2026 14:30:00 GMT
Cache-Control: no-cache
Cache Versioned Static Assets
Assets with content hashes in their filenames can use a long freshness lifetime because a changed file receives a new URL. immutable tells caches that the response will not change while it is fresh.
# Safe for a content-hashed asset URL
Cache-Control: public, max-age=31536000, immutable
Do not apply this policy to an unversioned URL whose content may change. Otherwise clients can keep the old representation for the full lifetime.
Use Vary Correctly
Vary adds selected request headers to the cache key. A response that changes by content encoding should normally vary on Accept-Encoding. Language-specific responses may vary on Accept-Language.
# Keep compressed and language-specific variants separate
Vary: Accept-Encoding, Accept-Language
Cache-Control: public, max-age=3600
Avoid varying on high-cardinality values such as the complete User-Agent header unless necessary, because it can reduce cache reuse dramatically.
Permit Controlled Stale Reuse
stale-while-revalidate allows a cache to serve a stale response while it updates it in the background. stale-if-error allows stale reuse when the origin fails. Support and policy vary across intermediaries, so monitor actual behavior.
# Stay fresh for five minutes, then revalidate in the background
Cache-Control: public, max-age=300, stale-while-revalidate=60, stale-if-error=86400
Choose Policies by Resource Type
- Versioned CSS, JavaScript, and images: long max-age with immutable.
- Frequently changing public pages: short freshness plus validators.
- Personalized pages: private with a deliberate freshness or revalidation policy.
- Sensitive responses: no-store when storage itself is unacceptable.
- Public API data: explicit freshness, validators, and correct Vary fields.
Test Caching Behavior
Inspect response headers in browser developer tools and with an HTTP client. Verify the initial status, cached reuse, conditional request, 304 response, Age value, and CDN cache-status headers where available.
# Inspect response and cache headers
curl -I https://example.com/assets/app.91f3.css
# Send a conditional request with a known ETag
curl -I -H 'If-None-Match: "article-91f3"' https://example.com/guide
Conclusion
HTTP Cache-Control works best as a resource-specific policy. Set explicit freshness, distinguish private and shared storage, use validators for efficient revalidation, vary only on required request headers, and test the complete request-response flow. Correct caching makes sites faster without sacrificing content accuracy or privacy.