TATECHATLAS
◎ English
Web & APIs

Understanding Cache-Control Headers for Public and Personalized Responses

This document explains the Cache-Control HTTP headers 'no-store', 'no-cache', and 'private', detailing their distinctions in controlling caching behavior for public and personalized responses, along with browser diagnostics and common pitfalls.

On this page

Cache-Control headers are crucial for managing how web browsers and intermediate caches (like CDNs) handle web resources. These headers instruct caches on whether to store, validate, or prevent the storage of responses. Let's break down the three key directives: 'no-store', 'no-cache', and 'private'. 'no-store' is the strictest. It prohibits *any* caching, including private caches. This is essential for highly sensitive data or one-time responses. Example: Cache-Control: no-store. 'no-cache' allows caching but mandates that the cache *always* revalidate the response with the origin server before using it. This ensures the browser receives the latest version, even if the cache has a stale copy. Example: Cache-Control: no-cache. 'private' restricts caching to only private caches - those associated with a specific user, like a browser's local cache. This prevents shared caches from serving personalized content to other users. Example: Cache-Control: private. When dealing with personalized responses, 'private' is paramount. Consider a user logging into a website; their response should only be stored in their browser, not a shared CDN. If a response contains personalized data, using 'private' is vital to prevent information leakage. The MDN documentation highlights that ‘no-cache’ does not mean “don’t cache”. It means “always validate before reuse”. The 'no-cache' directive doesn't guarantee revalidation for history navigations. Furthermore, the 'max-age' directive specifies how long a response is considered fresh. A value of 0 means the response should always be revalidated. The 'immutable' directive prevents caching, even if max-age is set, ensuring the browser always requests the latest version of the resource. When inspecting a browser's request headers, you'll see Cache-Control directives influencing the caching behavior. For example, a request for a public asset might have Cache-Control: public max-age=3600, indicating it can be cached for an hour. A request for a private profile might have Cache-Control: private no-cache, ensuring only the user's browser can store it and requires validation before reuse. Browser diagnostics can reveal whether a response is being cached and how. Tools like the browser's developer tools allow you to monitor network requests and examine the Cache-Control headers. Common mistakes include forgetting to set 'private' for personalized responses, leading to potential data leakage. Another mistake is relying solely on 'no-store' when a more nuanced approach is needed - sometimes, allowing caching with validation is preferable. The decision criteria should be based on the sensitivity of the data and the desired caching behavior. Finally, remember that Cache-Control is not an authorization boundary; 'no-cache' doesn't mean 'no storage'. The scope limits are determined by the specific headers used and the caching policies implemented by browsers and intermediate caches.

Separate storing from reusing

Caching is a fundamental technique for improving web performance, but it's crucial to understand how it interacts with data sensitivity. The core concept is to distinguish between storing a response and reusing a stored response. A cache can store a response, but it must validate it before reusing it. 'no-store' prevents both storing and reusing, ensuring that the response never leaves the client's browser. 'no-cache' allows storing but mandates validation before reuse, guaranteeing the browser always receives the latest version. 'private' restricts storage to private caches, preventing shared caches from serving personalized content.

Consider a scenario where a user logs in. The response containing their personalized profile data should be treated as private. Without 'private', a shared cache could serve this data to other users, compromising their privacy. The MDN documentation emphasizes that ‘no-cache’ does not mean “don’t cache”. It means “always validate before reuse.”

Understand no-store

The Cache-Control: no-store directive is the most restrictive. It instructs all caches - both private and shared - to *not* store the response at all. This is the appropriate choice for highly sensitive data, one-time responses, or content that should never be reused. It effectively disables caching for the specified resource.

Example: Cache-Control: no-store would prevent any cache from storing the response, regardless of whether it's a browser cache or a CDN cache. This is critical for preventing unauthorized access to sensitive information.

Understand no-cache

The Cache-Control: no-cache directive allows caching, but it *always* requires the cache to revalidate the response with the origin server before using it. This ensures that the browser receives the latest version of the resource, even if the cache has a stale copy. It’s a balance between performance and data freshness.

This directive doesn't mean ‘don’t cache’; it means ‘always validate before reuse.’ The browser will always make a request to the server to verify that the cached copy is still valid. This is particularly useful for resources that might change frequently, such as dynamic content.

Understand private

The Cache-Control: private directive restricts caching to only private caches - those associated with a specific user, like a browser's local cache. This prevents shared caches from serving personalized content to other users. It’s essential for protecting user privacy.

When a response contains personalized data, using 'private' is crucial. Without it, a shared cache could serve the same response to multiple users, potentially exposing their personal information. The MDN documentation notes that ‘private’ is particularly important for responses received after login.

Compare three response policies

Here's a table summarizing the key differences between the three directives:

| Directive | Storage | Validation | Scope |

| --------- | ------- | ---------- | ----------- |

| no-store | No | N/A | All caches |

| no-cache | Yes | Always | All caches |

| private | Yes | Always | Private caches |

Inspect a browser request

Using your browser's developer tools (usually accessed by pressing F12), you can inspect the Cache-Control headers of network requests. Look for the 'Headers' tab in the Network panel. This will show you the exact headers being sent by the browser, including the Cache-Control directives.

For example, if you're viewing a page with a personalized profile, you should see Cache-Control: private no-cache in the request headers. This indicates that the browser is requesting that the response be stored only in its local cache and that it must be validated with the server before reuse.

Handle personalized responses

Personalized responses - those containing user-specific data like login information, preferences, or shopping cart contents - require careful consideration of Cache-Control headers. Always use Cache-Control: private to prevent shared caches from serving this data to other users.

Failing to do so can lead to serious privacy breaches and security vulnerabilities. The MDN documentation explicitly recommends using 'private' for user-personalized content.

Recognize eviction and legacy limits

It's important to recognize that even with 'no-cache', a browser's history cache (bfcache) can still serve a cached response without revalidation. This is a legacy behavior designed for restoring previous sessions, and it's not controlled by the Cache-Control header.

Additionally, older HTTP/1.0 caches might not fully support the 'no-cache' directive, potentially leading to stale responses being reused. Using 'max-age=0, must-revalidate' as a workaround can address this issue, but it's best practice to use 'no-cache' whenever possible.

Things to check

  • Cache behavior depends on request/response and intermediaries.
  • Cache-Control is not an authorization boundary; no-cache does not mean no storage.
  • The 'private' directive prevents shared caching of personalized content.
  • The 'no-store' directive prevents all caching.
  • The 'no-cache' directive mandates validation before reuse.
  • Browser developer tools can be used to inspect Cache-Control headers.
  • Understanding the difference between private and shared caches is crucial.
  • Legacy caching behavior (bfcache) can bypass Cache-Control directives.
  • HTTP/1.0 caches may not fully support 'no-cache'.

These headers provide a mechanism for controlling caching behavior, but they do not guarantee complete protection against caching or information leakage. Browser implementations and intermediary caches may behave differently, and users can override caching settings. Furthermore, the 'no-cache' directive doesn't prevent a browser's history cache from serving a stale response.

Sources

  1. MDN: Cache-Control directives ↗
  2. MDN: HTTP caching guide ↗
Back to top ↑