SEO check

CSS Caching: How to Check and Fix Cache Headers on Your Stylesheets

Stylesheets sent without cache headers are often fetched again on repeat visits. See how our report tests this and how to fix it.

Free to use 11 min read Updated Oct 2026

What Is CSS Caching? The Quick Answer

Quick answer: CSS caching means your server tells the browser, through a Cache-Control or Expires header, that a stylesheet may be stored and reused instead of downloaded again on every visit.

  • Pass: every stylesheet we tested came back with a Cache-Control or Expires header.
  • Warning: some stylesheets had the header and some did not.
  • Fail: none of them had it, or the page contained no stylesheet link at all.
  • Fix: send a long max-age for .css files from your server or CDN, and give each stylesheet a versioned file name so updates still reach visitors.

Check my website

It is one of several speed checks, covered in the page speed and SEO post.

How CSS Caching Works in the Browser

On a first visit the browser downloads each stylesheet and stores it with the freshness lifetime the server supplied. While that window lasts, later visits load the file from disk with no network request. Afterwards the browser sends a conditional request (If-None-Match with the ETag, or If-Modified-Since), and an unchanged file earns a 304 Not Modified reply with no body, which saves bytes but still costs a round trip.

Stylesheets block rendering, so a cached copy removes the wait for returning visitors. This is also why the render blocking resources check matters here. It is browser side caching of one file type, so it does nothing for a first time visitor and isn't the full page cache described in server side page caching. Scripts follow the same logic in the JavaScript caching check.

Cache-Control and Expires in plain words

Cache-Control: max-age=N sets the lifetime in seconds and wins when a response carries both headers, while Expires is the older header with an absolute date. Our check accepts either. Adding public lets shared caches such as a CDN store the file, while private limits it to the visitor's browser, and stylesheets are normally public. Beyond those, no-cache doesn't mean do not cache: the browser stores the file but checks with the server before each reuse. No-store means never keep a copy, and immutable tells browsers the file won't change while it is fresh. The HTTP cache guide on web.dev shows how they combine.

Why return visits get faster

With a long lifetime the browser reads the stylesheet from disk instead of downloading it again on every page view, so a 90 KB stylesheet read from disk costs a returning visitor no request at all and the browser can start painting as soon as the HTML arrives, which also helps Largest Contentful Paint.

How SEO Site Checker Tests CSS Caching

Our audit loads your page in headless Chromium, waits for the load event and reads the rendered HTML. It keeps the first 8 link tags whose rel contains stylesheet, so a page with more than 8 stylesheets shows a total of 8, because only those are tested.

The exact rule: a stylesheet counts as cached when its response includes a Cache-Control header or an Expires header, matched without regard to letter case. We check that the header exists. We do not read the lifetime inside it.

Our server downloads each file with a plain HTTP request, following redirects, with a 3 second limit per file and about 10 seconds for the whole batch. The report prints a line such as "This webpage has total 5 Css links", then two expandable lists, Css Cache Links and Css Uncache Links, each with a count badge. The counts show for everyone, while the URLs inside the lists open only for logged in users. The red How to Fix button appears for Warning and Failed, and the check is one item in your score.

Pass, warning and fail at a glance

This is how each state looks in the report.

StatusWhat the report showsTypical cause
PassedTotal 5 Css links, 5 cached and 0 uncachedServer or CDN rules already cover .css files
WarningTotal 5 Css links, 3 cached and 2 uncachedThird party CSS, plugin generated CSS, or a timeout
FailedEvery file in the uncached list, or no stylesheet found at allNo rules for .css, or a page with inline CSS only

What this check does not measure

A Pass doesn't prove your lifetime is sensible. Lifetime length, CDN edge cache behavior and service worker caches sit outside the check, and so do inline style tags, @import rules and scripts that inject stylesheets after load. A stylesheet sent with no-store, no-cache or max-age=0 still counts as cached, because we only read header names. The Watch out note near the end explains why that matters. Lighthouse and PageSpeed Insights run a separate cache policy audit that flags short lifetimes, so a Pass here and a Lighthouse note can both be true.

Why a clean site can still show Warning

Third party stylesheets are a common cause: some outside hosts send their own cache header and pass, others don't, and you can't change theirs. A stylesheet that a theme or plugin generates through a PHP endpoint, such as style.php?ver=3, is another frequent culprit, because a rule written for the .css extension never touches it and the script must send its own Cache-Control.

Fetch problems look the same. A request that times out or refuses the connection counts as uncached, and a blocked request that returns an error page is judged by its headers like any other response. Files still downloading when the 10 second batch limit hits vanish from both lists.

Link form matters too. A protocol relative href such as //cdn.example.com/site.css fails to download, and a relative href is joined to the site root, so write absolute https:// URLs in your link tags.

