WordPress cache expiration headers: what actually gets cached

Manufacture Date and Expiration Date.jpg

Written by

in

The newsletter for newsletter operators

Daily field notes on deliverability, AI tools, hosting, and monetisation. No "top 10 plugins" filler — real tools, real numbers, real failures.

WordPress cache expiration headers: what actually gets cached
Photo: Gaurav Dhwaj Khadka via Wikimedia Commons (CC BY-SA 4.0)

Most WordPress operators assume their caching plugin handles everything. It doesn’t. Cache-Control and Expires headers—set at the server or application level—determine what gets stored by browsers, proxies, and CDNs, and for how long. WordPress sets some of these by default. Others depend on your host, your caching plugin, or manual configuration.

If you’ve ever wondered why a CSS file updates instantly but a hero image sticks around for days, or why logged-in users see stale content, the answer is in the headers. Here’s what actually gets cached, what WordPress controls, and when you need to intervene.

What WordPress sets by default

Out of the box, WordPress sends Cache-Control: no-cache, must-revalidate, max-age=0 for most dynamic pages—posts, archives, and any page generated by PHP. This tells browsers and intermediaries not to cache the response without revalidation.

Static assets—images, CSS, JavaScript uploaded to /wp-content/uploads or enqueued via wp_enqueue_script—typically don’t get cache headers from WordPress itself. Your web server (Apache, Nginx) or host sets them. Most hosts default to one year (max-age=31536000) for images and fonts, and shorter windows (one week to one month) for CSS and JS.

If you’re on shared hosting without custom server config, you’re stuck with those defaults unless you use a plugin or CDN to override them.

Where caching plugins take over

Full-page caching plugins—WP Rocket, W3 Total Cache, LiteSpeed Cache—generate static HTML and serve it with their own headers. Most set Cache-Control: public, max-age=3600 (one hour) or longer for cached pages, and private, no-cache for logged-in users.

The problem: if your plugin sets a one-hour cache but your CDN or browser already cached the page for 24 hours, the shorter directive won’t matter. Cache layers stack. The longest-lived cache wins until it expires or gets purged.

Check what’s actually being sent by opening DevTools, loading a page, and inspecting the response headers under the Network tab. Look for Cache-Control, Expires, and Age. If Age is present, the response came from a cache. If it’s missing, it’s a fresh hit.

When to override the defaults

Override cache headers when:

  • You version assets manually. If you append ?v=2 or use a build hash in filenames, set a long max-age (one year). The filename or query string will bust the cache when you update.
  • You serve user-specific content. Set Cache-Control: private so CDNs and shared proxies don’t serve one user’s view to another. Cookies usually trigger this automatically, but not always.
  • You publish time-sensitive content. If your site updates every few minutes (live scores, stock tickers, event countdowns), set max-age=60 or lower and pair it with s-maxage for CDN-specific caching.
  • Your CDN and origin disagree. Some CDNs (Cloudflare, for example) respect origin headers by default but let you override them with page rules. If your origin says “cache for one hour” but your CDN caches for 24, you’ll serve stale content unless you configure the CDN to honor the shorter window or purge on publish.

The non-obvious detail: stale-while-revalidate

Modern browsers and CDNs support stale-while-revalidate, a directive that serves cached content even after it expires while fetching a fresh copy in the background. If you set Cache-Control: max-age=600, stale-while-revalidate=300, the cache serves the page for 10 minutes, then for another 5 minutes while revalidating. The user never waits, and your server gets fewer simultaneous requests.

Most WordPress caching plugins don’t expose this setting. You’ll need to add it via your host’s control panel, a custom Nginx/Apache config, or a CDN rule. It’s worth it if you publish irregularly and want to keep pages fast without manual purging.

How to check what’s actually cached

Run a quick test:

  • Open an incognito window and load your homepage.
  • Open DevTools → Network → reload the page.
  • Click on the document request (usually the first row) and check the Response Headers.
  • Look for Cache-Control, Expires, Age, and X-Cache (CDN-specific).

If you see max-age=0 on a static page, your caching plugin isn’t running or isn’t configured for that route. If you see Age: 86400 (24 hours in seconds), the page was cached a day ago and hasn’t been purged.

Repeat the test logged in. If the headers are identical, you’re serving cached pages to authenticated users—a problem if your site shows user-specific content or admin bars.

Want more infrastructure breakdowns like this? Subscribe to One Two Three Send for weekly deep-dives into the systems that run online businesses—no fluff, just the details that matter.

The newsletter for newsletter operators

Daily field notes on deliverability, AI tools, hosting, and monetisation. No "top 10 plugins" filler — real tools, real numbers, real failures.

Other newsletters you might like

Love Paris

Love Paris — in your inbox Iconic landmarks, hidden gems and the best places to visit in Paris. One short email, every day.

Subscribe

Scottish Rugby Fans

The best Scottish rugby updates, straight to your inbox — Six Nations, the Nations Championship, Glasgow and Edinburgh. Only when there's something worth reading.

Subscribe

Love London

A newsletter for Londoners who want to rediscover their own city. Travellers planning their first or fifth visit. Anglophiles who fell in love with London through literature, film, or a rainy afternoon on the South Bank.

Subscribe

One Two Three AI

One Two Three AI — in your inbox AI news, practical tips and how-to guides. One useful idea a day.

Subscribe

Newsletters via the One Two Three Send network.  ·  Want your newsletter featured here? Click here