How to Exclude a Page from Cache in WordPress
Excluding a page from the plugin cache is half the job. The host cache and the CDN keep their own copies, so a page you excluded once can still be served stale from two other places.
Some links on this page are affiliate links: if you buy through one, we may earn a commission at no extra cost to you. It does not change what we recommend; see the editorial policy.
Short version: exclude the page at every layer that caches it — the plugin, the server, the CDN — and confirm with response headers. An exclusion in one layer does nothing about the copy sitting in the next.
Which pages must never be cached
A page cache stores one HTML response and hands it to everyone. That is fine for an article and wrong for anything that differs per visitor or per request.
- Cart, checkout and my-account. One customer's cart served to another is the classic failure, and stale checkout pages carry expired security tokens — see checkout not working.
- Pages with forms that use nonces. A cached nonce expires after a day or so, and every submission then fails with errors like the link you followed has expired.
- Personalised pages — dashboards, member areas, "hello, name" headers, recently viewed products.
- Geo- or currency-dependent pages, where the first visitor's country decides what everyone else sees.
- Pages that change on every request, such as a live availability calendar or a payment return URL.
Logged-in users are usually skipped by the page cache entirely, which is why these bugs show up only for anonymous visitors and are hard to reproduce while you are signed in.
Layer 1: the page cache plugin
Every caching plugin has a field for this, named something like "Never cache URLs", "Exclude pages" or "Do not cache". Enter paths, not full URLs, one per line:
/checkout/
/booking/confirm/
/members/
/members/(.*)
Most plugins accept regular expressions, so /members/(.*) covers every page under that path — but not /members/ itself, which needs its own line. Do not use a bare (.*) or a pattern that matches the homepage — one careless line disables the cache for the whole site.
The same settings screen usually has a cookie exclusion list. That is the right tool when the page is the same URL for everyone but the content depends on state: skip the cache whenever a cookie such as woocommerce_items_in_cart or a membership session cookie is present.
WP Rocket, for example, has a "Never Cache URL(s)" field under Advanced Rules that accepts patterns like /page/(.*), plus separate fields for cookies and user agents, and excludes the WooCommerce cart, checkout and account pages on its own. Other plugins differ in naming, not in principle — choosing a cache plugin covers the differences that matter.
After saving, purge the cache. The exclusion stops new copies from being made; it does not delete the one already stored.
Layer 2: from code, with DONOTCACHEPAGE
When a page should be uncached because of what it contains rather than where it lives — a shortcode, a template, a condition — set the DONOTCACHEPAGE constant before any output. Most page-cache plugins check for it before saving the response.
<?php
// Never cache pages that contain the booking form, wherever they live.
// has_shortcode() only sees post content: use has_block() or a template check for blocks and builders.
add_action('template_redirect', function () {
if (is_singular() && has_shortcode(get_post()->post_content ?? '', 'booking_form')) {
if (! defined('DONOTCACHEPAGE')) {
define('DONOTCACHEPAGE', true);
}
}
});
This travels with the content. Move the form to another page and the exclusion follows it, which a URL list cannot do. Purge the page once after deploying this: the old copy is served before WordPress, and the constant, ever runs.
Layer 3: response headers for everything downstream
The constant is a convention between WordPress and its plugins. A server cache or a CDN never sees it. What they do see is the Cache-Control header, so send one that forbids storage:
<?php
add_action('template_redirect', function () {
if (is_page('booking-confirm')) {
nocache_headers();
header('Cache-Control: no-store, private', true);
}
});
If a page-cache plugin is active, pair this with DONOTCACHEPAGE or a never-cache rule and purge: a page already in the plugin's cache is served without running this code, so the header is never sent.
nocache_headers() is the core helper WordPress itself uses for the admin and login screens. Since WordPress 6.8 it already sends no-store, private for every visitor; the explicit header() line only matters on older versions. Most server caches respect it out of the box; some are configured to ignore application headers, which is why you still check the next layer.
Layer 4: the host's server cache
Managed hosts and many control panels run Nginx FastCGI cache, Varnish or LiteSpeed in front of WordPress. These keep their own copy regardless of your plugin settings.
- Look in the hosting panel for a cache section with an exclusions field.
- If there is none, ask support to add the path to the bypass list — some managed hosts only apply exclusions through support.
- Confirm they also bypass on the WooCommerce and login cookies.
If you are not sure a server cache exists at all, checking the HTTP headers usually reveals it through headers like X-Cache, X-Proxy-Cache or X-LiteSpeed-Cache.
Layer 5: Cloudflare and other CDNs
By default Cloudflare does not cache HTML. If someone set up a "Cache Everything" page rule or an "Eligible for cache" Cache Rule to make the site faster, it does — and it will happily cache the checkout. Add a Cache Rule that matches the path (or the presence of a session cookie) with Cache eligibility set to Bypass cache, and place it below the rule that makes HTML eligible for cache — in Cache Rules the last matching rule wins. (Legacy Page Rules work the other way: first match wins, so there it goes on top.) The wider trade-offs are in Cloudflare and WordPress.
Summary by layer
| Layer | How to exclude | How to verify |
|---|---|---|
| Page cache plugin | Never-cache URL or cookie field | Plugin's HTML comment or header disappears |
| Code | DONOTCACHEPAGE before output |
Same as above, on matching pages only |
| Headers | nocache_headers() plus no-store |
Cache-Control shows no-store |
| Server cache | Panel exclusion or support ticket | X-Cache style header reads MISS or BYPASS |
| CDN | Cache Rule set to bypass | cf-cache-status: BYPASS or DYNAMIC |
Verify
Load the page twice while logged out and compare headers:
curl -sI https://example.com/booking/confirm/ | grep -iE 'cache|age|x-'
curl -sI https://example.com/booking/confirm/ | grep -iE 'cache|age|x-'
A cached page shows HIT on the second request and often an Age header that grows. An excluded page shows MISS, BYPASS or DYNAMIC both times, with no Age header, or Age: 0. If one layer still reports a hit, that is the layer to fix — and remember to purge the stale copy there once the exclusion is in place.
Common mistakes
- Excluding in the plugin only, while the host cache keeps serving yesterday's checkout.
- Testing while logged in, where most caches are skipped anyway and everything looks fine.
- A regex that is too broad, quietly turning the cache off for half the site.
- Forgetting to purge after adding the exclusion, so the old copy lives until it expires.
- Excluding a slow page to hide a bug. If a page only works uncached, the real problem is usually a stale nonce or a session cookie the cache should have bypassed on.
Frequently asked
- No. Every other URL keeps being served from cache. Only the excluded page runs PHP on every request, so keep the list short and exclude by exact path rather than broad patterns.
- Another layer is holding a copy, usually the host's server cache or a CDN rule that caches HTML. Check the response headers to see which layer answered, then add the exclusion there too.
- Usually not in a page-cache plugin, since most detect WooCommerce and exclude those pages automatically. Host caches and CDN rules do not always know about them, so verify each layer.