Skip to content

About

Kernel-integrated Nginx edge cache purge package for WordPress.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

SymPress Nginx Cache

PHP: ^8.5 License: GPL-2.0-or-later

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.

Package

composer require sympress/nginx-cache

The package is discoverable by sympress/kernel through Composer metadata:

{
  "extra": {
    "kernel": {
      "bundle": "SymPress\\NginxCache\\NginxCacheBundle",
      "entry": "nginx-cache/nginx-cache.php"
    }
  }
}

WP-CLI

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 details

The 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.

Configure Nginx caching

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.conf

Add 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.

Dashboard metrics

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.

Cache limits and background prewarm

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.

Features

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-Tag headers 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.

Tested scale

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.

Nginx Helper compatibility

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.

About

Kernel-integrated Nginx edge cache purge package for WordPress.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages