By the end of the lesson, you can explain the main directives of Cache-Control, the workings of freshness and revalidation (ETag, 304), you can tell a private cache from a shared one, and you know why a badly configured shared cache can be a privacy incident.
Why ask for the same thing again?
A visitor opens the home page: the browser downloads the HTML, the style sheet, the scripts, the logo, and the photos. On the next page the same logo, font, and style sheet appear. It would be wasteful to send these again at every click. And if ten thousand visitors ask for the same 200-kilobyte image, the server and the network do a senselessly large amount of repeated work. A cache preserves the earlier result for later use.
A cache, however, cannot "lie" without limit. Yesterday's news may still be useful, but an old value of an exam date or a stock level can cause harm. Cache design is therefore a trade-off among freshness, speed, cost, and load capacity.
Layers: private and shared cache
MDN distinguishes two kinds:
Private cache: tied to one client, typically the browser's. It can also store a personalized response (Cache-Control: private). Very fast, but helps only one user.
Shared cache: stands between the client and the server, and serves several users. This includes proxies and managed caches (reverse proxy, CDN, service worker, see 4.4 and 6.6). A personalized response must not be stored here.
A third layer is the server's own (application or data) cache: the result of a costly query that the program keeps in memory for a few minutes. Its fault is often spectacular: if after course registration the old headcount is shown for minutes, that is probably a refresh question, not "bad internet".
flowchart LR
B[Browser cache: private] --> E[CDN / reverse proxy: shared]
E --> A[Application cache]
A --> O[Origin server and database]
Fresh, stale, revalidated
A stored response is fresh while its age is less than its lifetime; afterwards it is stale. The headers communicate the lifetime:
Cache-Control: public, max-age=300
According to MDN max-age=N means that the response is fresh for N seconds. A shared cache communicates in the Age header how long the response has been with it (RFC 9111, see the example of example.com in lesson 4.4: age: 1950). max-age does not mean that the resource cannot change, but that the server accepts: for the given time the earlier version is acceptable.
If there is no Cache-Control, a cache may still store and use a response, based on a heuristic (according to MDN typically up to about 10% of the time since Last-Modified). That is why it is always worth stating the rule explicitly.
The main directives
Directive
Meaning (MDN, based on RFC 9111)
max-age=N
Fresh for N seconds.
no-cache
Can be stored, but must be revalidated with the server before every use. (It does not mean "do not store".)
no-store
Must not be stored anywhere. Not even in a private cache; it is not a reliable privacy tool, and it also loses other benefits (for example bfcache).
private
Only the individual user's private cache may store it.
public
May be stored even if there is an Authorization header in the request; use only when necessary.
immutable
The content does not change during its lifetime; a refresh (reload) need not revalidate.
MDN's typical settings:
# Dynamic HTML, should always be fresh
Cache-Control: no-cache
# Personal page of a logged-in user
Cache-Control: no-cache, private
# Sensitive data never to be stored
Cache-Control: no-store
# Versioned static file (for example app.4f8a2c.js)
Cache-Control: public, max-age=31536000, immutable
Revalidation: ETag and 304
A stale response can be revalidated with a conditional request. The server gives an identifier in the ETag header for a particular version of the response. The client can later send it in the If-None-Match header: "I have the version with this ETag; has it changed?" If not, the server sends a 304 Not Modified response, without a body, and the browser can regard the stored copy as fresh again. This way there is a network round trip, but the content need not be downloaded again. Last-Modified / If-Modified-Since do the same with dates.
Tried on the trial server (Cache-Control: no-cache + ETag):
The second response has an empty body: the client learned that its copy is still good, without downloading it again.
Versioned URL (cache busting)
According to MDN the HTML of the home page cannot be cached well, because its address does not change; it therefore needs no-cache. Static resources (JavaScript, CSS, images), however, can be cached for a very long time if the file name contains the version or the digest: app.4f8a2c.js. At a new release a new file name (app.91bd77.js) arises, which the HTML already references under the new name, so the old stored copy bothers nobody. The tried header: Cache-Control: public, max-age=31536000, immutable (one year).
The danger of the shared cache: leaking personal data
Imagine that the personalized "My enrolments" page of Kurzuspont gets no private marking, and someone even sets public, max-age=60 "for the sake of speed". A shared cache (a CDN or reverse proxy) stores the response by the URL. We tried a simple shared cache that uses only the URL as the key, with two users:
Origin setting: Cache-Control: public, max-age=60 (WRONG)
Anna requests : X-Cache: MISS <p>Üdv, anna! Jelentkezéseid: ...anna adatai...</p>
Béla requests : X-Cache: HIT <p>Üdv, anna! Jelentkezéseid: ...anna adatai...</p> <-- Béla sees Anna's data
Origin setting: Cache-Control: private (CORRECT)
Anna requests : X-Cache: MISS <p>Üdv, anna! ...</p>
Béla requests : X-Cache: MISS <p>Üdv, bela! ...</p>
(The page text means "Welcome, anna! Your enrolments: ...anna's data...".)
In the first case Béla got Anna's data, because the shared cache gave the response of the first request to everyone. This is a privacy incident (9.6), whose cause was a single faulty header. MDN also stresses: a personalized response must be marked private (Vary is not a solution for cookies), and shared caches may also collapse identical, simultaneous requests into one request, which makes the situation worse for personalized content.
The Vary header prescribes a separate cache entry according to the values of the given request headers (for example Vary: Accept-Language). MDN does not recommend using Vary: User-Agent, because it creates too many variants.
CDN in practice: the edge cache
The CDN was introduced in lesson 4.4. From the cache's viewpoint, the CDN is a shared cache, which stores at nodes close to the user (the edge). The age of responses (Age) and the hit signal (cf-cache-status: HIT in the response of example.com) show that the response came from the cache. The advantage of a CDN is conditional: it is greatest for static, public, versioned files, and it solves nothing for a personalized or database-heavy response if the slowness arises in the origin application.
Geographic distance matters (4.6), because the signal does not teleport, but a CDN is useful also because it protects the origin server from mass load: during a popular announcement the edge nodes absorb many identical requests. A CDN can then be useful for TLS termination or filtering attacks, but it cannot take over a dynamic business decision (authorization, 7.4) without thought.
Invalidation
MDN's remark: responses already stored in intermediate caches cannot be "recalled". If a faulty file has to be fixed, it is risky to upload a new one to the same URL (some may still see the old one). It is safer to give a new URL (for example tajekoztato-2026-09-v2.pdf), or to use the CDN's invalidation (purge) function, and for a frequently changing resource to give no-cache, so that control stays in the hands of the server.
Worked example: the cache rules of Kurzuspont
Resource
Rule
Reason
app.4f8a2c.js, stilus.9b1e.css, the logo
public, max-age=31536000, immutable
Versioned file name: a new URL at a new release.
A regulation in PDF (Portable Document Format, szabalyzat-2026-09.pdf)
public, max-age=86400
Does not change after release; a new name on correction.
Course description (public HTML)
public, max-age=300 + ETag
Changes rarely, a five-minute discrepancy is acceptable.
/kurzusok API (public data)
no-cache + ETag
We always check, but the 304 is cheap.
"My enrolments"
private, no-cache
Personal data: must not get into a shared cache.
The payment page and its responses
no-store
Sensitive, irreversible operation.
Number of free places
no cache / a few seconds
Live data; the server decides (5.5, 6.5).
The good solution is not "switching everything off", but choosing a rule for the nature of the content.
Common misconceptions
Statement
Clarification
"A cache is always faster, so let's store everything."
For personal, sensitive, or fast-changing responses old data is dangerous.
"no-cache means do not store it."
It means: it may store, but must revalidate before use. The prohibition is no-store.
"If I upload the new file, everyone sees it at once."
Only if the rules for old copies, invalidation, or a new URL ensure it.
"A CDN speeds up every page."
It does not solve the slowness of a personalized, database-heavy response.
"A cache is only a performance optimization."
It is a data-handling rule: it affects correctness and privacy too.
In brief
A private cache serves one client, a shared one several users; a personal response may go only into a private one.
A response is fresh for the max-age, afterwards it can be revalidated by ETag/If-None-Match; the 304 is a cheap response without a body.
no-cache = revalidate before use; no-store = do not store.
With a versioned file name a static resource can be cached even for a year.
On the tried example a faulty public header gave Anna's data to Béla.
Review questions
What is the difference between no-cache and no-store?
What does 304 Not Modified do, and why is it cheap?
Why is public, max-age=60 dangerous on a personalized page?
How can a static file be cached for a year so that a new release still takes effect at once?
Learning resources
HTTP caching (MDN) — Cache-Control, freshness, revalidation, the versioned URL.
HTTP cache and CDN in practice
Objectives
By the end of the lesson, you can explain the main directives of
Cache-Control, the workings of freshness and revalidation (ETag,304), you can tell a private cache from a shared one, and you know why a badly configured shared cache can be a privacy incident.Why ask for the same thing again?
A visitor opens the home page: the browser downloads the HTML, the style sheet, the scripts, the logo, and the photos. On the next page the same logo, font, and style sheet appear. It would be wasteful to send these again at every click. And if ten thousand visitors ask for the same 200-kilobyte image, the server and the network do a senselessly large amount of repeated work. A cache preserves the earlier result for later use.
A cache, however, cannot "lie" without limit. Yesterday's news may still be useful, but an old value of an exam date or a stock level can cause harm. Cache design is therefore a trade-off among freshness, speed, cost, and load capacity.
Layers: private and shared cache
MDN distinguishes two kinds:
Cache-Control: private). Very fast, but helps only one user.A third layer is the server's own (application or data) cache: the result of a costly query that the program keeps in memory for a few minutes. Its fault is often spectacular: if after course registration the old headcount is shown for minutes, that is probably a refresh question, not "bad internet".
flowchart LR B[Browser cache: private] --> E[CDN / reverse proxy: shared] E --> A[Application cache] A --> O[Origin server and database]Fresh, stale, revalidated
A stored response is fresh while its age is less than its lifetime; afterwards it is stale. The headers communicate the lifetime:
According to MDN
max-age=Nmeans that the response is fresh for N seconds. A shared cache communicates in theAgeheader how long the response has been with it (RFC 9111, see the example ofexample.comin lesson 4.4:age: 1950).max-agedoes not mean that the resource cannot change, but that the server accepts: for the given time the earlier version is acceptable.If there is no
Cache-Control, a cache may still store and use a response, based on a heuristic (according to MDN typically up to about 10% of the time sinceLast-Modified). That is why it is always worth stating the rule explicitly.The main directives
max-age=Nno-cacheno-storeprivatepublicAuthorizationheader in the request; use only when necessary.immutableMDN's typical settings:
Revalidation: ETag and 304
A stale response can be revalidated with a conditional request. The server gives an identifier in the
ETagheader for a particular version of the response. The client can later send it in theIf-None-Matchheader: "I have the version with this ETag; has it changed?" If not, the server sends a304 Not Modifiedresponse, without a body, and the browser can regard the stored copy as fresh again. This way there is a network round trip, but the content need not be downloaded again.Last-Modified/If-Modified-Sincedo the same with dates.Tried on the trial server (
Cache-Control: no-cache+ETag):The second response has an empty body: the client learned that its copy is still good, without downloading it again.
Versioned URL (cache busting)
According to MDN the HTML of the home page cannot be cached well, because its address does not change; it therefore needs
no-cache. Static resources (JavaScript, CSS, images), however, can be cached for a very long time if the file name contains the version or the digest:app.4f8a2c.js. At a new release a new file name (app.91bd77.js) arises, which the HTML already references under the new name, so the old stored copy bothers nobody. The tried header:Cache-Control: public, max-age=31536000, immutable(one year).The danger of the shared cache: leaking personal data
Imagine that the personalized "My enrolments" page of Kurzuspont gets no
privatemarking, and someone even setspublic, max-age=60"for the sake of speed". A shared cache (a CDN or reverse proxy) stores the response by the URL. We tried a simple shared cache that uses only the URL as the key, with two users:(The page text means "Welcome, anna! Your enrolments: ...anna's data...".)
In the first case Béla got Anna's data, because the shared cache gave the response of the first request to everyone. This is a privacy incident (9.6), whose cause was a single faulty header. MDN also stresses: a personalized response must be marked
private(Varyis not a solution for cookies), and shared caches may also collapse identical, simultaneous requests into one request, which makes the situation worse for personalized content.The
Varyheader prescribes a separate cache entry according to the values of the given request headers (for exampleVary: Accept-Language). MDN does not recommend usingVary: User-Agent, because it creates too many variants.CDN in practice: the edge cache
The CDN was introduced in lesson 4.4. From the cache's viewpoint, the CDN is a shared cache, which stores at nodes close to the user (the edge). The age of responses (
Age) and the hit signal (cf-cache-status: HITin the response ofexample.com) show that the response came from the cache. The advantage of a CDN is conditional: it is greatest for static, public, versioned files, and it solves nothing for a personalized or database-heavy response if the slowness arises in the origin application.Geographic distance matters (4.6), because the signal does not teleport, but a CDN is useful also because it protects the origin server from mass load: during a popular announcement the edge nodes absorb many identical requests. A CDN can then be useful for TLS termination or filtering attacks, but it cannot take over a dynamic business decision (authorization, 7.4) without thought.
Invalidation
MDN's remark: responses already stored in intermediate caches cannot be "recalled". If a faulty file has to be fixed, it is risky to upload a new one to the same URL (some may still see the old one). It is safer to give a new URL (for example
tajekoztato-2026-09-v2.pdf), or to use the CDN's invalidation (purge) function, and for a frequently changing resource to giveno-cache, so that control stays in the hands of the server.Worked example: the cache rules of Kurzuspont
app.4f8a2c.js,stilus.9b1e.css, the logopublic, max-age=31536000, immutableszabalyzat-2026-09.pdf)public, max-age=86400public, max-age=300+ETag/kurzusokAPI (public data)no-cache+ETag304is cheap.private, no-cacheno-storeThe good solution is not "switching everything off", but choosing a rule for the nature of the content.
Common misconceptions
no-cachemeans do not store it."no-store.In brief
max-age, afterwards it can be revalidated byETag/If-None-Match; the304is a cheap response without a body.no-cache= revalidate before use;no-store= do not store.publicheader gave Anna's data to Béla.Review questions
no-cacheandno-store?304 Not Modifieddo, and why is it cheap?public, max-age=60dangerous on a personalized page?Learning resources