# Understanding cache HITs and MISSes

> Every request is either a HIT or a MISS. Read the X-XSpeed-Cache header, learn which layer served the hit, and find out why some pages always miss.

- Category: Getting Started
- Canonical: https://xspeedcache.com/docs/hits-and-misses/

---

Every request to your site is either a **HIT** — answered from a saved copy of the page — or a **MISS**, one WordPress had to build from scratch. Knowing which is which, and *which layer* served the hit, is the fastest way to confirm caching is working.

You don't have to guess. xSpeed stamps its verdict on every front-end response, so a single command tells you what happened. And the first thing to know is that if you check while signed in to WordPress, the answer will never be a hit.

---

## The `X-XSpeed-Cache` header

xSpeed adds this header to every front-end response. Its value answers two questions at once: *was this cached*, and *what served it*.

| Value | Meaning | Typical speed |
| --- | --- | --- |
| `HIT (nginx)` | Served as a static file by nginx, before PHP runs at all. | ~5–15 ms |
| `HIT (static)` | Served as a static file by Apache via `.htaccess`, before PHP runs. | ~25–40 ms |
| `HIT (php)` | Served from cache by xSpeed's PHP drop-in. WordPress booted, but nothing was rendered or queried. | ~85 ms |
| `MISS` | Not cached. WordPress rendered it fresh — and stored the result, so the next request should hit. | Full page load |
| `BYPASS` | xSpeed decided this request must never be cached. | Full page load |

**`HIT (nginx)` is the fast path you want.** All three HITs are real caching, but the gap between the top row and `HIT (php)` is roughly an order of magnitude, because the top two skip PHP entirely. Which one your site can reach depends on your server — [What is xSpeed Cache?](https://xspeedcache.com/docs/what-is-xspeed/) covers the three paths and how to get to the fastest one available to you.

A **MISS is only a problem if it repeats.** The first request for any page after a purge is a miss by definition; that request is what creates the cached copy. Reload and you should see a hit. If the *same* URL misses every single time, that is a real finding — and [Why some pages always MISS](#why-some-pages-always-miss) below usually explains it.

---

## How to check it

From the command line, with no browser state to confuse things:

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

Run it twice. The first call may report `MISS`; the second should report a hit. To see every header xSpeed sets, including the bypass reason when `WP_DEBUG` is on:

```bash
curl -s -o /dev/null -D - https://example.com/ | grep -i '^x-xspeed'
```

> ⚠️ **Don't use `curl -I`.** It sends a `HEAD` request, and only `GET` requests are served from cache — so `curl -I` reports `BYPASS` every time, even on a perfectly healthy site. The commands above send a real `GET` and simply throw the page body away.

In the browser, open your site in a **private window**, open developer tools and select the **Network** tab, then reload and click the first request — the HTML document, not an image or a script. The value is under **Response Headers**.

> ⚠️ **Your own logged-in visits are never cached, and never will be.** `logged-in` is the second gate xSpeed checks, above almost everything else. A cached page is by definition a page that's identical for everyone, and an admin bar, a personal greeting and a form nonce are none of those things.
>
> So testing your cache while signed in reports `BYPASS` on every request, forever, on a perfectly healthy site. This is the single most common reason someone concludes caching isn't working. **Test in a private window, or with `curl`.**

If the header is missing altogether, that is itself a finding. Either page caching is off — check the **Caching** switch in the top bar — or something upstream answered before your server did, in which case you'll usually see a CDN's own cache header beside it. If neither applies, the drop-in isn't loading: `wp-content/advanced-cache.php` is missing, or `WP_CACHE` isn't defined in `wp-config.php`. [Health](https://xspeedcache.com/docs/health/) checks both and tells you which.

---

## Why some pages always MISS

Some requests are deliberately never cached. When the value is `BYPASS` and `WP_DEBUG` is enabled, a second header — `X-XSpeed-Reason` — names the gate that stopped it. The slug is fixed per gate: it never echoes the URL, cookie or user-agent that matched, so the header is safe to read and safe to share.

- **`logged-in`** — somebody is signed in. Expected, and not fixable by design.
- **`excluded-url`** — the path matched one of your **Excluded URLs**.
- **`excluded-cookie`** — a cookie matching your **Excluded Cookies** was present.
- **`query-param`** — the URL carried a query parameter that isn't in the ignored list.
- **`non-get`** — a POST or other non-`GET` request: a form submission, a comment, a checkout — and the `HEAD` request `curl -I` sends.
- **`cache-disabled`** — page caching is off entirely.
- **`non-frontend`** — an admin, AJAX, cron or REST request. Nothing to cache.
- **`donotcachepage`** — another plugin or your theme set the `DONOTCACHEPAGE` constant for this request.
- **`user-agent`** — the visitor's user-agent matched one of your **Bypass User Agents**.
- **`post-excluded`** — this individual post has caching disabled in its own cache settings.
- **`filtered`** — code on your site answered the `xspeed_cache_skip_for_post` filter.

Three of those account for most surprises.

**`query-param` is the one people hit first.** Any parameter outside the ignored list makes the URL a distinct request, so xSpeed declines rather than file it under the clean URL and risk serving the wrong page. Analytics parameters — `utm_*`, `fbclid`, `gclid` and the rest of a 31-entry default list — are ignored by default precisely so campaign traffic still hits the cache. If your site uses its own parameter that doesn't change the page, add it to **Ignored Query Parameters** in [Page Cache](https://xspeedcache.com/docs/page-cache/).

**`excluded-url` is usually working exactly as intended.** The defaults exclude `/cart`, `/checkout`, `/my-account`, feeds, the REST API and the login screen — the pages where a cached copy would be actively wrong. Finding one of those bypassed is a pass, not a failure.

**`excluded-cookie` covers the logged-out-but-stateful visitor.** WooCommerce and Easy Digital Downloads cart cookies are on the default list, so a shopper with something in their basket gets a freshly built page even though they never signed in.

---

## The hit ratio

**Hit ratio** on [Overview](https://xspeedcache.com/docs/overview/) and [Health](https://xspeedcache.com/docs/health/) is hits ÷ (hits + misses) over a rolling 24 hours, kept in hourly buckets. A healthy public site trends high — 80%+ on a site with steady repeat traffic.

Two things are deliberately kept out of that figure, and both make it more honest rather than more flattering. **Bot and scanner misses don't count**: a wave of requests for `/wp-x7.php` from a vulnerability scanner is noise, not a measurement of how your cache serves visitors, so those are recorded as a separate absorbed-requests line instead of cratering the denominator, and 404s are treated the same way. And **bypassed requests aren't misses** — a bypass was never a candidate for the cache, so counting it would make a correctly configured WooCommerce site look broken.

A low ratio on a site that otherwise looks healthy usually means the counting, not the caching. The likeliest cause is an edge in front of your origin: if a CDN or [Cloudflare](https://xspeedcache.com/docs/cloudflare/) is answering requests at the edge, they never reach your server to be counted, so your visitors are getting the faster result and your ratio simply can't see it. Failing that, a large site with long-tail traffic misses a lot because each page is requested rarely — [the preloader](https://xspeedcache.com/docs/preloader/) crawls your sitemap so visitors meet a warm cache instead of creating it. And if you only enabled caching an hour ago, the window is 24 hours; you have an hour of data.

---

## Related

- [How to enable Page Cache](https://xspeedcache.com/docs/page-cache/)
- [How to check your site health](https://xspeedcache.com/docs/health/)
- [How to warm your cache ahead of visitors](https://xspeedcache.com/docs/preloader/)
- [What is xSpeed Cache?](https://xspeedcache.com/docs/what-is-xspeed/)
