Cache-Control: fewer requests, faster pages
Every time a browser loads your page, it needs your images, stylesheets, and scripts. If you tell it nothing, the browser decides on its own how long to trust each file, and frequently re-checks with the server — producing a stream of 304 Not Modified responses in your logs. Each revalidation adds a round trip your visitor waits for and work your server does even when nothing changed. Cache-Control fixes that by telling browsers how long they may reuse a file without asking again.
How caching works
When a browser requests a file it hasn't cached, it downloads it. On later visits, without caching rules, it often sends a conditional request: "has this changed?" If the response carries a usable validator — an ETag or Last-Modified header — the server can answer 304 Not Modified: no new data, but still a round trip. (Without a validator it has to send the whole file again.) Cache-Control lets you skip the round trip entirely for files that don't need it:
Cache-Control: public, max-age=604800
max-age is in seconds (604800 = 7 days). While the cached copy is fresh and still in the cache, the browser normally reuses it without contacting the server at all. Cache-Control has largely replaced the older Expires header, though both still work.
no-cache, no-store and private — three different things
These three directives are easy to confuse, and the names are misleading:
no-cachedoes not mean "don't cache". It means a cache may store the response but must revalidate it with the server before reusing it — a "check with me before trusting your copy" instruction. A useful default for HTML that must be checked before reuse; pair it with a validator so the answer can be a cheap304. (Stable, public HTML can instead take a short freshness lifetime.)no-storetells compliant HTTP caches not to keep the response for later reuse at all. Use it for genuinely sensitive responses (a page showing bank details) — alongside proper access controls, because it can't stop screenshots, downloads, or a noncompliant intermediary from retaining a copy.privatemeans compliant shared caches — a CDN, a proxy — must not store the response; only the visitor's own browser may. Use it for personalized content such as a logged-in dashboard. The application still has to enforce access control and keep per-user cache entries separate:privatecannot fix an edge rule that caches the wrong thing.
For most static assets you want the opposite: public with a long max-age — where public is safe, meaning the response is identical for every visitor.
Recommended settings
- Static assets whose URL changes with their content (versioned CSS, JS, images, fonts): cache aggressively.
- Assets on reusable, unversioned URLs: a lifetime short enough for the staleness you can tolerate.
- HTML: revalidate or cache briefly; stricter (
private,no-store) where personalized or sensitive.
On Apache, you can set the headers with mod_headers in .htaccess (where your host allows those directives — mod_expires is a separate, still-current module that manages expiry times instead):
<IfModule mod_headers.c>
# Use only when every matching URL is versioned — the regex can't check that for you
<FilesMatch "\.(webp|avif|png|jpg|jpeg|gif|svg|woff2|css|js)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# HTML — always revalidate
<FilesMatch "\.(html|php)$">
Header set Cache-Control "no-cache"
</FilesMatch>
</IfModule>
Important: the one-year immutable rule is only safe for fingerprinted/versioned filenames (style.a1b2c3.css, app.9f2.js). This FilesMatch catches every matching file by extension, so if your CSS, JS, or images use plain, unversioned names (style.css, logo.png) it will pin stale copies in visitors' browsers for a year — "we rarely change it" is not enough for immutable. Either version your filenames (below) before using this, or give unversioned assets a much shorter max-age and drop immutable. And after configuring, check the headers a visitor actually receives at the public URL: a reverse proxy or CDN in front can rewrite or ignore your origin's cache policy.
Solve the "stale file" problem with versioning
Long cache lifetimes raise one worry: how do visitors get your update if their browser is told to keep the old file for a year? The answer is cache busting — change the filename when the content changes:
style.css → style.a1b2c3.css
Because the URL is new, browsers fetch it fresh, while all unchanged files stay cached. Two conditions make it work: update every reference (HTML, CSS, preloads, manifest) to the new URL, and leave the old file deployed until cached HTML that still references it has expired — the HTML's own cache policy is part of the system. Many production build pipelines generate these content hashes when configured to.
Tell browsers what they can safely reuse, and repeat visits get faster while fewer requests reach your server.
What to do
- Set long
max-age(andimmutable) on static assets — but only once their filenames are versioned; give unversioned files a short lifetime instead. - Keep HTML on
no-cachewith a workingETag/Last-Modified, or a short lifetime; useprivatefor personalized pages andno-store(plus access controls) for genuinely sensitive ones. - Version asset filenames and update every reference when content changes.
- Enable Gzip/Brotli compression too, and check the headers actually delivered at the public URL — a CDN can change them.
- Re-check your logs: repeat requests and
304s for long-lived versioned assets should drop (HTML onno-cachewill keep revalidating by design).
Frequently asked questions
- How do I stop long cache times from serving visitors an outdated file?
- Use cache busting: give the file a new URL whenever its contents change, for example style.css to style.a1b2c3.css, and update every reference to it. Browsers treat the unseen URL as a fresh download while unchanged files stay cached. Many production build pipelines can generate these content hashes when configured to — and keep the old file deployed until cached HTML referencing it has expired.
- What max-age should I set for images, CSS, and JavaScript?
- Assets whose URL changes whenever their contents change — images, fonts, CSS, and JavaScript alike — can take a long max-age (say 31536000) with immutable. Give reusable, unversioned URLs a lifetime short enough for the staleness you can tolerate. Keep HTML that must stay current on no-cache or a short lifetime, and use private or no-store where personalization or sensitivity demands it.
Looking for more? Browse all the website tips.
Source: “Cache-Control: fewer requests, faster pages” — https://www.siteadvice.be/articles/cache-control/ · © 2026 EUREGIO.NET AG. All rights reserved.