How to Choose Between public, private, no-cache, and no-store in Cache-Control: A Cache Configuration and Troubleshooting Guide
You updated a page, but users still see the old content; after logging in, they see another user's information; the API has already returnedno-cache, yet cache records still appear in the CDN console—these problems are often attributed to 'the cache wasn't purged,' but the real cause is often a failure to understandCache-Controlwhat each directive actually controls.
public,private,no-cacheandno-storeare not four settings from 'cache the most' to 'cache the least.' They primarily answer three different questions:
Can the response be stored?
Can it be stored in the browser, or also in shared caches such as a CDN or reverse proxy?
Before a stored response is reused, must it be validated with the origin server?
The easiest rule to remember is:
public: allows browser and shared caches to store it;private: allows only the user's own private cache to store it and should not enter shared caches such as a CDN;no-cache: can be stored, but must be validated with the origin server before each reuse;no-store: no cache should store this request or response.
The most easily misunderstood of these isno-cache. It does not mean 'do not cache'; what actually means prohibit storage isno-store.
First, distinguish browser cache from CDN cache
A website behind a CDN usually has at least two layers of HTTP caching:
源站
│
│ 源站响应与Cache-Control
▼
CDN边缘节点:共享缓存
│
│ CDN返回的响应
▼
用户浏览器:私有缓存The browser cache serves only the current user and is usually called a private cache. A CDN, enterprise proxy, or reverse proxy may serve many users at once and is a shared cache.
This distinction is important. Just because a login page allows the current user's browser to store it briefly does not mean it can enter the CDN cache; otherwise, the CDN might send the first user's personalized response to other users.
According toRFC 9111: HTTP Caching,privateprohibits shared caches from storing a response, but does not prohibit private caches from storing it;publiccan explicitly allow a shared cache to store a response that might not otherwise be eligible for shared caching.
Key differences among the four directives
Directive | Browser can store | CDN can store | Validate before reuse? | Common uses |
|---|---|---|---|---|
| Yes | Yes | Determined by | Public images, CSS, JS, and public pages |
| Yes | Should not store | Determined by | User account area, personalized pages |
| Yes | Yes, unless combined with | Must validate before every reuse | Content that changes often but can be validated with ETag |
| Should not store | Should not store | No cache to reuse | Highly sensitive responses, one-time data |
Note thatpublicandprivatemainly limit 'who stores it,'no-cacheandno-storemainly limit 'how it is stored and reused.' They can therefore be combined, for example:
Cache-Control: private, no-cachemeans the browser may save the response, but must validate it with the origin server before each use; shared caches such as a CDN should not save it.
public: allows CDN caching, but does not mean a cache lifetime has been set
The following response header explicitly allows browsers and shared caches to store the content:
Cache-Control: public, max-age=3600It means the response can be stored and is considered fresh for the next 3600 seconds. During that time, the cache can usually reuse it directly without contacting the origin server.
publicSuitable for resources that are the same for all users, such as:
Images, fonts, CSS, and JavaScript that contain no user information;
Public downloadable files;
Article pages with identical content for everyone;
Public APIs that do not return different results based on cookies, authentication status, or region.
But writing only the following is usually not explicit enough:
Cache-Control: publicpubliconly explicitly grants cache eligibility and does not specify how long the content can remain fresh. Caches may use heuristics based on status codes,Last-Modifiedand other information for heuristic caching, and different browsers and CDNs may handle it differently. Production environments should usually also provide an explicitmax-ageors-maxage.
For static assets whose filenames include a content hash and whose URLs always change when updated, you can use:
Cache-Control: public, max-age=31536000, immutableFor example:
/app.83f7c21a.js
/styles.192b8d44.cssA one-year cache is suitable for resources whose URL changes whenever the content changes. If you keep using/app.jsthis fixed address but set a one-year browser cache, then after a new version is released, users can easily keep receiving the old file for a long time.
public must not be used for personalized responses you do not control
Suppose the following URL returns different content based on a login cookie:
https://www.example.com/api/profileIf the response is configured as:
Cache-Control: public, max-age=300and the CDN cache key does not correctly distinguish users, User A's profile could be cached and then returned to User B.
Do not assume that 'the CDN cache key includes cookies' by default. Different CDNs and cache rules handle cookies, query parameters, and request headers differently, and including the full cookie in the cache key can also cause cache fragmentation and a sharp drop in hit rate.
For personalized user content, the safer approach is usually to useprivateorno-store, rather than trying to put every user's response into a public CDN cache.
private: blocks shared caches, but does not mean the browser won't cache
A typical configuration is:
Cache-Control: private, max-age=300It means the response may only be stored in a private cache, such as the current user's browser cache, and can be reused directly for 300 seconds. A CDN, shared proxy, and similar shared caches should not store the response.
Content suitable for usingprivateincludes:
User account pages after login;
HTML that displays different states based on cookies;
User preferences or non-sensitive settings;
Data that is meaningful only to the current user but may be briefly reused by the browser.
If the content must be checked for updates every time, you can use:
Cache-Control: private, no-cacheThis does not completely prevent the browser from saving the response. The browser can still save the content, but it must validate it with the origin server before reuse.
According toMDN's Cache-Control documentation,privateis especially suitable for personalized responses after login or for sessions maintained by cookies; if this restriction is omitted, the response may enter a shared cache and cause personal information leakage.
However,privateis not a data security mechanism. It relies on caches complying with the HTTP specification and does not encrypt response content. Sensitive information still requires HTTPS, reliable authentication and authorization, session invalidation, and server-side access control.
no-cache: can be stored, but must be validated before use
The following configuration is often mistaken for 'completely disabling caching':
Cache-Control: no-cacheWhat it actually means is: a cache may store this response, but it cannot use it directly to satisfy subsequent requests without validation with the origin server.
The validation process usually relies on the following response headers:
ETag: "page-a81f3"Or:
Last-Modified: Wed, 23 Sep 2026 08:00:00 GMTOn the next request, the browser or CDN can send a conditional request:
If-None-Match: "page-a81f3"If the content has not changed, the origin server returns:
HTTP/1.1 304 Not ModifiedThe cache can then continue to use the original response body without downloading the full content again. If it has changed, the origin server returns a new200response and content.
This is theno-cachevalue: it ensures validation before use while retaining the transfer efficiency of a 304 response. RFC 9111 explicitly states that ano-cacheresponse without field parameters must not be used to satisfy other requests before successful validation.
Scenarios suitable forno-cacheinclude:
HTML with a fixed URL whose content may be updated at any time;
Configuration files, version manifests, or application entry files;
When you want users to check for updates on every visit but avoid retransmission when the content has not changed;
Frequently updated resources that already have correctly configured
ETagorLast-Modifiedpublic resources.
For example, a SPA can set its entry HTML to:
Cache-Control: no-cache
ETag: "index-20260924"The HTML is validated on every visit, while the hashed JS and CSS referenced by the HTML can be cached long-term.
Without a validator, no-cache may not save much traffic
If the response has noETagorLast-Modified, the cache still has to make a request to the origin, but the origin may only be able to return the full200response again.
Therefore, after settingno-cache, you should also check the validator:
curl -sS -D - -o /dev/null https://www.example.com/index.htmlCheck especially:
Cache-Control
ETag
Last-ModifiedSuppose the first response includes:
ETag: "index-20260924"You can manually verify the conditional request:
curl -sS -D - -o /dev/null \
-H 'If-None-Match: "index-20260924"' \
https://www.example.com/index.htmlWhen the content has not changed, the normal result is usually304 Not Modified. If it still returns200, check whether the application, web server, or CDN correctly handles and forwardsIf-None-Match.
no-store: does not allow cache storage, but is not a complete privacy solution
When you need to prevent browsers, CDNs, and other HTTP caches from saving a response, use:
Cache-Control: no-storeSuitable scenarios include:
Responses containing highly sensitive personal information;
Payment, account security, or one-time operation results;
Dynamically generated keys, tokens, or sensitive download URLs;
Pages that should not leave a copy in the local cache of a shared device;
Data that explicitly must be fetched anew from the server every time.
no-storeis stronger thanno-cacheThe former should not be stored; the latter can be stored but must be validated.
However,RFC 9111's description ofno-storedescription ofalso reminds us that it is not a sufficient privacy protection measure. Malicious or non-compliant caches may ignore directives, and network paths may face other risks. Sensitive content must still use HTTPS, along with server-side authentication, authorization, session invalidation, and necessary data redaction.
There is another easily overlooked issue: changing the origin response frompublictono-storedoes not guarantee that old objects already present in the CDN will disappear immediately. The old cache may continue to be used before a new request reaches the origin.
When this happens, you need to do all of the following:
Change the origin to the correct
Cache-Control;purge the corresponding URL already stored in the CDN;
if necessary, change the resource URL or version number;
check whether the browser still retains the old version;
confirm that subsequent responses include the new policy.
How to configure different types of content
Hashed CSS, JS, and images
Generate a new URL whenever the asset changes:
Cache-Control: public, max-age=31536000, immutableThis approach makes full use of browser and CDN caches. When releasing a new version, do not overwrite the old URL; instead, have the HTML reference the new filename.
HTML with a fixed URL that needs timely updates
If the version must be confirmed on every visit:
Cache-Control: no-cache
ETag: "page-version"If the browser may cache briefly and the CDN may cache somewhat longer:
Cache-Control: public, max-age=60, s-maxage=300Here,max-age=60mainly controls browser freshness time,s-maxage=300controls shared caches. Whether this policy is followed exactly also depends on the CDN rules.
Personalized pages after login
Allow the browser to save, but prohibit the CDN from saving:
Cache-Control: private, no-cacheIf the page contains sensitive information, or you do not want it saved in the HTTP cache on the user's device:
Cache-Control: no-storeDo not assume that just because a response includesSet-Cookiethe CDN will definitely not cache it. Some CDNs bypass it by default, while other cache rules may override that behavior. You must verify against your current provider's configuration.
Public APIs
When all users receive the same result under the same request conditions and brief caching is allowed:
Cache-Control: public, max-age=30, s-maxage=300If the response varies based onAccept-LanguageorOrigin, you must also design the cache key correctly and return, as appropriate,Vary:
Vary: Accept-LanguageDo not simply addpublicwhile ignoring the request headers, query parameters, and cookies that determine the response content.
User profiles and authentication APIs
Ordinary user profiles can usually use:
Cache-Control: private, no-cacheSensitive responses such as tokens, payment results, and password reset information should usually use:
Cache-Control: no-storeWhichever one you use, cache directives must not be treated as permission checks. Whether an unauthorized user can obtain data must be determined by server-side authentication and authorization logic.
Can public and private be written together with no-cache and no-store?
Some combinations have clear meaning.
public, no-cache
Cache-Control: public, no-cacheAllows browsers and shared caches to store, but requires validation before every reuse. Suitable for public content that must stay current and supports ETag validation.
private, no-cache
Cache-Control: private, no-cacheAllows only private caches to save and requires validation before every reuse. Suitable for user-specific responses that do not need local storage completely prohibited.
private, no-store
Cache-Control: private, no-storeno-storealready prohibits all cache storage, soprivateusually adds no substantive constraint. To keep the policy concise and clear, generally just use:
Cache-Control: no-storeno-cache, no-store
This is also a common compatibility form, but in modern HTTP semantics, since storage is already prohibited, validating before reuse has little practical meaning. Unless you need compatibility with specific legacy systems or frameworks, you usually do not need to combine them mechanically.
Why a CDN may still cache even when the origin returns no-cache
You cannot determine how a CDN will behave just by looking at the origin's response headers. Cache rules, edge TTL, minimum TTL, and forced caching settings in the CDN console may override the origin's policy.
For example, Cloudflare's official documentation distinguishes whether Origin Cache Control is enabled: when enabled,no-cachecan be stored but is revalidated every time; under some configurations where it is not enabled, it is not cached at all. Cache rules can also override the origin'sCache-Control.
Amazon CloudFront's official documentation also specifically warns: if the cache policy's Minimum TTL is greater than 0, even if the origin returnsno-cache,no-storeorprivate, CloudFront may still cache for at least the time specified by Minimum TTL.
Therefore, when you encounter 'the response headers clearly do not allow caching, but the CDN is still hitting,' check in the following order:
应用生成的Cache-Control
↓
Nginx、Apache或网关是否覆盖/追加响应头
↓
CDN缓存规则是否覆盖源站TTL
↓
CDN缓存键是否包含必要参数
↓
旧缓存是否已经清理
↓
浏览器是否仍保留旧副本Different CDNs use different hit response headers. You can first use CDNChart'sCDN detection toolto help confirm whether the current domain is behind a CDN and which provider it may use, then check the rules in that provider's console.
Using curl to check the actual cache policy returned
When checking a publicly accessible path, you can run:
curl -sS -D - -o /dev/null https://www.example.com/app.jsCheck especially:
Cache-Control
Age
ETag
Last-Modified
Expires
Vary
Via
X-Cache
CF-Cache-Status
Set-CookieWhere:
Cache-Controlindicates the cache directives provided by the response;Ageusually indicates the number of seconds the response has been stored in a shared cache;ETagandLast-Modifiedare used for conditional validation;Varyindicates which request headers may affect the cached version;X-Cache,CF-Cache-Statusand similar fields are provider-specific clues and are not provided by every CDN;Set-Cookiemay affect cache eligibility, but the exact behavior depends on the provider and rules.
Requesting twice in a row can reveal changes:
curl -sS -D - -o /dev/null https://www.example.com/app.js
curl -sS -D - -o /dev/null https://www.example.com/app.jsIf the second request showsHITorAgeincreases, it usually means the shared cache is reusing the object. If it is alwaysMISS,BYPASSor has noAge, the response may be uncacheable, or the CDN may simply use different debug fields.
Do not rely only oncurl -I. It sends aHEADrequest, and some applications or CDNs handleHEADandGETdifferently. The above-D - -o /dev/nullperforms a normal GET request, but does not output the response body to the terminal.
You can also use CDNChart'swebsite speed test toolto observe status codes and performance for public access from different regions. However, public speed tests can only help identify differences in caching behavior; they cannot replace CDN console logs and origin response header checks. For the platform's testing methodology, seeTesting methodology and data notes.
How to confirm that cache configuration changes have actually taken effect
After changing the cache policy, do not just refresh the browser once. It is recommended to verify at least the following:
Whether the direct origin response already returns the new
Cache-Control;whether response headers are rewritten by edge rules when accessed through the CDN;
whether existing old objects have been purged;
whether the second request for a static asset hits as expected;
whether the HTML revalidates or expires according to the configuration;
whether logged-in and logged-out states return different and correct content;
whether two different accounts could receive the same personalized cached object;
whether query parameters, cookies, and key request headers are correctly included in the cache key;
whether users can see the new version within the expected time after content changes.
'Disable cache' in browser developer tools usually affects only the current browser requests while developer tools are open and does not represent the access state of ordinary users. A hard refresh may also carry additional request cache directives. Therefore, when troubleshooting, test normal access, incognito windows, CDN nodes, and direct origin results at the same time.
Frequently asked questions
Does no-cache mean no caching at all?
No.no-cacheallows a cache to store the response but requires validation with the origin server before each reuse. If you need no HTTP cache to save it, useno-store.
After setting private, will the browser still cache?
It may.privateWhat is prohibited is shared caching by CDNs and proxies; private caches such as the browser can still save it. How long it is actually saved also depends onmax-age,Expiresorno-cacheto determine.
Can public be used without max-age?
Syntactically yes, but the cache freshness time may depend on other response headers or heuristic rules, making the behavior less explicit. Production environments should usually provide explicitmax-ageors-maxage.
Are max-age=0 and no-cache exactly the same?
In many practical scenarios, both cause the response to become stale immediately and trigger validation, but their semantics are not exactly the same.max-age=0means the response becomes stale immediately,no-cacheexplicitly requires validation before every reuse. Once the CDN's own rules are added on top, the two may still behave differently in practice.
If no-store is already set, why do users still see old content?
The old content may have been stored in the CDN or browser before the response headers were changed. The newno-storeresponse cannot guarantee that the old cache is deleted immediately; you need to purge the CDN cache, check browser copies, and change the resource URL if necessary.
Will a public response with Set-Cookie be cached by a CDN?
Not necessarily. Many CDNs do not cache responses withSet-Cookieby default, but cache rules may change this behavior. Do not rely on unverified default settings, and never force public caching for responses that contain user identity information.
After using no-store, is it guaranteed that users cannot see old pages after logging out?
No such guarantee can be made. HTTP cache is only part of the browser's state; the browser may also use history or page snapshot mechanisms. When logging out, the session or token must still be invalidated on the server side, and sensitive APIs must revalidate permissions. Cache headers must not be relied on to perform access control.
If the CDN shows HIT but the response says no-cache, is the configuration definitely wrong?
Not necessarily.no-cacheallows storage but not direct reuse without validation. The CDN may have already validated with the origin and then continued to use the cached object. However, you should also check the provider's cache status definitions, edge rules, andAgechanges to confirm whether it really performed validation as required.
- Cache-Control: public
- Cache-Control: private
- no-cache vs no-store difference
- CDN cache settings
- browser cache control
- HTTP caching configuration