SSL Checker DMARC Meta Tags Site Speed Broken Links AI Chat Bookings Try ModusOp

Published 27 April 2026

Browsers and CDNs cache resources to avoid re-downloading them on every visit. The HTTP headers your server sends determine what gets cached, for how long, and under what conditions. Configured well, returning visitors load your site in well under a second; configured poorly, every visit re-downloads megabytes that haven't changed.

This guide covers the headers that matter, the typical strategy for different resource types, and the common mistakes that quietly defeat caching.

The Headers That Matter

Cache-Control

The primary header. Tells caches (browsers, CDNs, intermediate proxies) how to handle the response.

Cache-Control: public, max-age=31536000, immutable

Common directives:

ETag

A version identifier for the resource. Hashed from the content, or a sequence number, or anything else that changes when the resource changes.

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

When a cache has a stale copy, it asks the origin: "do you have version ? Or has it changed?" If the origin's current ETag matches, it returns 304 Not Modified (with no body) and the cache reuses its copy. If different, it returns 200 with the new content.

Last-Modified

An older alternative to ETag. The date the resource was last changed.

Last-Modified: Wed, 27 Apr 2026 06:00:00 GMT

Caches send If-Modified-Since with the date they have; the origin returns 304 if nothing newer.

ETag is more precise (handles changes within the same second, regenerated content with same modify time). Last-Modified is simpler. Most modern stacks send both.

Vary

Tells caches that the response varies based on certain request headers.

Vary: Accept-Encoding

Means: the cached response depends on the value of Accept-Encoding. The cache must store separate copies for gzip and brotli clients. Without this, a brotli-compressed response might be served to a non-brotli client and break.

The Strategy: Cache by Resource Type

Different resources need different cache strategies:

Static assets with content-based filenames

JavaScript bundles, CSS files, images with hashed filenames like main.a3f5b7.js. The filename changes whenever the content changes (typical of webpack, Vite, etc. build outputs).

Cache-Control: public, max-age=31536000, immutable

One year. Immutable. Browsers won't even check for updates — when the file changes, you ship a different filename.

Static assets without versioned filenames

Images uploaded via CMS, PDF documents, anything where the URL is stable but the content might change.

Cache-Control: public, max-age=86400, must-revalidate
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

One day. Must revalidate. ETag lets revalidation be cheap — origin returns 304 if unchanged.

HTML documents

The page itself. Content changes; you want users to see new versions but not constantly re-download unchanged pages.

Cache-Control: public, max-age=300, s-maxage=86400, must-revalidate

Browsers cache for 5 minutes (returning visitors within that window get instant loads). CDNs cache for 1 day (so even first-time visitors get fast responses from edge nodes). Must-revalidate ensures stale content gets refreshed.

API responses

Personalised or rapidly-changing data — cart contents, user profiles, dynamic search results.

Cache-Control: private, no-store

Don't cache. Every request hits the origin. Appropriate for anything user-specific.

Or, for cacheable but personalised:

Cache-Control: private, max-age=60

Browser caches for 60 seconds; CDNs don't cache (private). Returning user gets instant response within the minute.

Authenticated pages

Logged-in dashboards, account pages.

Cache-Control: private, no-cache

Browser may cache, but must revalidate (so logout state is respected immediately). CDNs don't cache.

Common Mistakes

1. No caching headers at all

Server doesn't send Cache-Control. Browsers apply heuristics (typically 10% of the time since Last-Modified). Behaviour is unpredictable. Always send explicit headers.

2. no-cache on static assets

"no-cache" means revalidate with origin every time, not "don't cache". For static assets, this still means an HTTP round-trip per request even when nothing has changed. Use max-age for actual caching.

3. Long max-age on HTML

HTML documents cached for 1 year. Now content updates take a year to reach returning users. Use short max-age (300-3600s) on HTML; rely on revalidation.

4. Missing Vary: Accept-Encoding

Server compresses with brotli for clients that support it, gzip for others. Without Vary, intermediate caches may serve brotli content to gzip clients (or vice versa) and break things.

5. Caching personalised content as public

"Welcome back, Sarah!" cached publicly on the CDN. Now everyone sees Sarah's name. Always use private for user-specific content.

6. Forgetting CDN purge after deploys

HTML cached at the CDN for 1 day. You deploy a fix at 3pm. Nobody sees it until 24 hours later. Either set short s-maxage or purge the CDN cache as part of deployment.

7. Inconsistent ETags

Server generates a different ETag each request (timestamp-based, or randomly). Caches always think the resource has changed; revalidation always returns 200, never 304. Fix: ETags must be content-based and stable.

Service Workers and Custom Caching

For modern web apps, service workers offer programmable caching beyond what HTTP headers can express:

Frameworks like Workbox simplify service worker caching. For sites that need offline support or app-like performance, service workers are the next layer beyond HTTP caching.

Verifying Your Caching

Three checks:

  1. curl with -I to see headers: curl -sI https://example.com/style.css | grep -i cache.
  2. Chrome DevTools Network panel — look at the "Size" column. "(disk cache)" or "(memory cache)" means the request was served from cache without touching the network.
  3. Site Speed Check reports caching headers and flags issues.

For deeper debugging, look at the Cache-Control, ETag, and Vary headers in DevTools and verify they match what you intended.

The Practical Configuration

For most sites, this gets you 95% of the way:

Set these in nginx, Apache, or your CDN of choice. Test with curl. Verify with Site Speed Check. Returning visitors will load your site near-instantly, and your origin server will see a fraction of the request volume.

Verify your caching

Site Speed Check reports cache headers, age, and revalidation behaviour for every resource on a page.

Run a Speed Test →