Browser Caching Explained: Cache-Control, ETag and Expires
Browser caching lets returning visitors reuse files instead of downloading them again. Learn Cache-Control, ETag and Expires, plus safe lifetimes per file type.
Every file on your page, from the logo to the main stylesheet, has to reach the visitor's browser. Browser caching lets the browser keep a copy and reuse it on later page views or return visits instead of downloading it again. Done well, it makes the second page a visitor opens feel almost instant and cuts the load on your server.
Caching is controlled by HTTP response headers your server sends with each file. This guide explains the important ones and gives sensible values for typical sites.
How the browser decides
When the browser needs a file, it asks itself two questions. Do I already have a copy? And is that copy still fresh? If it is fresh, the browser uses it without contacting the server at all. If it is stale, the browser can either download the file again or ask the server whether the copy is still valid. That question is called revalidation, and a server can answer "not modified" with a tiny response instead of resending the whole file.
Cache-Control
Cache-Control is the main header and the one to learn first. Common directives:
max-age=N: the file is fresh for N seconds. For example,max-age=31536000means one year.public: shared caches such as a CDN may store it as well as the browser.private: only the visitor's own browser may store it. Use this for responses that are personal.no-cache: the browser may store the file but must revalidate with the server before using it. The name is misleading.no-store: nothing should be stored at all, which is right for sensitive pages.immutable: tells the browser the file will never change at that URL, so it need not revalidate even on reload.
Expires, ETag and Last-Modified
Expires is the older way to set a fixed expiry date. If both are present, Cache-Control: max-age takes priority, so you only need Expires for very old clients.
ETag and Last-Modified help with revalidation. The server sends a fingerprint or a timestamp with the file. When the browser asks again, it sends it back (using If-None-Match or If-Modified-Since), and the server replies with a small 304 Not Modified when nothing changed.
Recommended lifetimes
| File type | Suggested policy | Why |
|---|---|---|
| Images, fonts, video | Long, such as one month to one year | Rarely change |
| CSS and JavaScript with versioned names | One year, immutable | New version gets a new URL |
| CSS and JavaScript with fixed names | Shorter, or use revalidation | You cannot force an update |
| HTML pages | No-cache or short max-age | Content changes and links to new assets |
| Account, basket, admin pages | private or no-store | Personal or sensitive |
The cache busting problem
Long cache times create a risk: you update your stylesheet, but returning visitors keep the old one. The standard solution is to change the file's URL whenever its content changes, by adding a version query string such as style.css?v=2 or, better, a content hash in the file name. Build tools and many WordPress plugins do this automatically. HTML pages should stay short-lived so that they can point to the new URLs.
Setting headers on Apache or Nginx
On Apache, the mod_expires and mod_headers modules let you set caching rules per file type in a .htaccess file. For example, you can give images a long expiry and leave HTML alone. On Nginx, the expires and add_header directives do the same. In Plesk, additional Apache or nginx directives can be set per domain (see the knowledge base for panel guides). WordPress caching plugins can write these rules for you, so check what yours already does before adding more.
Check what you are really sending
- Open Chrome DevTools, go to the Network tab and reload the page.
- Click a file and read the Response Headers section for Cache-Control, ETag and Expires.
- Reload again and watch the Size column: entries marked "disk cache" or "memory cache" were served locally.
- Run PageSpeed Insights, which flags static files with short or missing cache lifetimes.
Common mistakes
- Caching HTML for a long time and then wondering why edits are not visible.
- Using
no-storeeverywhere out of caution, which wastes bandwidth. - Forgetting that a CDN has its own cache and needs purging when files change.
- Sending a long cache time for a file with a fixed name that is updated often.
Caching and the back button
Caching also affects what visitors see when they press the back button. Browsers keep a back-forward cache of recent pages and may show them exactly as they were left. This is usually welcome, but it can surprise people on pages with sensitive data, such as an account page after logging out. For those pages, send Cache-Control: no-store so that nothing sensitive is held in the cache.
Compression works alongside caching
Caching avoids a download; compression makes the download smaller when it does happen. Make sure text files such as HTML, CSS, JavaScript and SVG are compressed with gzip or Brotli. Most hosting setups support both, and CDNs usually apply compression automatically. Check the Content-Encoding header in the Network tab to confirm.
Caching is the cheapest speed-up
Browser caching costs nothing and benefits every returning visitor. Zebra Hosting Cloud hosting runs on the Plesk control panel, where you can manage web server settings per domain,. Pair browser caching with the CDN option for even better results.