Skip to content
Website Speed

Leverage Browser Caching: Cache-Control Headers Explained

The browser caching warning in plain terms: what cache-control headers tell a browser, safe lifetimes for each file type and what to do about third-party files.

A laptop showing website analytics charts
9 sections · 9 min read
In this article · 9 sections

The line in the report is short and not very helpful. Older tools say “Leverage browser caching”. PageSpeed Insights has phrased it as “Serve static assets with an efficient cache policy” and, in newer versions, as advice to use efficient cache lifetimes. Underneath is a list of files, each with a “cache TTL” of a few minutes, an hour, or “None”.

The fix is not a plugin setting you switch on and forget. It is a set of instructions your server sends to browsers. To leverage browser caching properly, you need to know what those instructions say, why version numbers on files make long cache times safe, and which files on the list you can simply leave alone.

What the warning is telling you

Every time a browser downloads a file from your site, it also receives a few lines of instructions called response headers. Some of those say whether the browser may keep a copy, and for how long. If the instructions are missing or very short, the browser has to ask for the file again on the next page or the next visit, even though nothing has changed.

The warning lists files whose instructions are missing or short. It does not mean your site is broken. It means returning visitors and people clicking through to a second page are downloading your stylesheet, fonts, logo and scripts again when they could have reused them. On a site where most visitors look at one page and leave, the gain is smaller. On a shop where people browse ten products, it adds up.

Browser caching is only one of the caches on a site, and it is the only one that lives on the visitor’s device rather than on a server. Our overview of how the different website caches stack up shows where it fits.

Cache-control headers explained without the jargon

The main header is called Cache-Control. It contains one or more short instructions. These are the ones you will meet most often:

Instruction What it tells the browser
max-age=31536000 Keep this file and reuse it for this many seconds without asking. 31536000 seconds is one year.
public Any cache may store it, including shared ones such as a CDN.
private Only this visitor’s own browser may store it. Used for personal content.
no-cache You may store it, but check with the server before every reuse.
no-store Do not store it at all.
immutable This file will never change at this address, so do not even check during its lifetime.

Two details trip people up. First, no-cache does not mean “do not cache”. It means “always check first”. The instruction that really forbids storage is no-store. Second, there is an older header called Expires, which gives a date and time instead of a number of seconds. It still works, but when both are present, Cache-Control’s max-age wins.

For the full list of instructions and how browsers treat them, the MDN reference for Cache-Control is the clearest source.

What “check with the server” means

When a stored copy’s time is up, or the file is marked no-cache, the browser does not always download it again. It asks the server, in effect, “I have the version from this date, or with this fingerprint. Has it changed?” The fingerprint is a header called ETag, and the date is called Last-Modified. If nothing has changed, the server replies with a short “304 Not Modified” and the browser uses its copy.

That is cheaper than a full download, but it is still a round trip to the server for every file. On a slow mobile connection, dozens of those small checks still cost time. That is why speed tools prefer long max-age values for files that rarely change.

Why long cache times are safe: file versioning

The obvious worry is this: if a browser keeps your stylesheet for a year, how does a visitor ever see your new design?

The answer is that a changed file gets a new address. WordPress does this for you with most theme and plugin files. Look at your page source and you will see addresses like style.css?ver= followed by a number. When a theme or plugin is updated, the number changes. To the browser, that is a different file, so it downloads it fresh and the old copy is simply never asked for again. Build tools and some optimisation plugins go further and put a fingerprint into the filename itself.

Where versioning breaks down:

  • Images replaced under the same name. Upload a new logo.png over the old one by FTP and browsers that cached the old file may keep showing it until it expires. Upload under a new name instead.
  • Theme files edited directly. If you edit a stylesheet in a child theme and the version number does not change, browsers can hold the old one. Some developers bump the version in the theme’s code when they change it.
  • Files linked by hand. A script added straight into a header template, without WordPress’s normal loading functions, gets no version number.

Get versioning right and you can safely leverage browser caching with long lifetimes for every static file.

Sensible lifetimes by file type

These are reasonable starting points for a typical WordPress site, not rules from any standard:

  • CSS and JavaScript with version numbers: long, up to a year.
  • Fonts: long, up to a year. Fonts almost never change at the same address.
  • Images in the media library: long, months to a year. WordPress gives new uploads new names, and edits create new files.
  • Favicons and logos that you sometimes replace: shorter, or make sure replacements get new names.
  • HTML pages: short, or no-cache. The page itself is what changes, and it is what contains the versioned links to everything else.
  • Anything personal: private or no-store.

