What is Cache-Control: private?
Solution 1
To answer your question about why caching is working, even though the web-server didn't include the headers:
-
Expires:
[a date]
-
Cache-Control: max-age=
[seconds]
The server kindly asked any intermediate proxies to not cache the contents (i.e. the item should only be cached in a private cache, i.e. only on your own local machine):
- Cache-Control: private
But the server forgot to include any sort of caching hints:
- they forgot to include Expires (so the browser knows to use the cached copy until that date)
- they forgot to include Max-Age (so the browser knows how long the cached item is good for)
- they forgot to include E-Tag (so the browser can do a conditional request)
But they did include a Last-Modified date in the response:
Last-Modified: Tue, 16 Oct 2012 03:13:38 GMT
Because the browser knows the date the file was modified, it can perform a conditional request. It will ask the server for the file, but instruct the server to only send the file if it has been modified since 2012/10/16 3:13:38:
GET / HTTP/1.1
If-Modified-Since: Tue, 16 Oct 2012 03:13:38 GMT
The server receives the request, realizes that the client has the most recent version already. Rather than sending the client 200 OK
, followed by the contents of the page, it instead tells you that your cached version is good:
304 Not Modified
Your browser did have to suffer the round-trip delay of sending a request to the server, and waiting for the response, but it did save having to re-download the static content.
Why Max-Age? Why Expires?
Because Last-Modified sucks.
Not everything on the server has a date associated with it. If I'm building a page on the fly, there is no date associated with it - it's now. But I'm perfectly willing to let the user cache the homepage for 15 seconds:
200 OK
Cache-Control: max-age=15
If the user hammers F5, they'll keep getting the cached version for 15 seconds. If it's a corporate proxy, then all 67,198 users hitting the same page in the same 15-second window will all get the same contents - all served from close cache. Performance win for everyone.
The virtue of adding Cache-Control: max-age
is that the browser doesn't even have to perform a "conditional" request.
- if you specified only
Last-Modified
, the browser has to perform aIf-Modified-Since
request, and watch for a304 Not Modified
response - if you specified
max-age
, the browser won't even have to suffer the network round-trip; the content will come right out of the caches.
The difference between "Cache-Control: max-age" and "Expires"
Expires
is a legacy (c. 1998) equivalent of the modern Cache-Control: max-age
header:
-
Expires
: you specify a date (yuck) -
max-age
: you specify seconds (goodness) -
And if both are specified, then the browser uses
max-age
:200 OK Cache-Control: max-age=60 Expires: 20180403T192837
Any web-site written after 1998 should not use Expires
anymore, and instead use max-age
.
What is ETag?
ETag is similar to Last-Modified, except that it doesn't have to be a date - it just has to be a something
.
If I'm pulling a list of products out of a database, the server can send the last rowversion
as an ETag, rather than a date:
200 OK
ETag: "247986"
My ETag can be the SHA1 hash of a static resource (e.g. image, js, css, font), or of the cached rendered page (i.e. this is what the Mozilla MDN wiki does; they hash the final markup):
200 OK
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
And exactly like in the case of a conditional request based on Last-Modified:
GET / HTTP/1.1
If-Modified-Since: Tue, 16 Oct 2012 03:13:38 GMT
304 Not Modified
I can perform a conditional request based on the ETag:
GET / HTTP/1.1
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
304 Not Modified
An ETag
is superior to Last-Modified
because it works for things besides files, or things that have a notion of date. It just is
Solution 2
Cache-Control: private
Indicates that all or part of the response message is intended for a single user and MUST NOT be cached by a shared cache, such as a proxy server.
Solution 3
RFC 2616, section 14.9.1:
Indicates that all or part of the response message is intended for a single user and MUST NOT be cached by a shared cache...A private (non-shared) cache MAY cache the response.
Browsers could use this information. Of course, the current "user" may mean many things: OS user, a browser user (e.g. Chrome's profiles), etc. It's not specified.
For me, a more concrete example of Cache-Control: private
is that proxy servers (which typically have many users) won't cache it. It is meant for the end user, and no one else.
FYI, the RFC makes clear that this does not provide security. It is about showing the correct content, not securing content.
This usage of the word private only controls where the response may be cached, and cannot ensure the privacy of the message content.
Related videos on Youtube
user782220
Updated on October 23, 2021Comments
-
user782220 over 2 years
When I visit chesseng.herokuapp.com I get a response header that looks like
Cache-Control:private Connection:keep-alive Content-Encoding:gzip Content-Type:text/css Date:Tue, 16 Oct 2012 06:37:53 GMT Last-Modified:Tue, 16 Oct 2012 03:13:38 GMT Status:200 OK transfer-encoding:chunked Vary:Accept-Encoding X-Rack-Cache:miss
and then I refresh the page and get
Cache-Control:private Connection:keep-alive Date:Tue, 16 Oct 2012 06:20:49 GMT Status:304 Not Modified X-Rack-Cache:miss
so it seems like caching is working. If that works for caching then what is the point of Expires and Cache-Control:max-age. To add to confusion, when I test the page at https://developers.google.com/speed/pagespeed/insights/ it tells me to "Leverage browser caching".
-
pravdomil about 6 yearscheck this diagram stackoverflow.com/a/49925190/3748498
-
-
user782220 over 11 yearsSo can you explain why the response after the refresh has Status:304 Not Modified
-
Dan D. over 11 yearsBecause it was cached by your browser. You are the single user that response was intended for.
-
user782220 over 11 yearsSo isn't having each single user cache it effectively the same as Expires or Cache-Control:max-age.
-
Dan D. over 11 yearsNo, it is not because
Cache-Control:private
only states that shared caches (such as proxy caches) should not cache the response. -
Hakanai over 10 yearsI see that even when this header is present, Chrome will cache an item between two profiles in the same browser. So I assume that "single user" really means "single computer".
-
Dan D. over 10 years@Trejkaz No, it really means a single user. A user is a account which has its own home directory in which the cache resides. Those profiles which are owned by the same user may share their cache. As you have found. But two profiles on the same computer if owned by different users must not share their cache, unless that cache is treated as a shared cache.
-
Hakanai over 10 yearsAh, so it's per-user-at-the-OS-level. Yeah, the reason I'm wondering is because of an apparent information leak between Chrome's incognito windows and the non-incognito ones, which uses the cache to do it.
-
Didier A. over 10 yearsWhat would be the difference with proxy-revalidate?
-
Dan D. over 10 years@didibus
proxy-revalidate
requires that proxies always revalidate on each access. Where asprivate
prevents the proxy from caching. -
Oliver almost 10 yearsA private (non-shared) cache MAY cache the response. This part is key. Thanks.
-
mfaani over 5 yearsAwesome! I placed a bounty for this answer. What happens if
cache-control
doesn't exist? And you only have Etag? Doesn't it still need to make a 'conditional request' against the server? The behavior I'm seeing when I'm offline is that it just returns from cache. But when it's offline it can't make that conditional request. So does that mean if it will cache indefinitely if you stay offline? I've already asked this question in detail here. Can you take a look?