There are four caching layers on a WordPress site, they cache different things, and stacking two of the same kind causes bugs rather than speed.
| Layer | What it stores | Who usually provides it |
|---|---|---|
| Page cache | The finished HTML of a page | A plugin, or your host at server level |
| Object cache | The results of database queries | A persistent backend — Redis or Memcached — plus a drop-in |
| Browser cache | Static files on the visitor's device | HTTP headers from your server |
| Edge cache / CDN | Copies near the visitor | A CDN in front of your site |
Most "caching problems" are one of two things: a missing layer, or two plugins doing the same layer.
Start by finding out what you already have, because a great many managed hosts run a page cache at server level, and installing a caching plugin on top of it is the single most common way to make a WordPress site slower and stranger at the same time.
Why this matters more than it sounds
WordPress does not mince words about the consequences of no caching at all:
"if you don't use a caching solution, performance will slow to a halt as additional page requests come in and stack up, often crashing your web or database server."
That is not a speed argument, it is an availability one. Caching is what stops a traffic spike turning into an outage.
And on the upside, WordPress's own optimisation guidance says caching "will cache your WordPress posts and pages as static files" and can improve performance "several hundred times over for fairly static pages" — while calling it "the biggest benefit for the smallest hassle."
Layer 1 — Page caching
What it does: stores the finished HTML so the next visitor gets a file instead of a PHP process and a dozen database queries.
This is the layer with the largest effect for a content site, and for most WordPress blogs it is the only one that is strictly necessary.
What it does not cache, by design:
- Logged-in users, because their page is personalised
- Carts and checkouts, and anything else session-dependent
- Anything with query parameters, on most configurations
- Forms and dynamic widgets, unless the cache supports fragment exclusion
The two rules:
- Run exactly one page cache. Server-level or plugin, not both
- Know what invalidates it. Publishing a post should clear the affected pages; a comment usually should too; a price change certainly should
How to check whether you already have one: load a page in a private window and look at the response headers for a cache header — x-cache, cf-cache-status, x-litespeed-cache and similar are all page caches announcing themselves. If one is present, you have this layer, and a caching plugin's page-cache module should stay off.
Layer 2 — Object caching
What it does: stores the results of database queries in memory so WordPress does not ask the same question repeatedly within and across requests.
WordPress's own framing: "Using a persistent Object Cache helps speed up page load times by saving on trips to the database from your web server."
The word doing the work is "persistent." WordPress always has an object cache — but by default it lives only for the duration of a single request, so nothing carries over. A persistent object cache keeps it in Redis or Memcached between requests, which is where the gain is.
This is the layer that matters most for the pages a page cache cannot serve: the admin, logged-in users, WooCommerce carts, membership areas. A shop or membership site with no persistent object cache is doing the same database work on every single request from every logged-in user.
For a small brochure site it is close to irrelevant. For anything with logged-in users it is often the largest available win, and it is the one most speed guides skip entirely because it usually needs the host to provide the backend.
Layer 3 — Browser caching
What it does: tells the visitor's browser to keep static files — images, CSS, JavaScript, fonts — instead of re-downloading them.
It does nothing for a first-time visitor and a great deal for a returning one, which is worth remembering when a test tool complains about it. Your PageSpeed test is always a first-time visitor.
It is configured with HTTP headers, not usually with a plugin. Most hosts set sensible defaults; a CDN will normally override them.
The one thing to get right is versioning. Long cache lifetimes are only safe when file names change on update — which WordPress handles for enqueued assets through the version query string, and which is why hand-linked files in a theme are the ones that go stale for returning visitors.
Layer 4 — Edge caching and CDNs
What it does: keeps copies of your files, and sometimes your pages, on servers near your visitors.
Two distinct jobs, often confused:
- A CDN for static assets serves images, CSS and JS from a nearby location. Nearly always worth it
- Full-page edge caching serves the HTML itself from the edge. A much bigger win — it can remove your server from the path entirely — and a much bigger source of surprises, because now cache invalidation happens somewhere you do not control
The rule for full-page edge caching: be sure you can purge it, and be sure it is excluding logged-in sessions and carts. This is where "I updated the page and it still shows the old version" comes from.
The fifth thing, which is not caching
Transients get filed under caching and behave differently enough to deserve a warning.
A transient is a value stored with an expiry — WordPress describes the API as "very similar to the Options API but with the added feature of an expiration time." Plugins use them constantly, for API responses, feed data, and computed values.
The part that catches developers, in WordPress's own words:
"transient expiration times are a maximum time. There is no minimum age. Transients might disappear one second after you set them, or 24 hours, but they will never be around after the expiration time."
So expiry is a ceiling, not a promise. WordPress adds that "it is possible for the transient to not be available before the expiration time", and that "your code should have a fall back method to re-generate the data if the transient is not available."
And with an external object cache, transients may not be in the database at all — WordPress warns never to assume they are stored there. Which is why "delete the transients from wp_options" is advice that does nothing on a site running Redis.
The practical version for a site owner: an accumulation of expired transients in wp_options is a symptom of a plugin behaving badly, and it is worth checking against the autoload figure in how to audit your WordPress plugins.
The newest layer: speculative loading
WordPress 6.8 shipped speculative loading in core, using the Speculation Rules API. The browser starts loading a page before the visitor has finished clicking the link to it.
The default is deliberately cautious: prefetch mode with conservative eagerness, which means "prefetching is triggered when a user starts to click on a link." WordPress chose it as "a reasonable starting point to enable speculative loading at the scale of WordPress", because it "minimizes the chance of any speculative loads without a subsequent navigation to the URL."
Where it does not apply, by default:
- Logged-in users
- Sites with pretty permalinks disabled
- Any URL containing query parameters, which are "excluded from prefetching and prerendering automatically"
And the caveat if you turn it up. In prerender mode, "even their client-side code will be loaded" — so analytics, chat widgets and anything else client-side may fire for a page the visitor never actually opened, unless that code checks whether it is being prerendered first.
Treat the default as the setting. The aggressive modes are for people who can audit their own third-party scripts.
Setting the layers up, in order
Six steps.
Step 1 — Find out what you already have
Check the response headers in a private window for a cache header. Check your host's control panel for a caching or performance section. Check whether a caching plugin is already installed, including one bundled by your host.
This step exists because skipping it is how sites end up with two page caches.
Step 2 — Get exactly one page cache running
Server-level if your host offers it; a plugin if not. If both exist, turn off the plugin's page-cache module and leave the rest of its features alone.
Step 3 — Add a persistent object cache if you have logged-in users
Requires Redis or Memcached from your host, plus a drop-in to connect WordPress to it. Skip this entirely if your site is a brochure site with no logged-in traffic — it will do almost nothing.
Step 4 — Confirm browser caching headers on static assets
Usually already correct. Worth confirming rather than configuring.
Step 5 — Put a CDN in front of static assets
Then, only if you are confident about purging, consider full-page edge caching — and exclude logged-in sessions, carts and checkouts explicitly.
Step 6 — Test as a logged-out visitor, twice
Private window, first load and second load. Then log in and check the site still behaves. Then buy something, if you have a shop.
That last one is not optional. Cache misconfiguration on a WooCommerce site shows up as one customer seeing another's cart, and it is discovered by testing or by complaint.
When caching is the problem
Symptoms that mean the cache, not the code:
- Changes not appearing after publishing or editing
- Logged-in users seeing the logged-out version, or the wrong user's name
- A cart that empties or shows someone else's items
- Forms failing with a nonce or security error, because a cached page carried a stale token
- The admin bar appearing for visitors who are not logged in
The first diagnostic is always the same: purge everything, at every layer, then retest. If the symptom disappears, it was cache. If it returns on a schedule, something is caching a page it should be excluding.
And if a page will not stop being wrong, work outwards: plugin cache, server cache, CDN. The layer you forgot is usually the outermost one.
Frequently asked questions
What is WordPress caching?
Storing the result of work so it does not have to be repeated. There are four layers: page caching stores finished HTML, object caching stores database query results, browser caching stores static files on the visitor's device, and edge caching stores copies near the visitor.
Do I need a caching plugin if my host has caching?
No — you should not run two page caches. If your host caches at server level, leave the plugin's page-cache module off. Check your response headers for a cache header to find out which you have.
What is a persistent object cache in WordPress?
An object cache that survives between requests, backed by Redis or Memcached. WordPress always has an in-request object cache, but without a persistent backend nothing carries over. It matters most for logged-in traffic, which a page cache cannot serve.
Why are my changes not showing on my site?
Almost always caching. Purge every layer — plugin, server, CDN — and retest. If the change appears, one of those layers was holding a stale copy; the layer people forget is usually the CDN.
Do transients expire exactly when I set them?
No. WordPress states that expiration times are a maximum with no minimum age — a transient might disappear a second after being set. Code using them needs a fallback to regenerate the data if it is missing.
Can caching break WooCommerce?
Yes, if carts, checkouts and account pages are not excluded. The symptoms are shared carts, stale prices and failed checkouts. Always test a real purchase after changing any caching layer.
What is speculative loading in WordPress?
Core support, added in 6.8, for loading a page before the visitor finishes clicking. The default is prefetch with conservative eagerness, disabled for logged-in users and sites without pretty permalinks, with query-parameter URLs excluded automatically.
Is prerendering safe to enable?
Only with care. WordPress warns that in prerender mode even client-side code is loaded, so analytics and widgets can fire for pages nobody visits unless that code checks whether the page is being prerendered first.
What to do next
Open your site in a private window and look at the response headers for a cache header. That one check tells you whether you already have a page cache and therefore whether you need a caching plugin at all.
If a header is there and a caching plugin is also running its own page cache, turning one of them off is likely the fastest improvement available to you today.
Related guides
- Speed up WordPress: what actually moves the number — where caching sits among the levers
- Core Web Vitals for WordPress — the metrics caching moves
- How to audit your WordPress plugins — including transient and autoload bloat
- Choosing WordPress hosting for speed — which layers your host should provide
- When WordPress breaks: a diagnostic order — when purging does not fix it
- WooCommerce emails not sending — a different WooCommerce failure entirely
The short version
- Find out what you already haveCheck response headers in a private window for a cache header, check the host's control panel, and check for a bundled caching plugin. Skipping this is how sites end up with two page caches.
- Get exactly one page cache runningServer-level if your host offers it, a plugin if not. If both exist, turn off the plugin's page-cache module and leave its other features alone.
- Add a persistent object cache if you have logged-in usersRequires Redis or Memcached plus a drop-in. Skip it for a brochure site with no logged-in traffic u2014 it will do almost nothing.
- Confirm browser caching headers on static assetsUsually already correct, and worth confirming rather than configuring. Long lifetimes are only safe when file names change on update.
- Put a CDN in front of static assetsConsider full-page edge caching only if you are confident about purging, and exclude logged-in sessions, carts and checkouts explicitly.
- Test as a logged-out visitor, twice, then logged inPrivate window, first and second load. If you run a shop, complete a real purchase u2014 cache misconfiguration shows up as one customer seeing another's cart.
The WordPress Email Delivery Checklist
Stop your WordPress emails from failing silently. Get the complete setup guide.

Muhammad Basim has worked in digital marketing since 2013, focused on email deliverability and AI-assisted content production. He is the author of The Email Deliverability Playbook and The Email Copywriting Playbook.
Related Articles

Choosing a WordPress SEO Plugin
This page names no winner, and the reason is that the choice matters far less than the category's marketing implies. The major WordPress SEO plugins do the same six things. They have converged, they will continue to converge, and the difference between them is interface preference and a small number of features most sites never […]

Two-Factor and Login Hardening for WordPress
Login attacks against WordPress are constant, automated and indiscriminate. Every public WordPress site receives them, regardless of size or traffic, because the cost of trying is near zero. They are also the least successful route into a WordPress site. Vulnerable plugins account for more compromises than password guessing, which is worth holding onto — login […]

WordPress Email: Why Your Site’s Emails Never Arrive
WordPress sends email in a way that fails modern authentication by default, and it reports success while doing it. That combination is why this problem is so persistent. The site is not broken. Nothing is logged. wp_mail() returns true, the order is placed, the form says thank you — and the message never reaches an […]

