To set browser caching, send a Cache-Control header with every response. For static files with a version or hash in the filename (CSS, JavaScript, images, fonts) use Cache-Control: public, max-age=31536000, immutable, which caches them for a year. For HTML pages use Cache-Control: no-cache, so browsers always check for a fresh copy before reusing one.
The rest of this guide explains what each directive means, the safe pattern for most websites, copy-paste server configuration, and how to check your headers.
Key takeaways
- Cache versioned CSS, JavaScript, images and fonts with Cache-Control public, max-age=31536000, immutable for one year.
- Send Cache-Control no-cache on HTML pages so browsers keep a copy but check with the server before every reuse.
- No-cache does not mean do not cache, so use no-store for private pages such as account details or checkout.
- Only use one-year lifetimes on files whose filename or version changes when the content changes, otherwise visitors keep stale copies.
What is browser caching?
When a browser downloads a file, it can keep a copy and reuse it on the next page view or visit instead of downloading it again. The Cache-Control response header tells the browser, and any CDN or proxy in between, whether it may store the file and for how long.
Good caching does not make a first visit faster. It makes the second page view and every return visit much faster, because the logo, stylesheet, scripts and fonts come straight from the device. Since Chrome 86 the cache is partitioned by site, so a file cached while visiting another website is not reused on yours, even when it comes from the same shared CDN URL. Your own caching headers are what count.
If you send no caching headers, browsers do not simply skip caching. They guess, using heuristics based on headers like Last-Modified. That makes behaviour unpredictable, so it is better to set headers explicitly.
What do the Cache-Control directives mean?
| Directive | Meaning |
|---|---|
max-age=N |
The response is fresh for N seconds. 31536000 is one year. |
s-maxage=N |
Same as max-age but only for shared caches such as CDNs. |
no-cache |
May be stored, but must be revalidated with the server before every reuse. |
no-store |
Must not be stored by any cache at all. |
private |
Only the browser may store it, not a CDN. Use for personalised pages. |
public |
Any cache may store it. |
immutable |
The file will not change while fresh, so skip revalidation on reload. |
must-revalidate |
Once stale, it must be revalidated before reuse. |
stale-while-revalidate=N |
After going stale, the cache may serve it for N more seconds while fetching a fresh copy in the background. |
The most misunderstood pair: no-cache does not mean "don't cache". It means "check with me first". If you truly want nothing stored, for example on a page showing account details, use no-store.
What caching policy should most websites use?
The pattern recommended by Google's web.dev guidance is simple:
- Versioned static files (
style.4f3a9c.css,app.v42.js,logo.a1b2.webp):Cache-Control: public, max-age=31536000, immutable. One year is the longest supported value. When the file changes, its filename changes, so visitors fetch the new one automatically. - HTML pages:
Cache-Control: no-cache. The browser keeps a copy but checks with the server each time. If nothing has changed, the server replies304 Not Modifiedwith no body, which is quick. - Private or sensitive pages (account, basket, checkout):
Cache-Control: private, no-storeor at leastprivate, no-cache. - Unversioned static files (images uploaded through a CMS with fixed names): a moderate lifetime such as
max-age=2592000(30 days), accepting that changes may take a while to appear for repeat visitors.
Make sure the server sends an ETag or Last-Modified header so revalidation works. Web.dev recommends ETag as the more accurate of the two.
What is cache busting and why do I need it?
Long cache lifetimes are only safe if a changed file gets a new URL. Otherwise visitors keep the old stylesheet for a year. This is called cache busting, and there are two common ways:
- Fingerprinted filenames (
main.8d7e1f.css), produced by build tools such as Vite or webpack. This is the most reliable method. - Version query strings (
style.css?ver=6.6), used by WordPress and many themes. These work with browsers and most CDNs, though some caches handle query strings differently.
If your site has neither, use a shorter lifetime for CSS and JavaScript, or add versioning before going long.
How do I set Cache-Control headers in Nginx?
# Versioned static assets: cache for one year
location ~* \.(?:css|js|mjs|woff2|webp|avif|png|jpe?g|gif|svg|ico)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# HTML: always revalidate
location / {
add_header Cache-Control "no-cache";
try_files $uri $uri/ /index.php?$args;
}
Note that in Nginx, add_header directives in a location block replace those set at a higher level, so if you set security headers in the server block you need to repeat them inside each location that uses add_header. Adjust the try_files line to match your application.
How do I set Cache-Control headers in Apache?
In the virtual host or .htaccess, with mod_headers enabled:
<IfModule mod_headers.c>
<FilesMatch "\.(css|js|mjs|woff2|webp|avif|png|jpe?g|gif|svg|ico)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
<FilesMatch "\.(html|php)$">
Header set Cache-Control "no-cache"
</FilesMatch>
</IfModule>
Only use a one-year lifetime if those files are versioned. If they are not, reduce max-age to something like 2592000 (30 days).
How does browser caching work with a CDN?
A CDN is a shared cache, so it honours public, private and s-maxage. A common setup is to let the CDN keep HTML briefly while browsers always revalidate:
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=60
That lets the CDN serve the page for five minutes and refresh it in the background, which can cut server response time sharply. Never do this for logged-in or personalised pages. Our guide on whether you need a CDN covers CDN caching in more detail.
How do I check my caching headers?
- Open Chrome DevTools, go to Network, reload, click a file and read
cache-controlunder Response Headers. - Reload again without disabling cache. Files served from cache show "(memory cache)" or "(disk cache)" in the Size column.
- From a terminal:
curl -sI https://www.example.com/style.css | grep -i cache-control
Lighthouse reports short cache lifetimes under the Use efficient cache lifetimes insight. MakeItFlow shows this as part of its PageSpeed results, and the same scan checks your security headers. You can also use the free security headers checker to view your response headers quickly.
Frequently asked questions
Why does Lighthouse still flag short cache lifetimes?
Often the flagged files belong to third parties, such as analytics or chat scripts, which deliberately use short lifetimes so they can update. You cannot change their headers. Focus on files from your own domain and CDN.
Should I use Expires or Cache-Control?
Use Cache-Control. The older Expires header sets an absolute date and is overridden by Cache-Control: max-age when both are present. There is no need to send both on a modern site.
How long should I cache images?
If image URLs change when the image changes, one year. If you replace images in place with the same filename, use days or weeks rather than a year, or rename files when you update them.
Will long caching stop visitors seeing my updates?
Not for HTML set to no-cache, which is revalidated on every visit. Only unversioned static files with long lifetimes cause stale content. Versioning filenames solves this completely.
Does browser caching help SEO?
Not directly. Lab tests like Lighthouse always load with an empty cache. Caching helps real visitors browsing several pages, which improves field Core Web Vitals and the overall experience. See our Core Web Vitals guide for how field data works.
What to do next
Check the cache-control header on your home page, main stylesheet, main script and logo. If the static files show short or missing lifetimes, apply the snippet for your server, confirm your files are versioned, and re-test. Then run a free website audit with MakeItFlow to check caching, compression and Core Web Vitals together.
Sources
- Cache-Control – MDN
- Prevent unnecessary network requests with the HTTP Cache – web.dev
- Gaining security and privacy by partitioning the cache – Chrome for Developers
Written by the MakeItFlow team
We build the website audit these guides are based on. Every fact is checked against the primary sources listed above and this guide was last reviewed on 25 October 2026 . How we research and check
Check your own site in 30 seconds
Run a free audit covering speed, SEO, DNS, email authentication and security headers — and get a prioritised fix list.