# WordPress Cache Exclusions in 2026: Adding an Asterisk Makes the Rule Match Less

> Cache exclusions fail quietly. The four fields match differently, an asterisk narrows a rule, and the query field is an allow-list that runs first.

- Published: 2026-10-02
- Updated: 2026-10-04
- Author: xSpeed Cache Team
- Tags: Caching, Exclusions, Tutorial, WooCommerce, WordPress, check:D2, platform:woocommerce
- Canonical: https://xspeedcache.com/blog/wordpress-cache-exclusions-setup/

---

Updated October 2026

WooCommerce's own [caching documentation](https://developer.woocommerce.com/docs/best-practices/performance/configuring-caching-plugins), read on 2 October 2026, names exactly three pages that must stay dynamic: Cart, My Account, Checkout. [LiteSpeed's documentation](https://docs.litespeedtech.com/lscache/lscwp/cache/) warns on the same day that excluding one cookie which appears site-wide "effectively excludes your entire site from caching". So the list of paths is the easy half.

The hard half is that the four exclusion fields do not match the same way, and nothing on the settings screen says so. A list of paths to paste is the guide you expect. Which field matches what string is the guide that decides whether your rule catches nothing or un-caches everything.

## Quick Summary: Which Field Catches What

| If you want to… | Use this field | Why |
|:---|:---|:---|
| Keep one page always dynamic | Excluded URLs | Matched against the path only, never the query string |
| Keep one *visitor* dynamic | Excluded Cookies | Matched against the cookie name only, never its value |
| Keep a bot or monitor dynamic | Bypass User Agents | Case-insensitive substring, no wildcards at all |
| Stop campaign tags duplicating a page | Ignored Query Parameters | An allow-list, matched on whole names |

One rule governs the first three: a plain entry means "contains", and adding `*`, `?` or `[` switches it to anchored whole-value matching. The fourth field inverts the logic entirely.

## Four Fields, Three Matching Rules

Every field takes one pattern per line, and our [Page Cache documentation](https://xspeedcache.com/docs/page-cache/) lists 16 shipped URL exclusions covering `/wp-admin/`, `/wp-json/`, `/feed/`, sitemaps, `/wp-login` and the standard store pages. Three different matching rules are in play, and the differences are deliberate.

| Pattern style | Behaviour | Example |
|:---|:---|:---|
| Plain text | Matches anywhere in the value | `/cart` also catches `/cart/items` and `/foo/cart/bar` |
| Glob (`*`, `?`, `[abc]`) | Anchored, the whole value must match | `/cart/*` catches `/cart/items` but not `/foo/cart/bar` |
| `~` prefix | Raw regular expression, unanchored | `~wp-.*\.php` |

![Four exclusion fields mapped to the string each one is matched against: path, cookie name, user-agent header and parameter name](https://xspeedcache.com/images/blog/cache-exclusion-fields.webp)

Excluded URLs and Excluded Cookies use that glob matcher. Bypass User Agents does not: it is a plain case-insensitive substring test, and [`class-glob-matcher.php`](https://plugins.svn.wordpress.org/xspeed/trunk/includes/class-glob-matcher.php) in our published source gives the reason, that user-agent strings vary so wildly that anchoring them "rarely helps and confuses users". Ignored Query Parameters goes the other way and matches whole names.

## Why the Cart Default Has No Trailing Slash

Our shipped default is `/cart`, not `/cart/` and not `/cart/*`. That is deliberate, and the reason is the contains rule above.

A bare `/cart` catches `/cart`, `/cart/` and `/cart/items` together. Write `/cart/` instead and a visitor landing on `/cart` with no slash gets a cached page. Write `/cart/*` and the pattern becomes anchored, so it no longer catches `/cart` itself, which is the page holding the basket.

> **An asterisk narrows an exclusion, it does not widen one.** Everyone reads `*` as "and everything under here". Here it means "and nothing outside here".

Cookie matching keeps contains semantics for a separate reason, stated in the same file: the shipped defaults are prefixes of hash-suffixed real cookies, so `comment_author` has to catch `comment_author_<hash>`. Tightening that to an exact match would serve shared cached pages to commenters, and [cached pages showing logged-in content](https://xspeedcache.com/blog/cached-pages-showing-logged-in-content/) is the fix guide for that symptom.

## The Query Field Is an Allow-List, and It Runs First

The other three fields are deny-lists: an entry means "do not cache this". Ignored Query Parameters is the opposite. It lists the keys that are safe to **strip** before the cache key is computed, so `/post?utm_source=x` and `/post` share one entry. The defaults cover `utm_*`, `fbclid`, `gclid` and friends.

The consequence catches people out. Any parameter *not* on that allow-list makes the request uncacheable. Reading [`Cache::should_cache()`](https://plugins.svn.wordpress.org/xspeed/trunk/includes/class-cache.php) on 2 October 2026, the query gate runs **before** the URL gate, so `/cart/?add-to-cart=12` is reported as a `query-param` bypass and never reaches the URL rule at all.

WooCommerce's documentation recommends a Varnish rule matching `?add-to-cart=`. You do not need its equivalent here, and could not write it in the URL field anyway: that field sees the path only. The source says why, that a visitor writes the query string, so `/cart/?utm_source=/feed/` must not be able to talk a cart out of its hold. Two more properties:

- Matching is whole-name, so an entry of `ref` matches `ref` and will not swallow `preference`.
- `xspeed_css` and `xspeed_nc` can never be ignored by any entry, because our own measurement requests use them to ask for the page as it is before optimisation.

## Where Your Rule Actually Runs: The Three-Layer Test

Here is the framework this article is for. Before trusting any exclusion, ask where it is enforced, because a cached page can be served by three different layers and only one of them runs your full rule list.

| Layer | Runs | Reads your settings from |
|:---|:---|:---|
| Web server (nginx or Apache) | Before PHP starts | A generated config block |
| The `advanced-cache.php` drop-in | Before WordPress loads | Regexes baked in at save time |
| PHP `Cache::should_cache()` | Full WordPress context | The live settings |

![Three enforcement layers for a cache exclusion, showing which rule types each layer can see and where raw regex patterns drop out](https://xspeedcache.com/images/blog/cache-exclusion-enforcement-layers.webp)

The drop-in cannot read your settings, because WordPress is not loaded yet. So the cookie and user-agent lists are compiled into it when you save, and `class-server-rules.php` is explicit about why: before it existed, every cookie and user-agent entry "applied only while a page was cold", meaning the settings screen said the rule was active and on a warm page it was not.

The limitation, stated plainly: this test tells you *where* a rule is enforced, not whether the pattern is right. A pattern matching nothing is enforced perfectly at all three layers.

Two things fall out of it. Raw `~` regex patterns are deliberately skipped at the web-server layer, because arbitrary user regex inside an nginx config is how you take down every site on the box, so those patterns are enforced by PHP only. And the arrangement is safe by design: xSpeed caps a generated server rule at 100 patterns and merges three session cookie names in as a floor, so that as the same file puts it, PHP remains the authority and a stale server config "can only ever cost speed, never correctness".

## Four Mistakes That Un-Cache More Than You Meant

- **Excluding a cookie that exists on every page.** LiteSpeed's own documentation warns that this "effectively excludes your entire site from caching", and it is just as true here. `woocommerce_recently_viewed` is the trap: it sounds cart-related and it is set almost everywhere.
- **Writing `*Googlebot*` in the user-agent field.** That field is a substring test, so the asterisks are literal characters and the entry matches nothing. Write `Googlebot`.
- **Pasting a LiteSpeed list unchanged.** Per its documentation, LiteSpeed compares URI rules against `REQUEST_URI` and narrows them with `^` and `$`. Ours see the path and use glob syntax, so a pasted `^/cart` is a literal caret. Prefix it with `~` to keep it a regex.
- **Excluding a page that is merely stale.** Our own docs are blunt about this one: exclusions are "a safety tool, not a tuning knob", and adding `/blog` because one post looked stale un-caches your entire blog. Purge first.

## Confirming the Rule Fired

Start with the free [xSpeed Scan](https://xspeedcache.com/scan/). It needs no account and no plugin, and the check to read is **D2, "Page served from cache"**, confirmed against a live scan on rubric 2026.10.1 on 2 October 2026. Point it at the page you excluded and a *failing* D2 is the correct result: nothing is serving that page from a cache. Point it at an ordinary post and D2 should pass. Two scans settle it.

Run the free scan on your own site: [xspeedcache.com/scan/](https://xspeedcache.com/scan/)

If you would rather check by hand, read the `x-xspeed-cache` response header. `BYPASS` means the request was deliberately never cached, `HIT (nginx)` means the server answered without starting PHP, and `MISS` means WordPress built the page and saved it.

**One trap our documentation names.** Only `GET` requests are served from cache, so `curl -I` sends a `HEAD` and reports `BYPASS` every time, whatever your settings say. Send a real `GET` instead.

```
curl -s -o /dev/null -D - https://example.com/cart/ | grep -i x-xspeed-cache
```

[Understanding cache HITs and MISSes](https://xspeedcache.com/docs/hits-and-misses/) covers every value, and [checking whether caching is actually working](https://xspeedcache.com/blog/check-wordpress-caching-working/) covers the wider question of proving a cache is live.

## Running Exclusions Across Twenty-Five Client Sites

One store is four fields on one screen. Twenty-five stores is the per-site drift: one has an old `^/cart` pasted from a previous plugin, another excluded `/blog` during an incident two years ago and nobody removed it.

xSpeed Cache is ours, built by WPDeveloper, and every exclusion field above is in the free tier, confirmed against [Free vs Pro](https://xspeedcache.com/free-vs-pro/). A direct [wordpress.org Plugin API](https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request%5Bslug%5D=xspeed) query on 2 October 2026 returned xSpeed 1.3.7, rated 5.0 from 16 ratings.

For fleets, [xSpeed Hub](https://xspeedcache.com/xspeed-hub/) audits one exclusion list across sites instead of four fields twenty-five times. For one store, start with the [WooCommerce setup](https://xspeedcache.com/use-cases/woocommerce/) and [caching WooCommerce without breaking checkout](https://xspeedcache.com/blog/cache-woocommerce-without-breaking-checkout/).

## Frequently Asked Questions

### I added `/cart/*` and my cart page is still being cached. Why?

Because `*` anchors the pattern. `/cart/*` requires something after `/cart/`, so `/cart` itself no longer matches. Use the bare `/cart`.

### I pasted an exclusion list from another plugin and nothing changed. What went wrong?

Those boxes commonly take regular expressions or their own anchors, as LiteSpeed's documented `^` and `$` do. Here a plain line is a literal substring, so prefix anything meant as a regex with `~`.

### I excluded a cookie and now my entire site is uncached. How?

The cookie you chose is set on every page. Check it in your browser's developer tools against a page that should be cacheable, and pick a cookie that only appears for the visitors you meant.

### My rule works on a cold page and not on a warm one. Is that a bug?

That was a real bug, and it is fixed. The fast path answers before WordPress loads, so cookie and user-agent rules are compiled into the drop-in when you save your settings. Re-save the panel to force that rebuild.

### `curl -I` reports BYPASS on every page, even ones I did not exclude. What am I missing?

`curl -I` sends a `HEAD` request, and only `GET` requests are served from cache. Use `curl -s -o /dev/null -D -` instead.

### Which other plugins handle exclusions well?

LiteSpeed Cache has the most granular controls, including exclusion by role and by category, and W3 Total Cache exposes more of the underlying rules. WP Fastest Cache is the simplest if you only need a path list. All three are reasonable choices; xSpeed is ours and our [80-capability comparison](https://xspeedcache.com/comparison/) sets them side by side.

### Do I need to exclude the WordPress login or admin pages?

No. Those are in the shipped defaults, and the drop-in also skips any request whose path contains `/wp-admin` or `/wp-login` before it looks at your settings.

### Should I exclude logged-in users?

They are already excluded. xSpeed serves cached pages to logged-out visitors only, and three session cookie names are a permanent floor that an empty or broken setting cannot weaken.

### Can I exclude a single product without a site-wide pattern?

Yes, from that product's own editor screen. It applies to singular requests only, so archives and taxonomy pages keep the global policy.

### Is there a limit to how many patterns I can add?

The server layer takes 100, and anything longer than 120 characters is left to PHP. PHP has no practical limit, but every pattern is a page rebuilt on each visit.

## Conclusion: Your Exclusion Checklist

| Your goal | Field | Entry to write |
|:---|:---|:---|
| One dynamic page | Excluded URLs | `/cart` |
| One dynamic section, nothing else | Excluded URLs | `/private/*` |
| One dynamic visitor type | Excluded Cookies | the cookie name, no wildcards |
| One dynamic bot | Bypass User Agents | a plain substring |
| A regex from another plugin | any of the first three | prefix it with `~` |

**What to do this week:** scan the page you believe is excluded and read D2. Re-save the Page Cache panel so the drop-in is rebuilt. Delete any pattern you cannot explain. Replace pasted `^` and `$` patterns with a `~` prefix or plain text. If you are running more than five sites, xSpeed Cache is free to start and Pro is $29/yr at the founding price with a 14-day money-back guarantee.

Got an exclusion that behaves in a way this article does not explain? The [troubleshooting guide](https://xspeedcache.com/docs/common-problems-and-troubleshooting/) is the next place to look.
