Skip to content

ESI Support (Edge Side Includes)

Cache pages that contain per-user WordPress nonces by delivering the nonce as a cheap ESI sub-request instead of baking it into the HTML body.

The problem

wp_create_nonce() and wp_nonce_field() return values that change per user and per 12-hour tick. Any page carrying one of those values can't be cached — each visitor needs a different nonce.

The approach

When you put a nonce action on the whitelist, Cacheability Pro:

  1. Intercepts wp_create_nonce() and returns a placeholder string instead of the real nonce.
  2. During page rendering, replaces the entire <input value="placeholder"> element with <esi:include src="/?cacheability_esi=nonce&action=..." />.
  3. Ships the page to Varnish with Surrogate-Control: ESI/1.0.
  4. Varnish caches the page body indefinitely. When a visitor hits it, Varnish fires one cheap sub-request per nonce to fetch the fresh value.
<!-- Origin response (cached in Varnish) -->
<esi:include src="/?cacheability_esi=nonce&action=d3BfcmVzdA&name=_wpnonce" />

<!-- What the visitor's browser actually receives -->
<input type="hidden" name="_wpnonce" value="a1b2c3d4e5" />

Setup

  1. Configure Varnish. Add the snippet from the plugin settings page (or include vcl/cacheability-pro.vcl from the plugin directory) into your VCL. It advertises ESI capability, passes the nonce endpoint uncached, and enables beresp.do_esi on responses that request it.
  2. Activate the plugin. Activation copies the required MU-plugin to wp-content/mu-plugins/cacheability-nonce-esi.php automatically. If the directory isn't writable, the settings page shows a "Copy Snippet" fallback you can paste manually. The installed copy is refreshed on every admin page load if the bundled version is newer.
  3. Go to Cacheability Pro → Cache Controls & Policies → ESI Support. Check "Enable Edge Side Includes for WordPress nonces".
  4. List the nonce actions you want to ESI-ify in the "Nonce actions to ESI-ify" textarea. One action per line. * is a wildcard (e.g. woocommerce-*). The list is empty by default — the feature does nothing until you add at least one action.

Common starting points:

wpcf7-form-*
wpforms-nonce

Anything not on the list returns a real WordPress nonce like it always did.

Important constraints

Only <input> contexts are served via ESI. Varnish's ESI parser cannot process <esi:include> tags that live inside HTML attribute values or JavaScript string literals (LiteSpeed's as-var='1' workaround is proprietary to LiteSpeed Web Server and not available on Varnish). That means:

  • Nonces in URLs: href="?_wpnonce=..."
  • Nonces in wp_localize_script() output: var settings = {nonce: "..."}
  • Nonces in inline JS: <script>var x = "...";</script>
  • Nonces in data-attributes: data-nonce="..."

...cannot be ESI-included. Do not whitelist actions whose nonces render into these contexts — you would end up with broken pages (literal placeholder strings reaching the browser). Stick to actions that appear in hidden form inputs.

Anonymous visitors only. Placeholder mode is disabled for logged-in users. Logged-in users get real nonces, just like before, because admin-bar URLs and personalized markup aren't safe to share across sessions. If you need per-user caching for logged-in users, that requires private-cache infrastructure (LiteSpeed Web Server or a custom Varnish setup with per-session vcl_hash) which this plugin does not provide.

Page caching for anon users. Because nonce_user_logged_out returns 0 for all anonymous users by default, every anonymous visitor shares the same nonce for the same action during a given 12-hour wp_nonce_tick(). The ESI sub-request gives you per-request freshness on top of that — so a cached page body can live far longer than a nonce tick without breaking form submissions.

Varnish VCL

The plugin ships a production-ready VCL at vcl/cacheability-pro.vcl in the plugin directory. You can also copy it from the admin page (Cache Controls → ESI Support → "Varnish VCL snippet" disclosure).

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

If you prefer code over the admin textarea, register actions with the cacheability_pro_esi_nonce_actions filter:

add_filter( 'cacheability_pro_esi_nonce_actions', function ( $actions ) {
    $actions[] = 'wpcf7-form-*';
    $actions[] = 'wpforms-nonce';
    return $actions;
} );

Values from this filter are merged with the admin textarea; either source is fine.

Troubleshooting

  • "Nothing happens when I enable ESI" — the whitelist is empty. Add at least one action name. Until you do, the feature is deliberately inert.
  • "MU-plugin status: Not installed" on the settings page — wp-content/mu-plugins/ is not writable. The settings page shows a snippet you can save there manually.
  • Placeholder strings in rendered HTML — you've whitelisted an action whose nonce doesn't end up in an <input> element. Remove it from the list.
  • REST API calls fail with "cookie nonce is invalid" — you likely whitelisted wp_rest. Remove it. wp_rest nonces are emitted via wp_localize_script into inline JSON and can't be ESI'd on Varnish.