Skip to content

ESI Support (Edge Side Includes)

Cacheability Pro lets an ESI-capable cache, including NGINX with the ESI module or Varnish, cache anonymous page shells while fetching fresh WordPress nonce inputs for each response. Normal premium licensing is required. The feature is disabled by default, and nonce replacement stays inactive until you list at least one audited action.

How nonce ESI works

WordPress nonces depend on the action, user/session and time window. A page cache can otherwise retain a nonce after it expires. With an action on the whitelist and a trusted cache advertising ESI support, Pro:

  1. Returns a placeholder from wp_create_nonce() for that action.
  2. Replaces the entire input containing the placeholder with an ESI include.
  3. Marks the response with Surrogate-Control: content="ESI/1.0".
  4. Lets the cache retain that shell for its configured TTL and fetch the nonce fragment uncached when assembling each response.

For an audited example action named my_form_action:

<!-- Origin response stored in the shell cache -->
<esi:include src="/?cacheability_esi=nonce&amp;action=bXlfZm9ybV9hY3Rpb24&amp;name=_wpnonce" />

<!-- Assembled response delivered to the browser -->
<input type="hidden" name="_wpnonce" value="a1b2c3d4e5" />

Fresh means generated for the current request, not a different value on every request. With WordPress defaults, anonymous visitors share a nonce for the same action during a nonce tick. Sites customizing nonce_user_logged_out or anonymous sessions need a separate audit of their identity and cache behavior.

Select actions safely

List only actions used exclusively in hidden form inputs. Pro replaces whole input elements. It cannot replace nonces inside URLs, inline JavaScript, localized JSON or data attributes. Audit every use of an action in the theme and plugins, including any client-side form logic, before listing it.

Do not whitelist wp_rest: its nonces commonly appear in localized JavaScript. Do not assume a plugin-specific action or a broad wildcard is safe merely because one form uses it. Unsupported uses leave literal placeholders in the browser and break the affected feature.

Anonymous visitors only. Logged-in users receive real nonces and must bypass the shared page cache. Pro does not provide private per-session page caching. Admin, AJAX, REST, cron, CLI and non-GET/HEAD requests also retain real nonces.

Plugin setup

  1. Configure and validate the ESI cache as described below before enabling replacement. Never let a client choose the surrogate capability header.
  2. Activate normally licensed Pro. Activation installs wp-content/mu-plugins/cacheability-nonce-esi.php. If the directory is not writable, the settings page provides a manual installation snippet.
  3. Open Cacheability Pro → Cache Controls & Policies → ESI Support and enable nonce ESI.
  4. Enter audited action names, one per line. * is supported as a wildcard; every matching action still needs an audit. An empty list disables nonce replacement. Other actions keep their real WordPress nonces.

ESI-only integration

To retain an existing site's other cache/header behavior, define CACHEABILITY_PRO_ESI_ONLY as boolean true in wp-config.php before plugins load. Use compatible free Cacheability if it supplies the site's existing behavior. Normal premium licensing, the ESI setting, action list and trusted capability still apply. This mode does not initialize Pro's unrelated rewriting, page-cache or warming features. Before switching an existing full Pro install, use the normal controls to disable/remove its page-cache drop-in and warming schedules, then deactivate Pro before changing its mode. The constant cannot stop an earlier advanced-cache.php. Disable independent full comment-form ESI when validating only nonce inputs:

add_filter( 'cacheability_pro_esi_comment_form', '__return_false' );

Cache and delivery requirements

These requirements apply to both NGINX and Varnish:

  • Replace incoming Surrogate-Capability with a trusted ESI capability only on routes whose responses actually pass through ESI processing. Clear it on other WordPress routes and prevent untrusted access to the backend.
  • Bypass nonce fragments, authenticated traffic and sensitive routes. Honor upstream cache prevention and Set-Cookie; cache only eligible page shells.
  • Deliver assembled nonce-bearing HTML with Cache-Control: no-store. Apply this at delivery without disabling the internal shell cache. Prevent conditional 304 responses from allowing an old assembled nonce to be reused.
  • Check CDN rules as well as headers. Existing Cache Everything/TTL overrides can retain assembled HTML. Bypass dynamic HTML and invalidate old HTML objects at cutover, while keeping static CSS, JavaScript and images cacheable.
  • Verify complete HTML with no ESI tags or placeholders, valid nonce inputs on shell HITs, login/admin behavior, real form behavior and scoped purges.

NGINX integration

Use ABI-matched nginx-module-esi and, for native invalidation, nginx-module-cache-purge packages. Legacy PageSpeed requires ESI 1.0.2 or later, which fixes the shared buffering-bit conflict. Use cache-purge 2.6.1 or later when the purge consumer verifies Cache-Purge-Result. Headers-more provides the request-header clearing used by the tested configuration.

The NGINX integration tests exercise both HTTP proxy and FastCGI caches against real WordPress. Maintainers with source-repository access can inspect the fixtures. Production integration must preserve these contracts:

  • Preserve distinct permalink keys across WordPress's /index.php rewrite. FastCGI uses the original request URI; fragment queries must bypass caching.
  • Use volatile bypass maps so ESI subrequests evaluate their own URI/arguments instead of reusing the parent page's decision. Check both original and rewritten paths for sensitive routes.
  • Enable esi on on the cached PHP path and inject trusted capability there. Clear If-None-Match and If-Modified-Since before core conditional handling, including on cache HITs. Strip upstream compression where needed so ESI can process the HTML.
  • Serve static files directly. Keep open_file_cache off on cached PHP while retaining the PHP existence guard: open descriptors can otherwise keep a deleted cache object serving after a confirmed purge.
  • Restrict PURGE to an explicitly trusted source. Native tag purges use response X-Cache-Tags and request X-Cache-Tags-Pattern; verify a positive Cache-Purge-Result and target MISS with an untouched control HIT.

PageSpeed needs additional integration checks. Retain the correct public HTTPS identity for resource fetching, keep its optimized assets working, and prevent ModifyCachingHeaders from replacing the assembled response's no-store policy. The Pro test fixture does not contain PageSpeed; it cannot prove a production filter chain. Run configuration validation, full rendered-config analysis and live response/asset checks before cutover, with a per-site rollback and soak.

Varnish integration

The bundled vcl/cacheability-pro.vcl supplies ESI capability and fragment handling. The settings page also exposes it under Varnish VCL snippet. This excerpt shows those parts; complete the delivery policy above separately:

sub vcl_recv {
    set req.http.Surrogate-Capability = {"varnish="ESI/1.0""};
    if (req.url ~ "[?&]cacheability_esi=") {
        return (pass);
    }
}

sub vcl_backend_response {
    if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
        unset beresp.http.Surrogate-Control;
        set beresp.do_esi = true;
        set beresp.do_gzip = true;
    }
}

Adding actions via a filter

For an action whose complete usage you have audited:

add_filter( 'cacheability_pro_esi_nonce_actions', function ( $actions ) {
    $actions[] = 'my_form_action';
    return $actions;
} );

Filter values are merged with the admin textarea.

Troubleshooting

  • No replacement: check normal premium activation, the ESI setting, action whitelist, installed MU-plugin and trusted capability from the actual cache.
  • MU-plugin not installed: use the settings page's manual snippet when wp-content/mu-plugins/ is not writable.
  • Literal placeholders: remove the affected action and inspect all its rendering contexts before enabling it again.
  • REST cookie nonce errors: remove wp_rest from the list; localized JSON is not a supported replacement context.
  • Stale forms despite shell HITs: verify fragment bypass and current nonce validity, then inspect browser/CDN caching and conditional responses for the assembled HTML.