LiteSpeed Cache CLI & Performance
How id86.net uses the LiteSpeed Cache plugin (7.8.1) on OpenLiteSpeed for page caching, object caching, and automated cache invalidation — with a focus on the CLI-driven workflow for content updates.
Activation
The plugin is present but inactive by default. Activate it during the sites' development phase:
sudo -u Dmg59ZFtKg6bIws1 -- wp --path=/home/Dmg59ZFtKg6bIws1/id86net/public_html \
plugin activate litespeed-cache
On activation the plugin:
- Registers 8 WP-CLI command groups (
litespeed-purge,litespeed-option,litespeed-crawler,litespeed-image,litespeed-online,litespeed-database,litespeed-debug,litespeed-presets). - Sets
WP_CACHE = trueand enables the external object cache (Redis). - Regenerates the
.htaccessLSCACHE block (CacheLookup on). - Starts serving page cache from the OpenLiteSpeed
cachedatastore.
Verify activation
sudo -u Dmg59ZFtKg6bIws1 -- wp --path=... plugin list | grep litespeed # active
sudo -u Dmg59ZFtKg6bIws1 -- wp --path=... litespeed-option get cache # 1
The Two Cache Layers
| Layer | Mechanism | Purge |
|---|---|---|
| Page cache | OpenLiteSpeed cachedata store (/usr/local/lsws/cachedata/priv/) | Origin-direct PURGESINGLE per URL (targeted) or purge_all (full) |
| Object cache | Redis (via the LiteSpeed object-cache drop-in) | wp cache flush / Redis FLUSHALL |
Why wp litespeed-purge all Fails Here (important)
The plugin's CLI purge (wp litespeed-purge all) internally POSTs to admin_url('admin-ajax.php') through the public URL. Because the site is behind Cloudflare Zero Trust Access, that request is redirected (302) to the Access login and never reaches the origin — so the CLI reports "Success: Purged All!" but the cache is not actually cleared.
Verified: stale content persisted after wp litespeed-purge all; only an origin-direct request cleared it.
The Working Purge: Origin-Direct (nonce-free from localhost)
The LiteSpeed router treats requests from the debug-IP allowlist (litespeed.conf.debug-ips, currently ["127.0.0.1"]) as trusted, bypassing the nonce check. So from localhost you can purge the server page cache with a plain request.
Targeted vs Full purge — the strategy
Two invalidation strategies exist. The site uses targeted as the default to preserve warm caches for returning viewers, and full only for structural changes.
| Mode | Request | Use for |
|---|---|---|
| Targeted (default) | LSCWP_CTRL=PURGESINGLE requested at each URL you want to purge | Adding/updating one or a few posts — purge only the changed post URLs + the homepage |
| Full | LSCWP_CTRL=purge_all | Structural changes: theme, plugin, GP Elements, config, bulk republish |
# Targeted: purge ONLY a specific URL (request the controller at that URL)
curl -ksS --resolve id86.net:443:127.0.0.1 -o /dev/null -w "%{http_code}\n" \
"https://id86.net/<slug>/?LSCWP_CTRL=PURGESINGLE" # expect 200
# Targeted: purge the homepage (shows latest-reference cards)
curl -ksS --resolve id86.net:443:127.0.0.1 -o /dev/null -w "%{http_code}\n" \
"https://id86.net/?LSCWP_CTRL=PURGESINGLE" # expect 200
# Full: purge the entire server page cache
curl -ksS --resolve id86.net:443:127.0.0.1 -o /dev/null -w "%{http_code}\n" \
"https://id86.net/?LSCWP_CTRL=purge_all" # expect 200
--resolve id86.net:443:127.0.0.1forces the request to the local origin, bypassing Cloudflare.-kaccepts the origin's cert;-sSkeeps it quiet.- A
200response means the purge was accepted; the next page fetch returnsx-litespeed-cache: miss. - Do not use a double slash (
//?): LiteSpeed returns a301and the purge is skipped. Normalize URLs to a single trailing slash. - Cache tags confirm what each page carries: homepage
2ea_front/2ea_Po.3944, categories2ea_T.<term_id>, posts2ea_Po.<post_id>.
The Full Purge Stack (in order)
When a change must be visible everywhere, run all three:
# 1. LiteSpeed server page cache (origin-direct)
curl -ksS --resolve id86.net:443:127.0.0.1 "https://id86.net/?LSCWP_CTRL=purge_all"
# 2. PHP OPcache
sudo -u Dmg59ZFtKg6bIws1 -- wp --path=... flush-opcache flush
# 3. Cloudflare edge cache (full, for structural changes)
source ~/.ssh/cloudflare/.env
curl -sS -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CF_FULL_CONTROL_TOKEN" \
-H "Content-Type: application/json" --data '{"purge_everything":true}'
For routine content updates, use targeted Cloudflare URL purging instead:
# Targeted: purge only specific URLs (changed posts + homepage)
source ~/.ssh/cloudflare/.env
curl -sS -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CF_FULL_CONTROL_TOKEN" \
-H "Content-Type: application/json" \
--data '{"files":["https://id86.net/<slug>/","https://id86.net/"]}'
Note: the Cloudflare purge is origin-independent and always works via API. The LiteSpeed purge must use the origin-direct form above, not
wp litespeed-purge all.
The Content-Update Workflow (automated)
When content is added or updated, sync.py runs after writing posts to WordPress. It performs a targeted origin-direct LiteSpeed purge plus a targeted Cloudflare URL purge — only the changed post URLs and the homepage are invalidated, so untouched pages keep their warm cache:
1. Commit markdown
2. sync.py → wp_insert_post (hash-checked: only changed posts)
3. Bridge returns each changed post's permalink (created/updated)
4. purge_litespeed() → origin-direct LSCWP_CTRL=PURGESINGLE per changed URL + homepage (HTTP 200 each)
5. purge_cloudflare() → API purge_cache { files: [changed URLs + homepage] } (targeted, not purge_everything)
6. SEOPress sitemap regenerates on post save (automatic)
This is the performance-safe flow: returning viewers keep warm caches on every page that did not change; only the genuinely changed post(s) and the homepage (which lists latest-reference cards) reload. Verified on the server: after a targeted purge, the changed post returned miss while an unrelated cached post returned hit.
When a full purge happens instead
The pipeline uses purge_all only when:
--purge-allis passed (structural change), or--forceis passed and no specific URLs could be collected.
Otherwise it is always targeted.
CLI flags
tools/sync-docs.sh # targeted purge of changed posts + homepage
tools/sync-docs.sh --purge-all # full purge (theme/plugin/config changes)
tools/sync-docs.sh --skip-cache-purge
tools/sync-docs.sh --force # rewrite all posts; purges full only if no URLs collectible
Auto-purge on post save (hooks)
The plugin registers transition_post_status, delete_post, wp_trash_post, and wp_update_comment_count to auto-purge. These fire only on actual status transitions (e.g. draft → publish). Editing an already-published post without a status change does not trigger them — which is exactly why the sync pipeline's explicit targeted purge is required.
Skipping the purge
tools/sync-docs.sh --skip-cache-purge
LiteSpeed CLI Command Reference
Purge (litespeed-purge)
| Command | Purpose |
|---|---|
wp litespeed-purge all | Purge entire cache — do not use; goes through Cloudflare and no-ops. Use origin-direct instead |
wp litespeed-purge url <url> | Purge a URL's cache tags |
wp litespeed-purge post_id <id> | Purge a post by ID |
wp litespeed-purge category <term_id> | Purge a category archive (integer term ID) |
wp litespeed-purge tag <term_id> | Purge a tag archive (integer term ID) |
wp litespeed-purge blog <blogid> | Purge a blog (multisite) |
The
url/post_id/category/tagsubcommands also route throughadmin-ajax.phpand are likewise affected by Cloudflare Access. For reliable automation, prefer the origin-direct query-string form.
Option (litespeed-option)
| Command | Purpose |
|---|---|
wp litespeed-option get <key> | Read an option |
wp litespeed-option set <key> <value> | Write an option |
wp litespeed-option export | Export options to a file |
wp litespeed-option import <file> | Import options from a file |
wp litespeed-option reset | Reset options |
Useful keys: cache, cache-browser, cache-mobile, cache-priv, cache-exc, purge-post_all, purge-post_p, purge-post_h, purge-post_t, debug-ips.
Database (litespeed-database)
| Command | Purpose |
|---|---|
wp litespeed-database clear_posts | Delete orphaned revision/post rows |
wp litespeed-database clear_transients | Clear expired transients |
wp litespeed-database optimize_all | Optimize all tables |
Crawler / Image / Online / Presets
| Command | Purpose |
|---|---|
wp litespeed-crawler list / run | Run the crawler to warm the cache |
wp litespeed-image status / push | Image optimization queue |
wp litespeed-online init / sync | QUIC.cloud connection |
wp litespeed-presets apply <name> | Apply a preset config |
Recommended Performance Configuration
Baseline verified on the server (all purge-post_* hooks on, page + object cache on):
| Setting | Value | Effect |
|---|---|---|
cache | 1 | Page cache enabled |
cache-browser | 1 | Browser cache headers |
cache-mobile | 1 | Mobile vary cache |
cache-priv | 1 | Logged-in cache |
purge-post_all / _p / _h / _f / _a / _m / _t / _pt | 1 | Auto-purge on transitions |
debug-ips | ["127.0.0.1"] | Enables nonce-free origin-direct purge |
| Object cache | Redis | WP query/cache drop-in |
Security Note
The origin-direct purge relies on debug-ips trusting localhost. Keep debug-ips limited to the server itself (and trusted admin IPs). Do not widen it to public IPs — it bypasses the nonce check for control actions.
Verification
# Page cache working (first = miss, repeat = hit)
curl -ksS --resolve id86.net:443:127.0.0.1 -D - "https://id86.net/<slug>/" | grep -i x-litespeed-cache
# Targeted purge works (only that URL flips to miss; others stay hit)
curl -ksS --resolve id86.net:443:127.0.0.1 -o /dev/null -w "%{http_code}\n" \
"https://id86.net/<slug>/?LSCWP_CTRL=PURGESINGLE" # expect 200
curl -ksS --resolve id86.net:443:127.0.0.1 -D - "https://id86.net/<slug>/" | grep -i x-litespeed-cache # miss
# Full purge works
curl -ksS --resolve id86.net:443:127.0.0.1 -o /dev/null -w "%{http_code}\n" \
"https://id86.net/?LSCWP_CTRL=purge_all" # expect 200
# Object cache backend
sudo -u Dmg59ZFtKg6bIws1 -- wp --path=... eval 'var_dump(wp_using_ext_object_cache());' # true