The usual reason a WooCommerce site shows a 2% cache hit rate is that LSCache has been told to bypass the cache for every logged-in user and every visitor with a cart cookie. That's the safe default, and it throws away most of the benefit you bought LiteSpeed for. ESI — Edge Side Includes — is the fix: the server caches the page shell once and stitches in a handful of dynamic fragments (cart total, login greeting, security nonce) on every request.
This guide is for operators running WooCommerce or membership sites on LiteSpeed Web Server who already have LSCache working on static pages but see it disabled the moment someone adds to cart. It covers what ESI actually does, the three blocks WooCommerce needs punched, and how to debug a fragment that won't update.
How ESI changes the caching model
Without ESI, LSCache makes a binary decision per request: serve the whole page from cache,
or run all of PHP and cache nothing. A woocommerce_items_in_cart cookie flips it to the
second branch, so the entire catalogue page rebuilds for a shopper who added one item.
ESI splits the page into a cacheable parent and one or more independently cached (or uncached) child blocks. The parent — product grid, header, footer, sidebar — caches with a long TTL and serves to everyone. The cart widget becomes an ESI block with its own short TTL or no cache at all. LiteSpeed assembles the response inside the server binary before it hits the wire, so the browser never sees the seams.
The mechanism is a placeholder comment in the HTML:
<esi:include src='/?lscache_esi=storefront_cart' cache-control='no-cache'/>
LSWS intercepts that tag, fetches the fragment (running only the PHP needed for the cart), and substitutes the result. One PHP call for a small widget beats rebuilding the full page.
Enable ESI in the LiteSpeed Cache plugin
ESI is off by default. Turn it on per site in the WordPress admin:
- LiteSpeed Cache → Cache → ESI.
- Set Enable ESI to
ON. - Set Cache Admin Bar to
ON— the admin bar is the classic ESI block for logged-in users and the simplest one to verify against. - Leave Vary Group at defaults unless you run membership tiers (more on that below).
Save, then purge the whole cache (LiteSpeed Cache → Toolbox → Purge All). The plugin
ships WooCommerce ESI templates, so once ESI is on, the mini-cart and the "Hi, name"
account greeting are punched automatically — you don't hand-write the esi:include tags
for the bundled widgets.
Confirm the server actually processed the tag rather than leaking it to the browser:
curl -s https://shop.example.com/shop/ | grep -c 'esi:include'
A correctly configured server returns 0 — the tags were resolved server-side. If you see
a non-zero count, ESI is enabled in the plugin but the server isn't honouring it; check
that you're on LSWS Enterprise and not Apache, and that Module cache is loaded in WHM →
LiteSpeed Web Server → LSWS configuration.
The three blocks WooCommerce needs
Mini-cart and cart fragments
WooCommerce updates the cart count over AJAX (?wc-ajax=get_refreshed_fragments), which is
itself uncacheable. With ESI on, the LiteSpeed plugin replaces the mini-cart widget with an
ESI block so the page caches and the cart number stays correct. If your theme renders the
cart total somewhere outside the standard widget (a custom header), that copy won't update
— you must wrap it yourself (see the next section).
Login state and the account greeting
Any "My Account" / "Hi, name" element is per-user. The plugin's storefront_cart and
account ESI templates handle Storefront and most well-behaved themes. Page builders that
inject the greeting via a shortcode often miss it — symptom: the cached page shows the
previous visitor's name. That's the highest-severity ESI bug and the reason to test
logged-in rendering with two different accounts before going live.
Nonces in forms and AJAX
WordPress nonces expire (default 12–24 hours). If a nonce is baked into a page cached for a week, form submissions start failing with "Are you sure you want to do this?" once the nonce ages out. The LiteSpeed plugin ships an ESI block for the WooCommerce add-to-cart nonce; for custom forms, register the nonce field as its own non-cached ESI block so a fresh token renders on every load.
Punch a custom block by hand
For theme elements outside the bundled templates, wrap the dynamic part in the plugin's ESI shortcode in your child theme or a snippet:
echo do_shortcode(
'[esi cache="no-cache" ttl="0"]' .
'[your_dynamic_shortcode]' .
'[/esi]'
);
Or, for a fragment that's the same for all anonymous visitors but should refresh hourly,
give it a short TTL instead of no-cache:
echo do_shortcode('[esi ttl="3600"]' . '[live_exchange_rate]' . '[/esi]');
Purge all after editing. Then re-run the grep -c 'esi:include' check — it should still
return 0 from the public side.
Vary groups for membership tiers
If logged-in users should see different cached pages by role (free vs. premium), set the Vary Group values under Cache → ESI. Assign each role an integer:
administrator 99
premium-member 5
subscriber 2
LSCache then keeps a separate cached copy per vary group rather than serving uncached pages to every member. This is what turns a membership site from "cache disabled for all logged-in traffic" into "cache hit for everyone in the same tier" — often the single biggest TTFB win on a paywalled WooCommerce store. Pair it with an object cache via LSMCD so the per-group PHP that does run isn't hammering MySQL.
Verify hit rate after the change
Load a product page twice and read the response header:
curl -sI https://shop.example.com/shop/?add-to-cart=99 | grep -i x-litespeed-cache
x-litespeed-cache: hit— the parent served from cache; ESI punched the cart. Working.x-litespeed-cache: miss,no-cacheon every load — ESI isn't engaging; the page is still being bypassed wholesale. Re-check that the cart cookie rule wasn't left in Cache → Excludes.
If pages that used to bypass now report hit, your hit rate on a busy store typically jumps
from low single digits to 70–90%, and PHP worker pressure drops accordingly. If the cache is
still disabled, work through the LSCache disabled on WordPress
checklist first — ESI can't help a site that isn't caching at all.