Test my stylesheets

How to Check CSS Cache Headers Yourself

  1. Open DevTools. Use a private window so extensions and old copies can't hide the answer, open the Chrome DevTools Network tab with Disable cache unticked, filter by CSS, reload, click a stylesheet and read its Response Headers.
  2. Read the Size column. Reload again and look at the Size column. A cached file shows memory cache or disk cache instead of a byte count.
  3. Use the command line. Run curl -sI on the full stylesheet URL and look for the cache lines in the output.
  4. Cover every uncached URL. Repeat the check for each URL the report lists as uncached, including third party hosts you can't fix.
Text
$ curl -sI https://example.com/assets/style.8f3a1c.css
HTTP/2 200
content-type: text/css
cache-control: public, max-age=31536000, immutable

The -sI flag sends a HEAD request, and a few servers answer those differently. If the result looks odd, run curl -s -o /dev/null -D- followed by the URL to print the headers of a real GET.

How to Cache CSS Files on Apache, Nginx and WordPress

Pick your stack, add the rule, reload and test again, remembering that long lifetimes are only safe with versioned names.

Apache and LiteSpeed (.htaccess)

Option A uses mod_expires, which sends both Expires and Cache-Control max-age. The Apache mod_expires documentation lists the syntax.

Text
# Option A: mod_expires sends Expires and Cache-Control: max-age
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
</IfModule>

Option B uses mod_headers. Choose one option, not both, and add immutable only with versioned file names.

Text
# Option B: mod_headers sets the header on every .css file
<FilesMatch "\.css$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>

One gotcha: nothing is sent without ExpiresActive On, and the IfModule wrapper hides a missing module. LiteSpeed Enterprise reads .htaccess, while OpenLiteSpeed supports only some directives, so confirm with curl.

Nginx

The expires directive sets both headers in one line.

Text
# Stylesheets: Expires plus Cache-Control: max-age=31536000
location ~* \.css$ {
    expires 1y;
}

Any add_header line inside this block stops outer level add_header lines being inherited, so repeat your security headers here. If an earlier regex location already serves static files, put expires 1y there, because Nginx stops at the first match. Apply the change with nginx -s reload.

WordPress and caching plugins

A caching plugin is the easiest WordPress route. WP Rocket writes the expiry rules to .htaccess itself on Apache and LiteSpeed, if the file is writable and mod_expires is on, and on Nginx you add them by hand. LiteSpeed Cache and W3 Total Cache both have a Browser Cache option.

Check your host first, because managed hosting often sends headers already and plugin rules can double up. Then version your theme CSS with the file modified time. That gives a ?ver= query string, which beats a fixed version number.

PHP
function mytheme_styles() {
    $path = get_stylesheet_directory() . '/style.css';
    wp_enqueue_style( 'mytheme-style', get_stylesheet_uri(), array(), filemtime( $path ) );
}
add_action( 'wp_enqueue_scripts', 'mytheme_styles' );

Cloudflare and other CDNs

A CDN has two layers, the edge cache and your visitor's browser cache, and our check reads only what the browser receives. The CDN usage test shows whether you use one.

The Cloudflare Browser Cache TTL defaults to 4 hours. It replaces a lower origin header and leaves a higher one alone. Choose Respect Existing Headers only when your origin already sends the header, since it stops Cloudflare adding one. A cache purge in Cloudflare only clears the edge, and visitors' browsers keep their copy until its lifetime ends.

CSS Cache Busting: Safe Long Caching with Versioned Files

Long lifetimes feel risky, but renaming the file on every change removes the risk. A new URL is a new file to the browser, so cache invalidation happens by itself. If CSS is not updating after a deploy, an unversioned name under a long lifetime is the usual reason.

  • Hash the file name. Webpack contenthash and Vite output names build hashed file names from the file's content, so style.8f3a1c.css becomes style.2b9d40.css only when the CSS changes.
  • Query strings are second best. Query string versioning like style.css?v=2.4.1 works, but file name fingerprinting is safer because some cache rules and older proxies ignore query strings.
  • Add immutable. RFC 8246 defines it, and supporting browsers skip revalidation while the file is fresh. Pair it with hashed names.
  • Keep the HTML short lived. Send Cache-Control: no-cache on the document so it always points at the newest file, as the MDN cache busting section explains. While you build, tick Disable cache in DevTools instead of shortening production lifetimes.
  • Revalidate dynamic stylesheets. For a stylesheet that is genuinely dynamic, send Cache-Control: no-cache so the browser revalidates every time and still gets a cheap 304 when nothing changed.
  • Minify before you hash. Editing the CSS after hashing leaves the name out of step, so follow the CSS minification guide first.

