SymPress Nginx Cache provides WordPress cache purge controls for Nginx FastCGI, proxy and uWSGI cache setups running on the SymPress kernel. It combines admin tools, WP-CLI commands, automatic purge hooks, surrogate tag tracking, queue processing, diagnostics and generated Nginx configuration.
Version 1 adds safe Multisite scope, Network Admin, delegated editor actions, a network worker, migration, Site Health, compact setup, scalable tag retention and Polylang integration. See operation and configuration, migration and the extension API.
composer require sympress/nginx-cacheThe package is discoverable by sympress/kernel through Composer metadata:
{
"extra": {
"kernel": {
"bundle": "SymPress\\NginxCache\\NginxCacheBundle",
"entry": "nginx-cache/nginx-cache.php"
}
}
}wp nginx-cache status
wp nginx-cache purge /
wp nginx-cache purge --queue --prewarm
wp nginx-cache diagnostics
wp nginx-cache config
wp nginx-cache queue details
wp nginx-cache side-effects detailsThe legacy edge-cache namespace is also registered for compatibility.
Queues retain failed work, retry at most five times with capped exponential
backoff, and expose exhausted work through status and details. After fixing
the cause, wp nginx-cache queue retry or wp nginx-cache side-effects retry
resets its retry budget and schedules processing. See the
side-effect contract for timing and delivery guarantees.
New purge events use 64 bounded merge slots plus at most four overflow markers for the dry-run/prewarm combinations. When selective work exceeds the URL budget, it becomes a full invalidation within its original scope rather than losing URLs. URL-only editor/REST requests retain selective targets and fail explicitly if the durable inbox is unavailable; they never escalate to a site purge. Retry exhaustion does not scan the inbox on content hooks. Existing legacy inbox rows are ingested in batches of at most 68 after processing or an explicit retry resumes.
Cache directory does not exist. means the plugin's configured cache path is
missing. Installing the plugin alone does not configure Nginx. Apply the HTTP
and FastCGI sections generated by the package, using the same path for Nginx and
the plugin. Nginx creates the cache directory when it loads fastcgi_cache_path;
its worker user and PHP must both be able to read and write that directory.
Generate separate includes with wp nginx-cache config --section=http and
wp nginx-cache config --section=fastcgi. The default --section=all output
remains a reference containing sections for multiple contexts; it cannot be
included as a single Nginx configuration file. --section=server exports the
static-asset location and metrics logging directive. --section=logging
exports only the metrics logging directive. These same options work through
wp console nginx-cache:config.
For a project-owned DDEV Nginx configuration:
mkdir -p .ddev/nginx-cache
ddev exec --raw -- wp nginx-cache config --section=http > .ddev/nginx-cache/http.conf
ddev exec --raw -- wp nginx-cache config --section=fastcgi > .ddev/nginx-cache/fastcgi.confAdd include /mnt/ddev_config/nginx-cache/http.conf; above the server block
in .ddev/nginx_full/nginx-site.conf. Inside the existing PHP location, add
include /mnt/ddev_config/nginx-cache/fastcgi.conf; after
include fastcgi_params;. Remove the #ddev-generated marker if present, then
run ddev restart. Ensure generated files contain only Nginx directives; PHP
diagnostics from an outdated WP-CLI must not be redirected into them.
Regenerate both includes after changing cache profiles or bypass rules.
Repeat existing server-level add_header directives in the PHP location after
the include, for example add_header X-Content-Type-Options nosniff always;.
The cache status headers otherwise replace Nginx's normal header inheritance.
See DDEV's Nginx customization
and the NGINX FastCGI cache directives.
Verify X-Nginx-Cache: MISS followed by HIT on an anonymous page. Admin,
login, authenticated requests, semantic queries and non-GET/HEAD methods must
avoid cache hits. Eligible GET/HEAD requests report BYPASS; other methods may
omit X-Nginx-Cache because they never enter the cache handler.
Do not suppress the directory warning or mark an arbitrary
directory as a cache root merely to make the dashboard green.
Regenerate older configurations: the empty-query allowlist must use the exact
"" 0; map entry because Nginx skips regex matching for empty map inputs.
Run composer tests:nginx with a real nginx binary to verify directory creation
and query/authentication bypass maps against an isolated local Nginx instance.
The performance panel combines the hit rate, its request denominator and a
proportional cache-served/newly-requested split. File, size, queue and tag counts
remain alongside it. Measurement details are initially collapsed and contain
all six Nginx status counts, eligibility rules and sample limitations. Native
keyboard controls open the details without submitting the settings form.
The plugin uses NGINX green accents and black action buttons, independently of
the surrounding WordPress admin theme. Card headings share one type style;
section descriptions have explicit spacing beneath their titles.
The purge-rule matrix scrolls within the Cache section on narrow screens and
can be focused and scrolled by keyboard. Section buttons expose their active
state with aria-pressed.
The HTML prototype provides preview states;
its example data and state selector are not production measurements or settings.
Cache file count and size inspect the configured cache root (excluding the
plugin lock and sentinel); queue count and tag/URL counts query their persistent
stores. Counts can be zero when no anonymous page is cached. A bounded filesystem
scan displays a + after both file count and size when it is incomplete.
Unavailable roots show an unavailable value instead of a misleading zero. The
header confirms the cache directory's readiness, not that Nginx served a HIT.
The hit rate reads real Nginx requests, including hits that never execute PHP.
Apply the HTTP include (which defines the JSON log format) and export
wp nginx-cache config --section=logging into a separate include. Add that
include alongside existing access_log directives in the server block;
location-specific access_log overrides must also include it to count those
requests. Existing access logging must be preserved.
The default log is /var/log/nginx/sympress-cache-metrics.jsonl. Override it with
the private SYMPRESS_NGINX_CACHE_METRICS_LOG PHP constant and regenerate the
includes. Keep the absolute path outside the cache root and document root, grant
PHP read access, and configure normal log rotation. Nginx writes only timestamp,
host and cache status, without IPs, URLs, cookies or authorization headers.
See the NGINX access log directives.
The card covers this site's host over the last 60 minutes. HIT, STALE, UPDATING and REVALIDATED count as cache-served responses; MISS and EXPIRED count as misses. BYPASS and non-cache requests are excluded. Reads are limited to the last 1 MiB and 5,000 records and labelled as a sample when capped. Missing/unreadable logs or no eligible samples show no measurement, never an invented 100%. The card distinguishes missing/unreadable logs from a readable log with no eligible requests in the current window. Dashboard values are a snapshot taken when the page loads; reload after generating anonymous traffic. The dashboard performs no HTTP probes, log writes or purge operations. One shared diagnostic snapshot supplies the page, avoiding duplicate filesystem scans, configuration generation, history and queue reads. The details show each cache status separately, the snapshot time, and a small-sample notice below 100 eligible requests. Prewarm requests are included in the percentage.
The Cache tab configures these generated Nginx limits. All profiles preserve the same defaults; changing a profile does not silently increase cache lifetime.
Setting / SYMPRESS_NGINX_CACHE_ constant suffix |
Default | Allowed range |
|---|---|---|
valid_seconds / VALID_SECONDS |
600 seconds | 60–86400 seconds |
inactive_seconds / INACTIVE_SECONDS |
3600 seconds | 60–604800 seconds |
max_size_mb / MAX_SIZE_MB |
256 MiB | 16–65536 MiB |
keys_zone_mb / KEYS_ZONE_MB |
100 MiB | 1–1024 MiB |
Options use the sympress_nginx_cache_ prefix. Constants override saved options.
Malformed values fall back to defaults and numeric values are bounded. Export,
review and apply both HTTP and FastCGI includes, then run nginx -t before
reloading Nginx. Saving plugin options alone does not alter the running server.
Upstream cache headers can override validity; inactive can evict an entry even
when its response remains fresh. Only extend validity with reliable automatic
invalidation. See the NGINX FastCGI cache directives.
Enable automatic purge, selective purge, the queue and prewarm together for targeted invalidation followed by warming affected pages. Each side-effect tick handles at most one task and five prewarm URLs. Target discovery is checkpointed once; each successful URL is checkpointed individually. Successful continuations are scheduled again without spending the five-failure retry budget. Failed HTTP responses (including redirects) remain retryable; acknowledged URLs and other providers are not repeated. Delivery remains at least once around a crash before a checkpoint. Same-origin checks, no redirects and five-second request limits remain enforced. Initial opt-in sitemap discovery retains its separate bounded 20-map budget. Explicit CLI prewarm remains synchronous and reports failure with a nonzero exit code.
Run wp cron event run --due-now once per minute through an external scheduler
under the site's normal user. Cached visitors do not execute WordPress and cannot
be relied on to drain queues. Once that runner is verified, configure
DISABLE_WP_CRON=true to avoid visitor-driven scheduling. Monitor pending and
exhausted tasks via wp nginx-cache queue and wp nginx-cache side-effects.
See WordPress system scheduling.
Dry runs also work when Nginx has expired every cache file and retained only its empty hexadecimal directories. A dry run creates no cache directory, sentinel or lock file and deletes nothing. It respects an existing purge lock and previews a snapshot when no lock exists. The notice reports the number of top-level entries that a full purge would remove; cache-file KPIs count files inside those entries. Failure notices include the actual cause rather than only a generic purge error.
WordPress admin notices appear below the plugin header, outside the onboarding banner and settings cards. A missing cache-directory warning means the configured local cache path is unavailable; installing the plugin does not enable Nginx FastCGI caching or create that directory.
-
Select Redis page-cache or Nginx
GET /purge/<path>backends instead of local files. -
Configure separate homepage, singular-page and archive rules for edits, deletes and comment approval/removal.
-
Discover prewarm URLs from same-origin sitemap indexes or URL sets.
-
Generate and optionally atomically maintain a Multisite Nginx map.
-
Optionally include a rendering timestamp, query count and duration in public HTML.
-
Purge Nginx cache files by URL, path, cache layer or full cache directory.
-
Queue purge requests and process side effects safely.
-
Track surrogate tags for targeted invalidation.
-
Purge affected posts, posts pages, feeds, date archives, paginated archives, AMP companion URLs, REST/GraphQL resources and WooCommerce product URLs.
-
Forward tag purges to Cloudflare through
Cache-Tagheaders and the Cloudflare purge API when configured. -
Delegate whole-zone purges to a protected Nginx endpoint instead of removing local cache files directly.
-
Inspect cache path availability, writability, file counts and byte size.
-
Generate Nginx cache snippets for the configured profile.
-
Prewarm selected URLs after purge operations.
-
Expose admin dashboard actions and REST endpoints for integrations.
Reproduce with composer tests:scale and the same disposable database/core
environment variables as the integration harness. The opt-in QA dispatch input
scale=true runs both MariaDB and MySQL and uploads build/scale-report.json.
CI gives both disposable database engines a fixed 512 MiB InnoDB buffer pool;
the report records effective memory, durability settings and the ten slowest
data queries. CI supplies the budget when starting its dedicated Docker
container. The harness never changes database server settings; local runs use
the server configuration prepared by the operator.
Measured locally on 2026-10-09: native Linux, PHP 8.5.4, WordPress 7.1.2, MariaDB 11.8.6 with a 64 MiB buffer pool; warm filesystem cache, zero external HTTP. Fixture seeding is excluded. Nginx entries use synthetic lightweight bodies; network fixtures have real site records and only the options tables used by the worker. Separate native Nginx tests verify real binary KEY parsing and MISS/HITs.
| Workload | Measurement |
|---|---|
| 50,000 posts, 200 categories; 200 content-hook samples | 9.3 ms save_post p95 |
| 10,000 cache files | 37,179 files/s site scan |
| 200 queued edits coalesced to one purge | 145.8 coalesced tasks/min (one measured task, not a capacity bound) |
| 100,000 indexed URLs / 200,000 rows | 159.0 ms maximum query; limits reached in 398 batches of at most 500 rows |
| Shared root, 500 sites × 200 files | 4.5 s full site purge, retaining all other sites |
| 500 pending site URL tasks | 9.2 s network worker |
| 5,000 registered sites | 27.0 ms for the current 20-row Network Admin page |
These measurements describe this fixture/environment, not production latency
guarantees. Provider requests and real page rendering add their own cost.
The report preserves maximum index data-query, transaction and overall statement
latency separately. The 200 ms index target applies to data queries. On the hosted
MySQL runner, one COMMIT took 330.9 ms (MariaDB: 690.7 ms); storage/fsync and scheduling can exceed
that target even when index queries meet it. No durability setting is relaxed.
Earlier default-sized MySQL runs also recorded isolated insert/delete spikes
of 303/413 ms and one 98.9 ms hook p95. The index alone allocated about 136 MB,
exceeding MySQL's 128 MiB default buffer pool.
Memory pressure is a possible contributor; these results remain limitations,
not a universal 200 ms latency guarantee. Final CI uses the explicit memory
budget above, with the same latency targets and full durability.
The Cache settings tab contains backend connection settings and the purge-rule
matrix. Existing installations continue using local files and their existing
all-scope rules. Redis and HTTP are alternatives to disk purging, not WordPress
object-cache adapters. Redis requires a site-dedicated, nonempty prefix; passwords
are encrypted and are never exported or rendered. TCP, ACL users and Unix sockets
are supported. Redis keys use either PREFIX$scheme$request_method$host$request_uri
(nginx-srcache / Nginx Helper) or SymPress's pipe-separated key format. Coordinate
the key and prefix with your server configuration. A full Redis purge removes
only that prefix. It never runs FLUSHDB/FLUSHALL.
For HTTP URL purges configure the same-origin prefix (default /purge) and the
existing remote signing secret. A compatible FastCGI/proxy purge module must
provide that location. Protect it with an IP allowlist or signature verification;
the module does not itself authenticate signature headers. The existing protected
full-purge endpoint is required for full HTTP purges. No redirects are followed.
Enable both prewarm and sitemap discovery to preload after full purges, or run
wp nginx-cache prewarm. The default sitemap is /wp-sitemap.xml; custom SEO
sitemap indexes can be configured. Discovery reads at most 20 sitemaps (2 MiB
each) and shares the existing configurable limit of up to 200 prewarm URLs.
Foreign-origin entries, external entities and redirects are rejected. Selective
purges continue warming only their affected URLs. The optional HTML stamp is
restricted to anonymous HTML responses; REST, feeds, AJAX, cron and login/admin
responses are unchanged. It records PHP rendering time, not a claim of a cache hit.
Network administrators can preview the Multisite map. For automatic persistence,
set SYMPRESS_NGINX_CACHE_MULTISITE_MAP_FILE to an absolute .conf filename outside
the public document root. Site creation/update/deletion and admin initialization
refresh that file atomically, leaving unchanged content and previous files on
failure intact. Nginx must include the map in its HTTP context and use
$sympress_blog_id in the appropriate static-upload rule. Nginx configuration,
legacy/current upload paths and reloads remain operator responsibilities. Up to
5000 active sites per current network are supported; domain aliases can be added
with sympress_nginx_cache_multisite_map_entries. For multiple networks configure
separate destinations per network in wp-config. WordPress itself detects Nginx
rewrite support; no additional index.php permalink workaround is installed.