The common mistake is to apply one long lifetime to everything, HTML included. Then a visitor who came yesterday keeps seeing yesterday’s page, with yesterday’s file links, and you get reports of a site that “will not update”.

How to see what your site sends today

  1. Open your site in Chrome, right-click and choose Inspect, then open the Network tab.
  2. Reload the page. Click a CSS file, then look under Response Headers for cache-control, expires, etag and last-modified.
  3. Reload again normally, without a hard refresh. In the Size column, files reused from the browser show “memory cache” or “disk cache” instead of a size.
  4. Repeat for an image, a font and the page itself.

Keep the “Disable cache” box unticked for this test, since it switches browser caching off while DevTools is open. Our walk-through of the DevTools Network tab for speed checks covers the panel in more depth.

Where the headers come from, and how they get changed

The headers are set by the web server, not by WordPress pages, so how you change them depends on the hosting.

  • Apache and LiteSpeed servers read rules from the .htaccess file in your site’s root folder. Many caching plugins have a browser caching option that writes these rules for you. Adding them by hand is possible too. Save a spare copy of .htaccess first: one stray character in it is enough to stop every page loading.
  • Nginx servers do not read .htaccess. The rules live in the server configuration, which on most hosting only the host can edit. A plugin option may appear to work and change nothing. Ask your host what lifetimes they set.
  • Managed WordPress hosts often set sensible lifetimes already and do not let you change them.
  • A CDN can rewrite these headers on the way through. Cloudflare, for example, has a browser cache setting that can override what your server sends. If the headers in DevTools do not match what you set on the server, the CDN is the likely reason. Our post on Cloudflare caching for WordPress explains how the two interact.

Change one place only. Rules in .htaccess, a plugin and a CDN all setting different lifetimes for the same files make it very hard to know which one is winning.

Files on the list that you cannot control

Almost every report will still list some files after you fix your own. Look at the domain next to each one. Files from google-analytics.com, googletagmanager.com, connect.facebook.net, chat widget providers, map services and embedded video players come from someone else’s server, with someone else’s headers.

Many of these are deliberately given short lifetimes so the provider can update them quickly. You cannot change that, and the old trick of copying an analytics script onto your own server to give it a longer lifetime tends to cause more trouble than it saves: the copy goes out of date and tracking can quietly break.

What you can do is decide whether each third-party file needs to be there at all, and whether it can load later. That is a bigger topic than caching, covered in our post on external scripts that slow a page down. For the purposes of this warning, it is fine to leave third-party items on the list once your own files are sorted.

What to expect after the change

Be realistic about the result. Browser caching does nothing for a visitor’s very first page view, and lab tests like Lighthouse load the page with an empty cache, so the score may barely move. The warning will shrink, and repeat visits and second pages will get lighter. Check it the way a visitor would: open a page, click through to another and watch how many files are reused in the Network tab. The guide to website performance puts this alongside the changes that do help first visits.

Frequently asked questions

Will fixing browser caching improve my PageSpeed score?

Often only a little. Lab tests load the page with an empty cache, so they mostly see the first-visit experience, where browser caching does not help. The warning will shrink, and real visitors who return or open a second page will download less. Judge it by repeat page views, not by the score.

Why does my site still look old to a returning visitor?

Usually because a file was changed without its address changing, so the browser keeps its stored copy until it expires. This happens with images replaced under the same name and theme files edited without a new version number. It can also mean the HTML page itself was given a long lifetime.

Is it safe to add browser caching rules to .htaccess myself?

It is safe if you keep a copy of the original file and can restore it by FTP or the hosting file manager. A single typing error in .htaccess can stop the whole site loading. On Nginx servers, .htaccess is ignored, so rules there have no effect and the host has to set lifetimes.

What cache lifetime should HTML pages have?

Short, or none. The HTML is the part of the site that changes most often, and it contains the versioned links to every other file. Keeping it in the browser for days means returning visitors may see outdated content. Long lifetimes belong on versioned CSS, JavaScript, fonts and images.

When the report keeps listing your own files whatever you change, the headers are usually being set or overridden somewhere out of sight, often by the host or a CDN. Tracing that falls under the hosting and server review in a speed optimisation job.

Next in this series: Redis Object Cache for WordPress: When It Is Worth It

Is Your Website Working Properly?

Get a free website health check from WebMaintor. We'll review your website's speed, security, forms, and technical health — and tell you exactly what needs attention.

No obligation. No automated reports. A real review by our team.