Common CSS Caching Mistakes That Fail the Check

  • Rules in the wrong place. If curl still shows no cache-control line, remember an .htaccess edit does nothing on Nginx, and Apache ignores the rules when the module isn't loaded.
  • Rules that stop at your own folder. Third party stylesheets stay uncached and give Warning, as explained under why a clean site can still show Warning.
  • One year on an unversioned file. Returning visitors keep the old design until the lifetime ends. Version the name first.
  • Inline styles only. With no stylesheet link the report shows Failed because nothing exists to test.

Watch out: no-store on stylesheets stops browsers keeping any copy, yet our check still passes it because the header exists, so a green result can hide a real slowdown.

How to Fix

The short version, the same steps you see when you click How to Fix in your report.

Why this fails: one or more stylesheets on the page were served without a Cache-Control or Expires header, so browsers can't reuse them. Our report tests up to 8 stylesheets and needs a header on every one. Some missing gives Warning, none gives Failed, and a page with no stylesheet link also gives Failed.

How to fix it:

  1. Make sure your CSS file names change with each release, for example style.8f3a1c.css or a ?v= value, because the next rule keeps files for a year.
  2. Find out which server you run, then use only the matching block below. Pasting the Nginx block into .htaccess causes a 500 error.
  3. On Apache or LiteSpeed, connect over SFTP or open your hosting file manager, download a backup of the .htaccess file in the site root, paste the block that follows, and save.
  4. On Nginx, add the Nginx block below to your server config, then run nginx -s reload.

Apache or LiteSpeed only (paste into .htaccess):

Text
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
</IfModule>

Nginx only (add to your server config, never to .htaccess):

Text
location ~* \.css$ {
    expires 1y;
}
  • WordPress: WP Rocket adds the rules to .htaccess by itself on Apache and LiteSpeed, so check the file is writable and mod_expires is on, and add the rules by hand on Nginx. LiteSpeed Cache has a Browser Cache setting and W3 Total Cache has a Browser Cache tab.
  • Cloudflare: set a long Browser Cache TTL, and pick Respect Existing Headers only if your origin already sends the header.
  • Shopify and Wix: you usually can't edit server headers, so check which listed URLs come from apps or third parties. If the uncached URLs sit on another domain you cannot change their headers, so self-host the file or accept the Warning.

Re-test: run curl -sI on one stylesheet URL and look for a Cache-Control or Expires line. Then run the free SEO report again and look for Passed on CSS Caching. If no header appears, ask your host whether mod_expires is enabled.

Re-test my website

Frequently Asked Questions

What is CSS caching?
CSS caching is the browser storing a downloaded stylesheet and reusing it because the server sent a Cache-Control or Expires header. Returning visitors skip the download, so pages render sooner. It is browser side caching of one file type, separate from server side page caching.
How do I cache CSS files in the browser?
Make your web server or CDN send a Cache-Control max-age or an Expires header on every .css response. Apache uses mod_expires or mod_headers, Nginx uses the expires directive, and most WordPress sites rely on a caching plugin. Confirm the result in DevTools or with curl.
How long should I cache CSS files?
Use one year, which is max-age=31536000, when the file name changes with every edit, because a new name is a new URL. If the name never changes, send Cache-Control: no-cache so the browser revalidates with an ETag, or use a short max-age. Never pair a long lifetime with unversioned names.
How do I stop browsers from showing old CSS after an update?
Change the URL whenever the file changes, either with a content hash in the file name or a bumped version query string. In WordPress you can pass the file modified time as the version. Visitors then fetch the new file at once, as long as the page HTML is not cached for long either, so send no-cache on the document.
Are CSS files cached by default?
Not reliably. Browsers may guess a lifetime from the Last-Modified date, which is called heuristic caching, but without explicit headers the behavior is unpredictable, and unversioned files often trigger a conditional request on every visit. Explicit Cache-Control makes it dependable, and it is exactly what our check looks for.
How do I clear the CSS cache in Chrome?
For one page, use a hard reload with Ctrl+Shift+R, or Cmd+Shift+R on a Mac. To wipe everything, open Clear browsing data and tick cached images and files. While you develop, tick Disable cache in the DevTools Network tab. This affects only your browser.
Does CSS caching help SEO and Core Web Vitals?
Not as a direct ranking signal. Cached stylesheets make repeat views faster and ease render blocking, which supports Core Web Vitals. The Google Search Central documentation on JavaScript problems notes that Googlebot caches aggressively and may ignore caching headers, so file names that carry a content hash help too.

See how your site scores right now

Paste your address into the free report, then use the How to Fix button on anything that needs work.

Free to use. Results in under a minute for most sites.