Skip to main content

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​

DirectiveWhat It DoesUse Case
max-ageDefines freshness lifetimeStatic assets
s-maxageShared cache lifetimeCDNs
publicAllows all caches to storeFonts, images
privateOnly browser may storeUser-specific HTML
no-storeProhibits all cachingSensitive data
no-cacheRequires revalidationDynamic HTML
must-revalidatePrevents serving stale dataFinancial or critical content
immutableGuarantees content never changesVersioned static files
stale-while-revalidateServes stale while updatingHigh-performance caching
stale-if-errorServes stale on failuresResilient 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.