# LumenCache — Complete Documentation

> RAM-first full-page caching for WordPress — Nginx, Apache & LiteSpeed  
> Version 1.6.9 · PHP 8.1+ · WordPress 6.0+

---

## Table of Contents

1. [What is LumenCache?](#1-what-is-lumencache)
2. [How It Works](#2-how-it-works)
3. [Requirements](#3-requirements)
4. [Installation](#4-installation)
5. [Initial Setup](#5-initial-setup)
6. [Settings Reference](#6-settings-reference)
7. [Security Hardening](#7-security-hardening)
8. [Server Configuration](#8-server-configuration)
9. [Cache Invalidation](#9-cache-invalidation)
10. [WP-CLI Reference](#10-wp-cli-reference)
11. [REST API](#11-rest-api)
12. [Admin Bar Controls](#12-admin-bar-controls)
13. [Platform Integrations](#13-platform-integrations)
14. [wp-config.php Constants](#14-wp-configphp-constants)
15. [Tracking Parameter Handling](#15-tracking-parameter-handling)
16. [Developer Hooks](#16-developer-hooks)
17. [Benefits](#17-benefits)
18. [Shortcomings & Known Limitations](#18-shortcomings--known-limitations)
19. [Troubleshooting](#19-troubleshooting)
20. [Glossary](#20-glossary)

---

## 1. What is LumenCache?

LumenCache is a **RAM-first full-page caching plugin** for WordPress. Unlike
object-cache plugins that cache database queries, LumenCache caches the entire
rendered HTML of each page in fast in-memory storage. Its PHP drop-in serves a
cache HIT before the normal WordPress bootstrap; the optional Nginx direct-RAM
bridge can serve a HIT before PHP starts.

A cached page on a standard WooCommerce shop requires:

| Without LumenCache | With LumenCache (PHP drop-in) |
|---|---|
| PHP init + autoloader + wp-settings.php + 40–120 DB queries + template rendering | One in-memory lookup (~0.2 ms) |
| 200–800 ms typical | 5–30 ms typical |

Pages are served from RAM. For high-traffic stores where Google Shopping, Meta Ads, and organic search hit the same product pages simultaneously, this difference is the margin between a fast store and a slow one.

---

## 2. How It Works

LumenCache uses a **three-layer serving architecture**. All three can be active simultaneously; PHP drop-in is always the foundation.

### Layer A — PHP Drop-In (recommended foundation)

WordPress loads a file called `advanced-cache.php` very early in its boot sequence, before plugins, themes, or the database are loaded. LumenCache deploys its own `advanced-cache.php` drop-in that:

1. Reads the cache configuration from `wp-content/lumen-cache-config.php` (no database needed)
2. Checks bypass conditions (logged-in user, cart-active visitor, POST request, excluded URLs)
3. Strips ad-platform tracking parameters from the URL to normalise the cache key
4. Looks up the page in Redis using the normalised key
5. On **HIT**: sends the cached HTML directly and exits — the normal
   WordPress bootstrap never loads
6. On **MISS**: lets WordPress load normally; at the end of the request, captures the rendered HTML and writes it to Redis

### Layer B — Nginx Direct-Redis (optional, zero-PHP overhead)

For Nginx servers, LumenCache can generate a `server{}` location block that makes Nginx query Redis directly using `redis_pass` — bypassing PHP-FPM entirely on cache HITs. This is the fastest possible mode: the OS network stack never even wakes PHP. You paste the generated config into your site vhost and reload Nginx.

**Note:** This mode is incompatible with key salting (see [Security Hardening](#7-security-hardening) and [Shortcomings](#18-shortcomings--known-limitations)).

### Layer C — Apache/LiteSpeed Proxy (optional)

For Apache and LiteSpeed servers, LumenCache deploys `lumen-cache-proxy.php` to the WordPress root and writes `RewriteRule` directives to `.htaccess`. Apache routes requests through the proxy before WordPress loads; the proxy checks Redis and serves the cached HTML if available.

---

## 3. Requirements

| Requirement | Minimum | Recommended |
|---|---|---|
| PHP | 8.1 | 8.2+ |
| WordPress | 6.0 | 6.5+ |
| Redis | 5.0 | 7.0+ |
| Redis PHP client | phpredis ext. OR Predis | phpredis (faster) |
| Web server | Nginx / Apache / LiteSpeed | Nginx |

LumenCache does not require a third-party hosted service. Rendered HTML and
cache metadata remain in the Redis instance configured by the site owner. The
user-state REST endpoint is public by design for browser hydration and returns
only explicitly requested state values; review its output against the site's
privacy requirements before using personalized fragments.

### Redis client detection

LumenCache auto-detects which Redis client is available:

1. **phpredis** (PHP extension) — preferred; faster, lower memory overhead
2. **Predis** (Composer library) — fallback; pure PHP, no extension required

To install phpredis on Ubuntu/Debian: `apt install php-redis`  
To install phpredis on CentOS/RHEL: `yum install php-pecl-redis`  
On Plesk: enable the Redis extension in PHP settings for your domain.

---

## 4. Installation

### 4.1 Upload and activate

1. Upload the `lumen-cache` folder to `/wp-content/plugins/`
2. Go to **WordPress Admin → Plugins**
3. Activate **LumenCache**
4. Navigate to **LumenCache** in the admin sidebar — the dashboard shows your Redis connection status

### 4.2 Verify Redis is connected

The Dashboard tab shows a green **Connected** badge next to the Redis status row. If it shows **Unavailable**, see [Troubleshooting](#19-troubleshooting).

---

## 5. Initial Setup

The Overview tab includes a **Quick Setup** tracker for administrators. It
shows the first incomplete step, links each step to the relevant dashboard
section, and provides one-click actions for the two safe WordPress bootstrap
changes. Completed steps show a checkmark; once all five steps are done the
tracker collapses to a compact success row with a "View all setup steps"
control. **The success row only appears when the cache backend is reachable.**
A site where the backend is unavailable (common on shared cPanel hosting)
always shows the tracker with step 1 as the active blocker — never a false
"fully configured" state. See **Hosting Guide** in the sidebar for help
getting the backend enabled on your server.

### Step 1 — RAM Cache Engine

The in-memory backend must be connected before any pages can be stored or
served. If this step is incomplete, go to the **Connection** tab to review
the host, port, and client library status, or open the **Hosting Guide** tab
for environment-specific instructions on requesting or enabling the backend
from your hosting provider.

### Step 2 — Enable RAM Cache

Click **Enable** in Quick Setup or **Deploy Drop-In** in **Server Setup**.
This copies LumenCache's `advanced-cache.php` and
`lumen-cache-config.php` into `wp-content/`. The drop-in is the PHP cache
foundation and runs before the normal WordPress bootstrap.

### Step 3 — Activate Boot Hook

Click **Enable** in Quick Setup or **Enable WP_CACHE** in **Server Setup**.
LumenCache adds `define( 'WP_CACHE', true );` to `wp-config.php` when needed.
Existing true definitions are preserved, and a recognized false definition is
corrected safely.

### Step 4 — Security Hardening

Open **Settings → Security Hardening**, click **Generate**, and save the
cache key salt. A salt hashes cache keys with SHA-256 so raw key names
do not reveal page URLs.

On standard Nginx, key salting and the optional direct-RAM bridge are
mutually exclusive: Nginx cannot compute the salted key. If the direct bridge
is active without a salt, Quick Setup treats security hardening as not
applicable. Otherwise, use the salted PHP drop-in path.

### Step 5 — Server Ready

On Apache or LiteSpeed, open **Server Setup** and click **Write .htaccess
Rules**. This deploys the proxy and rewrites.

On Nginx, the direct-RAM bridge is optional. If you use it, copy the
generated map block into the global `http {}` context and the server block
into the site's vhost, then reload Nginx (`nginx -s reload`). If you use key
salting instead, the PHP drop-in remains the serving path and no direct
bridge is required.

### Verify caching is working

Open any product or category page in an **incognito/private window**
(logged-out, no cookies). Return to the LumenCache dashboard — the **Cached
Pages** counter should increment. Alternatively check the response headers:
`X-Cache-Status: HIT` confirms a cached response.

---

## 6. Settings Reference

All settings are in **LumenCache → Settings**. Constants defined in `wp-config.php` always override UI settings and are shown as locked.

### Redis Connection

| Setting | Default | Description |
|---|---|---|
| Host | `127.0.0.1` | Redis server hostname or IP. Used for TCP connections. |
| Port | `6379` | Redis TCP port. |
| Unix Socket | *(auto-detected)* | Path to Redis socket file. Overrides Host/Port when set. Auto-detected at common paths (`/var/run/redis/redis.sock`, etc.). |
| Password | *(blank)* | Redis `AUTH` password. Leave blank if Redis requires no authentication. |
| Database Index | `0` | Redis database number (0–15). |
| Connection Timeout | `1.0` | Seconds to wait for a connection before failing open (not caching, not breaking the site). |
| Key Prefix | `cache:` | Namespace for all Redis keys. Change if multiple sites share one Redis instance. Must match the Nginx config if using Nginx direct-Redis mode. |

### Cache Behaviour

| Setting | Default | Description |
|---|---|---|
| Cache TTL | `86400` (24 h) | Seconds before a cached page is automatically expired by Redis. `0` = never expire (manual flush or invalidation events only). |
| Cache Query Strings | Off | When off, URLs with non-tracking query parameters (`?in_stock=1`, `?s=perfume`) bypass the cache. When on, each unique query string gets its own cache entry. **Pro only.** |

### Security (see full section below)

| Setting | Default | Description |
|---|---|---|
| Cache Key Salt | *(blank)* | A random string that is hashed (SHA-256) into every Redis key. Prevents key enumeration. Configure directly from the Settings tab — no wp-config.php edit needed. |

---

## 7. Security Hardening

The **Security Hardening Matrix** on the Dashboard shows three independent security layers evaluated against your live configuration.

### Layer I — Cryptographic Key Salting

**What it does:** Applies SHA-256 to every Redis key using your salt as input. Raw Redis key inspection (`KEYS *`, `SCAN`) reveals only hashes like `cache:a3f8d2b1…` instead of readable URLs like `cache:httpsGETyoursite.com/product/blue-shirt/`.

**How to enable:** Settings tab → Security Hardening → type or click **Generate**, then **Save**. No wp-config.php edit required. The Generate button uses `crypto.getRandomValues()` for cryptographic randomness.

**Compatibility:** Fully compatible with PHP drop-in mode (both read and write use the same hash). **Incompatible** with Nginx direct-Redis mode (Nginx cannot compute SHA-256 — the plugin detects this and blocks the form automatically).

**Dashboard badge:** Active — SHA-256 · Not configured · Nginx Conflict

### Layer II — ACL Isolation

**What it does:** Restricts access to your Redis instance so other processes on the same server cannot read cache contents.

**Two valid approaches:**
- **Unix socket (recommended):** The socket file's OS permissions (`0660`, owned by `redis:www-data`) ensure only your web server process can connect. No password needed — the OS is the ACL. Badge shows **Socket-secured**.
- **Redis AUTH password:** Set via the Pro settings UI or `LUMENCACHE_REDIS_PASSWORD` constant. Applies when Redis is on TCP. Badge shows **Active** when set.

**On TCP without a password:** Any process on the server that can reach `127.0.0.1:6379` can read all your cached pages. Badge shows **Recommended** as a prompt to act.

### Layer III — Unix Socket Lockdown

**What it does:** Keeps all Redis traffic within the operating system's IPC layer — no network port is opened, no packets traverse even the loopback interface.

**Badge states:**
- **Optimal** — Unix socket is active (best possible configuration)
- **Using TCP** — TCP connection; still works, but OS-level isolation is not in place

**How to configure:** Set the socket path in Pro settings → Unix Socket field, or define `LUMENCACHE_REDIS_SOCKET` in wp-config.php.

---

## 8. Server Configuration

### 8.1 Nginx map block (nginx.conf `http{}` context)

Paste this **once** into `nginx.conf` inside the `http {}` block. It defines the `$lumen_state` variable used to bypass caching for logged-in users and cart-active visitors.

```nginx
map $http_cookie $lumen_state {
    default                             "";
    "~*sys_active_state=true"           1;
    "~*wordpress_logged_in_"            1;
    "~*woocommerce_items_in_cart"       1;
    "~*woocommerce_cart_hash"           1;
    "~*learndash_"                      1;
    "~*llms_"                           1;
    "~*mepr_"                           1;
}
```

Copy this block from **Server Setup → Nginx → Global Map Block** — it is always generated with your live settings.

### 8.2 Nginx server block (site vhost `server{}` context)

Paste into your site vhost inside `server {}`. This is the Nginx direct-Redis location block. It is generated from your live settings (correct prefix, socket/TCP address).

```nginx
location / {
    error_page 418 = @fallback;

    if ($lumen_state = 1) { return 418; }
    if ($request_method != GET) { return 418; }

    set $redis_key "cache:$scheme${request_method}$host$request_uri";

    redis_pass   unix:/var/run/redis/redis.sock;
    default_type text/html;
    # ...
}

location @fallback {
    try_files $uri $uri/ /index.php$is_args$args;
}
```

After pasting, run `nginx -t` to verify syntax, then `nginx -s reload`.

### 8.3 Apache / LiteSpeed

Use the **Server Setup → Apache** panel. Click **Write .htaccess Rules** to deploy automatically. The plugin writes inside `# BEGIN LumenCache … # END LumenCache` markers and can remove them with **Remove .htaccess Rules**.

The Apache proxy (`lumen-cache-proxy.php`) is deployed to the WordPress root. It reads `lumen-cache-config.php` for all settings — no constants required.

---

## 9. Cache Invalidation

LumenCache automatically purges cached pages when content changes. No manual intervention is needed for routine publishing workflows.

### Automatic purge triggers

| Event | Pages purged |
|---|---|
| Post published / updated / deleted | That post's URL + homepage + all archive/term pages |
| Term edited / deleted | All cached pages |
| Navigation menu updated | All cached pages |
| Theme switched | All cached pages |
| Customizer saved | All cached pages |
| WooCommerce order status changed | Homepage |
| WooCommerce stock level changed | All cached pages |
| WooCommerce sale price changed | All cached pages |
| LearnDash lesson/quiz completed | All cached pages |
| LifterLMS course enrollment / lesson completion | All cached pages |
| MemberPress subscription event | All cached pages |
| BuddyBoss activity / group join | All cached pages |

### Manual purge

- **Admin bar:** Click **LumenCache → Flush All Cache** from any admin page
- **Dashboard:** Click **Flush All Cache** button
- **Single URL:** Use the Flush URL form on the Dashboard tab
- **WP-CLI:** `wp lumencache flush` or `wp lumencache flush-url https://yoursite.com/product/x/`

---

## 10. WP-CLI Reference

LumenCache registers commands under the `lumencache` namespace.

```bash
wp lumencache <command> [options]
```

### `flush`

Purge all cached pages from Redis.

```bash
wp lumencache flush
wp lumencache flush --yes        # Skip confirmation prompt
```

### `flush-url`

Purge the cached version of a specific URL.

```bash
wp lumencache flush-url https://yoursite.com/product/blue-shirt/
```

### `status`

Display a full status table: Redis connection, address (socket/TCP, source), key prefix, TTL, query string caching, key salting, cached page count, memory used, active platform drivers, plugin version.

```bash
wp lumencache status
```

Example output:

```
+------------------+--------------------------------------------------+
| Property         | Value                                            |
+------------------+--------------------------------------------------+
| Redis connected  | Yes                                              |
| Connection       | /var/run/redis/redis.sock (socket, source: auto) |
| Key prefix       | cache:                                           |
| Cache TTL        | 86400s                                           |
| Query strings    | Bypass (default)                                 |
| Key salting      | SHA-256 active                                   |
| Cached pages     | 1,247                                            |
| Memory used      | 14.3 MB                                          |
| Active drivers   | woocommerce, buddyboss                           |
| Plugin version   | 1.2.6                                            |
+------------------+--------------------------------------------------+
```

---

## 11. REST API

LumenCache exposes a REST endpoint used by its JavaScript skeleton-hydration system to deliver personalised data (cart count, user greeting) into otherwise-cached pages.

### Endpoint

```
GET /wp-json/lumencache/v1/user-state
```

### Query parameters

| Parameter | Type | Description |
|---|---|---|
| `keys` | string | Comma-separated list of data keys to return |
| `course_ids` | string | Comma-separated LearnDash course IDs for `course-progress` |

### Available keys

| Key | Anonymous response | Authenticated response |
|---|---|---|
| `cart-count` | `0` | Number of items in WooCommerce cart |
| `cart-total` | `"$0.00"` | Formatted WooCommerce cart total |
| `member-greeting` | `""` | `"Hello, {display name}"` |
| `notification-count` | `0` | BuddyBoss unread notification count |
| `course-progress` | `null` | LearnDash progress % for requested course IDs |

### Example request

```bash
curl https://yoursite.com/wp-json/lumencache/v1/user-state?keys=cart-count,member-greeting
```

### Example response (logged-out)

```json
{
  "cart-count": 0,
  "member-greeting": ""
}
```

### Example response (logged-in, items in cart)

```json
{
  "cart-count": 3,
  "member-greeting": "Hello, Jane"
}
```

The JavaScript hydration script is automatically enqueued on frontend pages for anonymous visitors. It calls this endpoint and updates DOM elements that have the `data-lc-key="cart-count"` (or other key) attribute.

---

## 12. Admin Bar Controls

When logged in as an administrator, LumenCache adds a top-level **LumenCache** item to the WordPress admin bar. It includes:

| Item | Action |
|---|---|
| Live status dot | Green = Redis connected; grey = disconnected |
| Cached pages count | Current number of pages in Redis |
| **Flush All Cache** | Purges all entries; redirects with `?lc_flushed=1` confirmation |
| **Settings** | Links to Settings tab |
| **Server Setup** | Links to Server Setup tab |

The flush action is nonce-protected. It works on both the frontend and wp-admin.

---

## 13. Platform Integrations

LumenCache auto-detects active plugins and activates the appropriate bypass and invalidation rules. No configuration is needed.

### WooCommerce

- **Bypass:** Visitors with items in the cart (`woocommerce_items_in_cart` or `woocommerce_cart_hash` cookie) receive live PHP responses — cached pages never show a stale cart state
- **Bypass pages:** Cart, Checkout, My Account, and WooCommerce AJAX endpoints are always bypassed
- **Cookie management:** LumenCache sets a `sys_active_state=true` cookie when a visitor adds to cart; this is read by the Nginx map block for server-level bypass
- **Invalidation:** Order status changes, stock updates, and sale price changes trigger appropriate cache purges

### LearnDash

- **Bypass:** Active learner sessions (identified by `learndash_` cookies) bypass the cache
- **Invalidation:** Lesson completion, quiz submission, and course completion events purge all cached pages

### LifterLMS

- **Bypass:** Active student sessions (`llms_` cookies) bypass the cache
- **Invalidation:** Course enrollment and lesson completion purge all cached pages

### MemberPress

- **Bypass:** Active member sessions (`mepr_` cookies) bypass the cache
- **Invalidation:** Subscription create, upgrade, downgrade, expire, cancel, resume events purge all cached pages

### BuddyBoss / BuddyPress

- **Bypass:** Logged-in members bypass the cache via the standard `wordpress_logged_in_` cookie
- **Invalidation:** New activity posts and group joins purge all cached pages
- **Hydration:** `notification-count` key in the REST API returns live unread notification counts for hydrating cached pages

---

## 14. wp-config.php Constants

Constants override all plugin UI settings and the `lumencache_settings` database option. Define them in `wp-config.php` **before** the `require_once ABSPATH . 'wp-settings.php'` line.

```php
// ── LumenCache Configuration ──────────────────────────────────────────────────

// Redis connection
define( 'LUMENCACHE_REDIS_SOCKET',   '/var/run/redis/redis.sock' ); // preferred
define( 'LUMENCACHE_REDIS_HOST',     '127.0.0.1' );                 // TCP fallback
define( 'LUMENCACHE_REDIS_PORT',     6379 );
define( 'LUMENCACHE_REDIS_PASSWORD', '' );                           // blank = no auth
define( 'LUMENCACHE_REDIS_DATABASE', 0 );
define( 'LUMENCACHE_REDIS_TIMEOUT',  1.0 );                         // seconds
define( 'LUMENCACHE_REDIS_PREFIX',   'cache:' );

// Cache behaviour
define( 'LUMENCACHE_CACHE_TTL',           86400 ); // seconds; 0 = never expire
define( 'LUMENCACHE_CACHE_SALT',          '' );    // set to a random string to enable SHA-256 key hashing
define( 'LUMENCACHE_CACHE_QUERY_STRINGS', false );  // true = cache ?query=string URLs (Pro)

// Pro license
define( 'LUMENCACHE_PRO_LICENSE', 'your-license-key' );

// Debug (remove after use — log file is unbounded)
define( 'LUMENCACHE_DEBUG', false );
```

**Priority rule:** `constant > DB option (plugin UI) > built-in default`

When a constant is defined, the corresponding settings field is shown as locked (⚿ Locked) in the UI and cannot be changed without editing wp-config.php.

---

## 15. Tracking Parameter Handling

Ad platforms append tracking tokens to URLs that do not affect page content. Without special handling, `yoursite.com/product/x/` and `yoursite.com/product/x/?srsltid=abc123` would be cached as separate entries — and Google Shopping traffic (which always arrives with `?srsltid=…`) would never get a cache HIT.

LumenCache strips the following parameters **before** the bypass check and **before** building the cache key, in all three serving layers:

| Parameter | Platform |
|---|---|
| `srsltid` | Google Shopping |
| `gclid`, `gclsrc`, `gbraid`, `wbraid` | Google Ads |
| `fbclid` | Meta Ads (Facebook/Instagram) |
| `msclkid` | Microsoft / Bing Ads |
| `ttclid` | TikTok Ads |
| `twclid` | Twitter/X Ads |
| `li_fat_id` | LinkedIn Ads |
| `_ga`, `_gl` | Google Analytics |
| `mc_eid` | Mailchimp |
| `yclid` | Yandex |

**Result:** `yoursite.com/product/x/?srsltid=AfmBOorXXX` hits the same Redis key as `yoursite.com/product/x/`. Google Shopping, Google Ads, Meta Ads, and Bing traffic all share the same cached entry with organic visitors.

Non-tracking parameters (`?in_stock=1`, `?s=keyword`, `?page=2`) are **preserved** and bypass the cache by default (or get their own entries with query string caching enabled).

---

## 16. Developer Hooks

### Cache engine actions

```php
// Fired after a URL is purged from Redis
do_action( 'lumencache_url_purged', $url, $key );

// Fired after all cache is purged
do_action( 'lumencache_all_purged' );
```

### Bypass filter

```php
// Return true to force a bypass (skip caching) for the current request
add_filter( 'lumencache_should_bypass', function( bool $bypass ): bool {
    // Example: bypass for a specific URL segment
    if ( str_contains( $_SERVER['REQUEST_URI'] ?? '', '/live-pricing/' ) ) {
        return true;
    }
    return $bypass;
} );
```

### Cacheable URL filter

```php
// Modify the URL used as the cache key (after tracking-param stripping)
add_filter( 'lumencache_cache_key_url', function( string $url ): string {
    return $url;
} );
```

---

## 17. Benefits

### Performance

- **Zero WordPress overhead on cache HITs.** The entire WordPress bootstrap — autoloader, `wp-settings.php`, plugins, themes, 40–120 database queries — is skipped. Only one Redis lookup is made.
- **Sub-10 ms page delivery** on cache HITs under typical server conditions.
- **Ad traffic cached correctly.** The 14-parameter tracking-token stripping means Google Shopping, Google Ads, and Meta Ads traffic hits the same cache entry as organic visitors. Without this, ad traffic counts as 0% cache rate because every click arrives with a unique token.
- **Memory-efficient.** Redis stores HTML as plain strings. A typical product page HTML is 30–80 KB; Redis can hold tens of thousands of pages in a few hundred MB of RAM.

### Developer Experience

- **Zero-config first run.** Connect to Redis → Deploy drop-in → done. No rules to write, no rewrite conditions to debug.
- **WP-CLI native.** Full flush, URL-specific flush, and live status from the command line.
- **Context-aware admin UI.** Every setting, badge, and warning reflects the actual live server state — not generic documentation. Locked settings show which constant controls them.
- **Safe defaults.** Failed Redis connections fail open — the site serves normally without caching rather than returning errors. TTL expiry means stale cache is self-healing.

### Security

- **Three-layer security matrix.** Key salting, Redis ACL isolation, and Unix socket lockdown are configurable from the plugin UI without touching wp-config.php.
- **Correct incompatibility detection.** The salt form is blocked when Nginx direct-Redis mode is active, preventing users from creating a configuration that breaks their cache silently.

### E-commerce Specific

- **WooCommerce cart state is always live.** Cache-active visitors see correct cart counts via JavaScript hydration; PHP responses are never cached for users with items in cart.
- **Order-triggered invalidation.** Stock changes, order status updates, and sale price changes purge relevant cached pages automatically.
- **LMS and membership platforms** (LearnDash, LifterLMS, MemberPress) have full bypass and invalidation support — member-only content is never served from cache to anonymous visitors.

---

## 18. Shortcomings & Known Limitations

Understanding these limitations before deployment prevents unexpected behaviour.

### 1. Nginx direct-Redis mode is incompatible with key salting

When the Nginx server block is deployed and Nginx reads Redis directly, it uses a plain string key. SHA-256 hashing (Layer I salt) cannot be performed by Nginx — so Nginx always gets a MISS and falls through to PHP. The plugin detects this and blocks the salt form when the conflict is detected. **Resolution:** Use either salting (PHP drop-in mode) or Nginx direct-Redis, not both.

### 2. No cache warming — first visitor takes the full hit

LumenCache is a *passive* cache: pages are written to Redis on the first uncached request. After a full flush (plugin update, theme switch, etc.), every page must be visited once by a real visitor before it is cached again. There is no built-in crawler or preloader. **Resolution:** Use an external tool (e.g., `curl` sitemap loop, screaming frog) to warm the cache after bulk flushes.

### 3. Logged-in users never receive cached pages

By design, any user with a `wordpress_logged_in_*` cookie bypasses the cache entirely. This is correct behaviour (personalized admin bars, user-specific content) but means your admin team always generates PHP load. **Resolution:** Use a staging environment for content preview; the cache is for anonymous visitors.

### 4. Full-page cache only — no fragment or partial caching

LumenCache caches the entire HTML response. It cannot cache individual widgets, sidebars, or dynamic sections while leaving others live. The REST API + JavaScript hydration pattern (`data-lc-key="cart-count"`) provides a workaround for small personalised fragments in otherwise-static pages, but does not support arbitrary partial caching.

### 5. Query string caching is a Pro feature

By default, any URL with non-tracking query parameters (`?in_stock=1`, `?color=blue`) bypasses the cache. Enabling query-string caching (which gives each parameter combination its own Redis entry) requires a Pro license. For sites with large filtered product listings, this can result in lower cache hit rates.

### 6. LiteSpeed native cache integration not supported

LiteSpeed is supported via the Apache-style PHP proxy (`lumen-cache-proxy.php`), not via LiteSpeed's native `X-LiteSpeed-Cache` header integration. Sites using LiteSpeed Enterprise with LSCache active alongside LumenCache should disable one or the other to avoid conflicts.

### 7. LUMENCACHE_DEBUG log is unbounded

When `define('LUMENCACHE_DEBUG', true)` is set, every request writes a detailed trace to `wp-content/lc-debug.log`. There is no log rotation or size limit. On a live site, this file can grow to gigabytes within hours. **Always remove the constant immediately after diagnosis.**

### 8. WordPress.org distribution and server requirements

LumenCache is distributed under GPL-2.0-or-later. The repository
`readme.txt` contains the WordPress.org directory metadata. This longer
`docs/DOCUMENTATION.md` file is bundled as an administrator and developer
manual; it is not used as the directory listing readme.

The plugin requires a Redis service and a PHP Redis client. The normal PHP
drop-in path does not require an Nginx Redis module. The optional Nginx
direct-Redis path requires an Nginx build with `redis_pass` support. Server
configuration changes are opt-in and should be reviewed by the server
administrator.

Current compatibility information:

- Requires WordPress 6.0 or later.
- Tested up to WordPress 7.1.
- Requires PHP 8.1 or later.
- PHP 8.5.8 was used for compatibility testing.
- License: GPL-2.0-or-later.

`Tested up to PHP` is not a recognized WordPress.org readme header, so the
PHP 8.5.8 test result is documented in the changelog and this manual instead
of being added as unsupported repository metadata.

### 9. Remote Redis cluster support is Pro only

The basic tier connects to a single Redis instance (local socket or TCP). Connecting to a Redis Sentinel cluster, Redis Cluster, or a managed cloud Redis (Upstash, ElastiCache, Redis Cloud) requires a Pro license and the custom connection override feature.

### 10. Apache proxy requires mod_rewrite

The `.htaccess` rewrite rules and proxy depend on `mod_rewrite` being enabled in Apache. Most shared hosting environments have it enabled, but it must be confirmed for non-standard setups.

### 11. Key prefix must match across all serving layers

If you change the Key Prefix setting (default: `cache:`), you must also update the Nginx server block (`set $redis_key "newprefix:…"`), regenerate `.htaccess` rules, and redeploy the drop-in. A mismatch means the Nginx/Apache layer never finds keys that the PHP layer wrote, resulting in a 0% HIT rate from those layers while PHP drop-in continues working.

### 12. No multisite network cache isolation by default

On WordPress Multisite, all subsites share the same Redis key namespace (the key prefix). If subsites have overlapping URL structures (e.g. subfolders), keys can collide. **Resolution:** Set a unique `LUMENCACHE_REDIS_PREFIX` per subsite, or use separate Redis databases (`LUMENCACHE_REDIS_DATABASE`).

### 13. Nginx direct-Redis mode requires the `ngx_http_redis` module

The Nginx direct-Redis server block uses `redis_pass`, which requires the `ngx_http_redis` module. This is not compiled into Nginx by default. On Ubuntu/Debian: `apt install nginx-extras`. Alternatively, use OpenResty (which includes the module by default). Standard `nginx` package from the nginx.org repo does not include this module.

---

## 19. Troubleshooting

### Redis connection unavailable

1. Verify Redis is running: `redis-cli ping` should return `PONG`
2. Check the socket path: `ls -la /var/run/redis/redis.sock`
3. Verify PHP-FPM's process user has read/write access to the socket: `stat /var/run/redis/redis.sock` → check group and permissions
4. If using TCP, confirm the port is open: `redis-cli -h 127.0.0.1 -p 6379 ping`
5. If a password is set, test: `redis-cli -a yourpassword ping`

### Cache hit rate is 0% (no pages being cached)

1. Check that the drop-in is deployed: **Server Setup** tab → PHP drop-in row should show ✓
2. Confirm `WP_CACHE = true` in wp-config.php
3. Open an incognito window and visit a page — confirm no `wordpress_logged_in_` cookie is sent
4. Check `wp lumencache status` — if Cached pages = 0 after an incognito visit, enable debug mode temporarily

### Debug mode

Add to wp-config.php:

```php
define( 'LUMENCACHE_DEBUG', true );
```

Visit a page in incognito. Read `wp-content/lc-debug.log`. Look for:
- `bypass=true` and the reason — tells you why the page was not cached
- `redis->set()=SUCCESS` — confirms the page was written
- `redis->get()=HIT` — confirms subsequent requests served from cache

**Remove `LUMENCACHE_DEBUG` immediately after diagnosis.**

### Cached pages counter always shows 0

Symptom: Redis is connected and pages are being cached, but the counter shows 0.

This was a known bug in versions before 1.2.3 caused by misuse of the phpredis `SCAN` API. Update to 1.2.3 or later.

### Pages not updating after content changes

1. Confirm automatic invalidation hooks are firing: check if `save_post` is being called (can be blocked by direct database writes or cron-based publishing plugins)
2. Flush manually: **LumenCache → Flush All Cache**
3. If a specific post type is not invalidating, check if it uses a custom publishing hook that bypasses `save_post`

### Admin bar flush does nothing

Confirm the nonce is valid (sessions can expire on long-open tabs). Reload the page and try again.

### Salt form shows "Salt not available" on a PHP drop-in server

This means `nginx_config_active()` returned true — the Nginx direct-Redis server block was previously detected (via `X-Cache-Status` header) and the result is cached in a WordPress transient for 24 hours. If you removed the Nginx server block, wait 24 hours or delete the `lumencache_nginx_confirmed` transient from the database.

---

## 20. Glossary

**Cache HIT:** A request whose response was served entirely from Redis without loading WordPress.

**Cache MISS:** A request where no cached entry was found. WordPress loads normally; the response is written to Redis at the end.

**Cache TTL:** Time To Live — the number of seconds Redis keeps a cached page before automatically expiring it.

**Drop-in:** A PHP file in `wp-content/` that WordPress loads automatically at a specific point in its boot sequence. `advanced-cache.php` is loaded before any plugins.

**Key prefix:** A string prepended to every Redis key to namespace it. Default `cache:`. Used to isolate LumenCache entries from other data in the same Redis instance.

**Key salting:** Applying a secret string + SHA-256 hash to the cache key so that raw key inspection reveals only hashes, not readable URLs.

**ngx_http_redis:** An Nginx third-party module that allows Nginx to read values from Redis directly using the `redis_pass` directive, without invoking PHP.

**PHP drop-in mode:** The LumenCache serving layer where `advanced-cache.php` checks Redis early in the WordPress boot and serves cached HTML if available.

**Tracking parameter:** A URL query string parameter appended by an ad platform (e.g. `?srsltid=`, `?gclid=`) that identifies the click source but does not affect page content.

**Unix socket:** An inter-process communication file (e.g. `/var/run/redis/redis.sock`) that Redis listens on instead of a TCP port. Faster than TCP and not accessible over the network.

---

*Documentation for LumenCache v1.6.9 · Last updated 2026-08-09*
