Skip to main content

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 = true and enables the external object cache (Redis).
  • Regenerates the .htaccess LSCACHE block (CacheLookup on).
  • Starts serving page cache from the OpenLiteSpeed cachedata store.

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

LayerMechanismPurge
Page cacheOpenLiteSpeed cachedata store (/usr/local/lsws/cachedata/priv/)Origin-direct PURGESINGLE per URL (targeted) or purge_all (full)
Object cacheRedis (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.

ModeRequestUse for
Targeted (default)LSCWP_CTRL=PURGESINGLE requested at each URL you want to purgeAdding/updating one or a few posts — purge only the changed post URLs + the homepage
FullLSCWP_CTRL=purge_allStructural 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.1 forces the request to the local origin, bypassing Cloudflare.
  • -k accepts the origin's cert; -sS keeps it quiet.
  • A 200 response means the purge was accepted; the next page fetch returns x-litespeed-cache: miss.
  • Do not use a double slash (//?): LiteSpeed returns a 301 and the purge is skipped. Normalize URLs to a single trailing slash.
  • Cache tags confirm what each page carries: homepage 2ea_front/2ea_Po.3944, categories 2ea_T.<term_id>, posts 2ea_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-all is passed (structural change), or
  • --force is 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)

CommandPurpose
wp litespeed-purge allPurge 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 / tag subcommands also route through admin-ajax.php and are likewise affected by Cloudflare Access. For reliable automation, prefer the origin-direct query-string form.

Option (litespeed-option)

CommandPurpose
wp litespeed-option get <key>Read an option
wp litespeed-option set <key> <value>Write an option
wp litespeed-option exportExport options to a file
wp litespeed-option import <file>Import options from a file
wp litespeed-option resetReset 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)

CommandPurpose
wp litespeed-database clear_postsDelete orphaned revision/post rows
wp litespeed-database clear_transientsClear expired transients
wp litespeed-database optimize_allOptimize all tables

Crawler / Image / Online / Presets

CommandPurpose
wp litespeed-crawler list / runRun the crawler to warm the cache
wp litespeed-image status / pushImage optimization queue
wp litespeed-online init / syncQUIC.cloud connection
wp litespeed-presets apply <name>Apply a preset config

Baseline verified on the server (all purge-post_* hooks on, page + object cache on):

SettingValueEffect
cache1Page cache enabled
cache-browser1Browser cache headers
cache-mobile1Mobile vary cache
cache-priv1Logged-in cache
purge-post_all / _p / _h / _f / _a / _m / _t / _pt1Auto-purge on transitions
debug-ips["127.0.0.1"]Enables nonce-free origin-direct purge
Object cacheRedisWP 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