X-LiteSpeed-Cache response header does, and once you know how the server reaches that decision, most 'is my cache working' questions have short answers.
Where the cache decision actually happens
- The request reaches LiteSpeed. If cache lookup is enabled for that site, the server builds a cache key from the hostname, the path, the query string, and any vary values, such as a vary cookie or a mobile flag if mobile caching is on. On LiteSpeed Web Server, the plugin typically enables lookup by writing a
CacheLookup ondirective into.htaccessinside anIfModule LiteSpeedblock. On OpenLiteSpeed, the cache module is configured at server or virtual host level, so the details depend on the setup. - The server checks its cache storage for that key. If a fresh entry exists and the request qualifies (typically a GET or HEAD request), the server sends the stored response with
X-LiteSpeed-Cache: hit. PHP never starts, WordPress never loads, and no database query runs. - If no entry exists, the request goes to the PHP handler (lsphp), and WordPress runs normally: plugins load, the theme renders, queries run. At the end, the plugin attaches an instruction header,
X-LiteSpeed-Cache-Control, with a value such as public with a max-age, private, or no-cache. It usually addsX-LiteSpeed-Tagas well, which labels the page so a later purge can target it. - The server reads that instruction. If the response is cacheable, the server stores a copy and labels the outgoing response
miss. If the response is no-cache, the server passes it through without storing it, and you typically see noX-LiteSpeed-Cacheheader at all.
miss is not a failure. It means the page was cacheable and has just been stored. The real problems look different. You get a miss on every single request, or the header never appears.
What each result tells you
| What you see | What it usually means |
|---|---|
X-LiteSpeed-Cache: hit | Served from the public cache. PHP did not run. |
X-LiteSpeed-Cache: miss | Cacheable, not in cache yet, now stored. The next identical request should hit. |
X-LiteSpeed-Cache: hit,private | Served from a per-visitor private cache, for example a logged-in user when private caching is enabled. |
No X-LiteSpeed-Cache, but X-LiteSpeed-Cache-Control: no-cache | WordPress ran, and the plugin marked this response uncacheable. |
| No LiteSpeed headers at all | The request may not have reached LiteSpeed, a proxy stripped the headers, or cache lookup is off. |
X-LiteSpeed-Cache-Control header is visible to the client can depend on the server version and configuration. If you don't see it, its absence alone proves nothing.
Checking it yourself with curl
BASH
# -D - prints response headers, -o /dev/null discards the body.
# This sends a GET, like a browser, rather than the HEAD request that -I sends.
for i in 1 2; do
curl -s -D - -o /dev/null -w 'TTFB: %{time_starttransfer}s
' 'https://example.com/sample-page/' \
| grep -i -E '^(x-litespeed|set-cookie|server|ttfb)'
echo '---'
doneTEXT
server: LiteSpeed
x-litespeed-cache: miss
TTFB: 0.900s
---
server: LiteSpeed
x-litespeed-cache: hit
TTFB: 0.050s
---miss, look at the set-cookie lines. A response that sets a cookie on every anonymous request is a common reason pages never settle into the cache.
If the site sits behind a CDN or another proxy, the headers you see may come from the edge rather than from LiteSpeed. To test the origin directly, add --resolve 'example.com:443:203.0.113.10', using your server's real IP address. curl then connects straight to the origin while still sending the correct hostname and SNI.
Query strings matter too, because they are part of the cache key:
BASH
# A new query string usually means a new cache key: expect miss, then hit.
curl -s -D - -o /dev/null 'https://example.com/sample-page/?test=1' | grep -i 'x-litespeed-cache'
curl -s -D - -o /dev/null 'https://example.com/sample-page/?test=1' | grep -i 'x-litespeed-cache'Why cookies and logged-in sessions bypass the cache
wordpress_logged_in_ cookie, and the plugin typically sets its own vary cookie (commonly _lscache_vary). On the next request, the server sees that cookie, folds it into the cache key, and the visitor no longer matches the anonymous entries. That request either goes to PHP every time, or, if you enable the plugin's option to cache logged-in users, it is served from a private cache that belongs to that one visitor. That is where hit,private comes from.
The same idea applies beyond login. WooCommerce cart cookies put shoppers with items in their cart into a different bucket, and pages like cart, checkout, and account are typically excluded outright. Some plugins start a PHP session or set a cookie for every guest, which can push otherwise ordinary pages into no-cache or a vary that never repeats. When a site never seems to cache, look for a cookie being set on anonymous requests. It often turns out to be the reason.
This also explains the most common confusion at a support desk: the site owner tests speed in the same browser where they are logged in to wp-admin. They will never see a public hit there, no matter how well the cache is configured. A private window, or curl, shows what visitors actually get.
When the header disagrees with how the site feels
X-LiteSpeed-Cache-Control value, if visible, shows whether the plugin marked the page no-cache or whether the entry was simply gone.
If there are no LiteSpeed headers at all, first confirm the request reaches LiteSpeed. The server header helps, though it can be hidden or rewritten. Then test the origin with --resolve to rule out a CDN. If the origin also shows nothing, cache lookup may be disabled at server level. On shared hosting that is a host-side setting, so ask your provider to confirm the cache module is enabled for your account.
If you see a hit but the page still feels slow, the HTML is fine and the time goes elsewhere. A hit with a low time to first byte means the server finished its part quickly. What remains is the browser downloading and running images, fonts, CSS, JavaScript, and third-party scripts, and the browser's developer tools waterfall shows that much better than any header. Uncached requests still matter for capacity, though. Every miss starts a PHP process, so a low hit rate under heavy traffic shows up as load on the PHP workers, not only as slow pages.
The practical habit is short. Before changing any setting, curl the page twice from a clean session, read X-LiteSpeed-Cache, and look for set-cookie. A miss then a hit means the cache works and the slowness lives elsewhere. A miss twice, or no header at all, tells you which layer to investigate next: cookies and query strings, exclusions and purges, or the server and CDN in front of WordPress.