A plain example: one page, two visitors
/my-account/ page. When Alice is logged in and opens it, WordPress builds HTML that includes her name, her recent orders, and maybe the admin bar if she's an editor. That HTML is personal. It only makes sense for her.
Now imagine the cache stores that response under the key "/my-account/ on this domain" and nothing else. Bob, who isn't logged in, requests the same URL. The cache sees a matching key, skips PHP entirely, and sends him Alice's page. Bob didn't break anything. The cache simply had no way to tell Alice's request apart from Bob's.
That's the whole problem in one sentence: a cache only separates visitors by whatever it includes in its cache key. If login state isn't part of the key, and the page isn't excluded from caching, personal pages become public pages.
How LSCache decides who gets which copy
.htaccess that say "cache everything for N seconds." Those rules don't know anything about the application's login cookies unless you add exclusions yourself. This is where many leaks start, especially on custom PHP apps, forums, or membership systems where the session cookie has a name LiteSpeed has never heard of.
The result of all this shows up in the X-LiteSpeed-Cache response header. Typical values are miss (not served from cache this time), hit (served from public cache), and hit,private (served from that visitor's private cache). If the header is missing entirely, the response usually didn't go through LSCache at all. Exact values can vary by server version, so treat these as the common pattern rather than a guarantee.
Checking it with curl
miss and tells you nothing about what the cache will do next.
BASH
# 1) Anonymous request, no cookies. Run it twice.
curl -s -o /dev/null -D - 'https://example.com/my-account/' | grep -i 'x-litespeed'
# 2) Same URL with a logged-in cookie copied from the browser (use a test account)
curl -s -o /dev/null -D - -H 'Cookie: COOKIE_NAME=COOKIE_VALUE' 'https://example.com/my-account/' | grep -i 'x-litespeed'
# 3) Look for logged-in markers in the anonymous HTML
curl -s 'https://example.com/my-account/' | grep -i -o -E 'wpadminbar|logged-in' | sort -u-D - flag prints response headers to the terminal, and -o /dev/null throws away the body. This sends a normal GET request. Using -I sends a HEAD request instead, and some setups handle HEAD differently, so GET is the safer choice for this check.
Here's how to read what you get back.
If the anonymous request to an account, cart, checkout, or dashboard URL returns hit, that page is in public cache. That's not automatically a leak, because the cached copy might be the generic "please log in" version. But it means you need step 3.
Step 3 looks for traces of a logged-in session in the HTML that an anonymous visitor receives. On WordPress, wpadminbar is the ID of the admin bar element, and logged-in is a class WordPress adds to the body tag for authenticated users. If either appears in a response fetched without cookies, a logged-in page is being served publicly. You can also grep for the test account's display name or email.
For the logged-in request, miss on every try or hit,private is what you want to see. A plain hit on a logged-in request isn't wrong by itself if the page is genuinely the same for everyone, but on an account page it deserves a closer look.
A note on the cookie: it's a live session credential. Use a throwaway test account, don't paste real session cookies into tickets or chat, and log that session out when you're done.
Common mistakes that cause or hide the leak
.htaccess cache rule with no cookie exclusion. Someone enables LSCache for a custom app to speed it up, the homepage gets faster, and nobody notices that the logged-in dashboard is cached too. The fix is to skip caching when the app's session cookie is present, or limit the rule to paths that are truly public. The exact rewrite syntax depends on whether you're on LiteSpeed Enterprise or OpenLiteSpeed, so check the LiteSpeed docs for your edition.
Inside WordPress, the usual suspects are settings changes. Turning on caching for logged-in users, adding custom "do cache" URIs that overlap with account pages, or removing default excludes for WooCommerce pages can all reintroduce the problem. Another pattern is a second caching plugin or a theme's own cache running alongside LSCache, each assuming the other handles exclusions.
Testing only once is a mistake that hides the problem rather than causing it. A miss on the first request just means nothing was stored yet. The leak shows up on the next anonymous request after a logged-in user has visited, so the order of requests matters. If you want to be thorough, request the page with the logged-in cookie first, then anonymously.
Forgetting the layer in front is another one. If a CDN or reverse proxy sits in front of LiteSpeed and is configured to cache HTML, it can leak pages even when LSCache is set up correctly. Your curl result might say hit from the CDN while LiteSpeed never saw the request. Check that layer's own cache status header too, and remember that a purge on the server doesn't clear the CDN unless they're integrated.
Finally, fixing the rule and not purging. Correcting the configuration stops new bad entries, but the leaked copy that's already in cache keeps being served until it expires or you purge it. This is the step that most often makes people think their fix didn't work.
From the support side, the confusing part for site owners is usually that the problem looks intermittent. It depends on who visited first and when the cache expired, so it appears and disappears. A clean curl test turns that into something reproducible.
What to do once you find one
wpadminbar, no logged-in class, and no account details.
If personal data was actually exposed, treat it as a data incident, not just a cache bug. Figure out roughly how long the bad rule was in place and what kind of information those pages contained, and involve whoever handles privacy or compliance for the site.
The habit worth building is simple: any time someone changes cache rules, adds a caching layer, or installs a membership or shop plugin, run the two-request curl test against one private URL. It takes a minute, and it's the only way to know what an anonymous visitor really receives.