Cache Control
The Cache-Control header is the core mechanism that defines how HTTP caching behaves across browsers, CDNs, proxies, and any intermediate cache that may handle a response. It provides a flexible, modern way to specify freshness, validation rules, privacy restrictions, and storage behavior.
Because of its expressiveness, Cache-Control has replaced the older Expires header in modern HTTP specifications (RFC 9111). By combining multiple directives, you create a caching policy that shapes exactly how a resource should be reused or revalidated. A typical example looks like this:
Cache-Control: public, max-age=31536000, immutable
In this case, the resource is allowed to be cached by any cache, stays fresh for one year, and is treated as an unchanging file that does not require revalidation.
max-ageβ
Cache-Control: max-age=86400
The max-age directive determines how long a response is considered fresh. During this period, the browser can reuse the cached response without contacting the server. Setting max-age=86400 makes the resource fresh for 24 hours. Once this period ends, the resource becomes stale and must either be revalidated or refetched.
s-maxageβ
Cache-Control: public, s-maxage=600
The s-maxage directive behaves like max-age but applies exclusively to shared caches, such as CDNs and reverse proxies. Browsers ignore it. When present, shared caches may reuse the response for the specified duration even when the browser must revalidate sooner. This separation allows servers to tune caching independently for private and shared environments.
publicβ
Cache-Control: public, max-age=31536000
The public directive explicitly states that the response can be stored by any caching layer, including browsers, proxies, and CDNs. This is ideal for static assets that are the same for all users, such as fonts, images, and hashed JavaScript bundles.
privateβ
Cache-Control: private, max-age=600
The private directive restricts caching to the userβs browser. Shared caches must not store the response. This ensures user-specific or personalized content β such as dashboards or authenticated HTML pages β will never be reused for other users. The browser, however, can still store it according to the defined max-age.
no-storeβ
Cache-Control: no-store
The no-store directive is the strictest possible caching policy. It prevents all caches β browsers, CDNs, proxies, and service workers β from storing the response in any form. This is critical for highly sensitive information such as banking data, medical records, or OAuth authentication flows, where even temporary storage poses a security risk.
no-cacheβ
Cache-Control: no-cache
Despite its name, no-cache does not prohibit caching. Instead, it instructs the browser to always revalidate the cached response before using it. The browser may store the resource, but it cannot rely on the stored version until it receives confirmation from the server, usually via an ETag or Last-Modified header. This makes no-cache an excellent choice for dynamic HTML where correctness matters more than raw performance.
must-revalidateβ
Cache-Control: max-age=0, must-revalidate
The must-revalidate directive enforces strict compliance with freshness rules. When a resource becomes stale, the browser must not use it unless it successfully revalidates it. If the server cannot be reached, the browser is prohibited from serving the stale version. This directive is important when stale data could lead to incorrect or harmful outcomes, such as financial information or real-time monitoring dashboards.
immutableβ
Cache-Control: public, max-age=31536000, immutable
The immutable directive tells the browser that the resource will not change for the duration of its freshness lifetime. Because of this, the browser does not need to revalidate the file even when the user explicitly reloads the page. Versioned assets like app.84ae2.js, fonts, and static images are ideal candidates for immutable, since any change results in a new file name.
stale-while-revalidateβ
Cache-Control: max-age=600, stale-while-revalidate=30
The stale-while-revalidate directive allows the browser to serve a stale response while fetching a fresh version in the background. This improves performance by delivering instant responses even when the cached content has expired, while still ensuring that an updated version is fetched proactively. Many CDNs and modern frameworks such as Next.js make extensive use of this pattern.
stale-if-errorβ
Cache-Control: max-age=600, stale-if-error=86400
The stale-if-error directive enables the browser to serve a stale response when the server returns an error, such as a 500 or 502. This offers graceful degradation and prevents temporary server issues from breaking the user experience. The value defines how long stale content may be served during such failures.
Summary Tableβ
| Directive | What It Does | Use Case |
|---|---|---|
max-age | Defines freshness lifetime | Static assets |
s-maxage | Shared cache lifetime | CDNs |
public | Allows all caches to store | Fonts, images |
private | Only browser may store | User-specific HTML |
no-store | Prohibits all caching | Sensitive data |
no-cache | Requires revalidation | Dynamic HTML |
must-revalidate | Prevents serving stale data | Financial or critical content |
immutable | Guarantees content never changes | Versioned static files |
stale-while-revalidate | Serves stale while updating | High-performance caching |
stale-if-error | Serves stale on failures | Resilient applications |
Cache-Control is the heart of HTTP caching. It determines how long content remains reusable, when it must be revalidated, and who is permitted to store it. By carefully combining its directives, you can create caching policies that maximize performance while maintaining correctness and security. For most applications, optimizing Cache-Control provides one of the highest returns in web performance improvements.