Skip to content
WP Engine Developers

WPGraphQL Smart Cache Optimization on WP Engine Managed Hosting

This guide covers WPGraphQL Smart Cache as it relates to leveraging network caching on WP Engine’s Managed Hosting for WordPress.

This guide assumes familiarity with:

To enable network caching, change your GraphQL client from using POST requests to GET requests. This is the only required step other than installing WPGraphQL Smart Cache.

To disable caching for specific queries, switch those specific requests back to HTTP POST requests. This assumes you’re not using Smart Cache’s object caching feature.

On the WP Engine platform, all requests go through a minimum of two layers of caching + the browser cache. However, events from WordPress, via Smart Cache, purge only one of these caches.

This could be the user’s browser or your headless application server. Whatever the source, these caches usually operate independently of Smart Cache and thus solely rely on the TTL and cache-control headers to determine caching.

The next possible cache is WP Engine’s Advanced Network (AN), powered by Cloudflare. By default, this will not cache GraphQL responses. Cloudflare considers HTML and JSON responses dynamic, and thus uncacheable, without additional configuration. To resolve this, WP Engine recently introduced the Edge Full Page Cache (EFPC) – a feature of AN that enables caching HTML and JSON responses.

Enabling EFPC means our GraphQL responses are now being cached on the edge. However, EFPC does not cache based on keys and is not purged by Smart Cache. It must rely on the configured TTL.

Anywhere you read Advanced Network (AN) in this guide, you can substitute Global Edge Security (GES) if you’re using that product instead. EFPC is available on both AN and GES.

Third, all WP Engine WordPress sites have a Varnish server cache. Varnish is the only place (other than the object cache) where Smart Cache purges data. Varnish also uses the configured TTL as a fallback.

Flow diagram illustrating the described cache and cache invalidation mechanisms.

These are the only caches on the WordPress side. Your headless application may have its own GraphQL cache, and pages can also be cached by frameworks or ISPs based on the Cache-Control header. All downstream caches rely on the configured TTL.

The following table summarizes how each cache layer handles GraphQL responses:

CacheCaches GraphQL responsesTTL ConfigTag-based cache invalidationTime-based cache invalidation
BrowserYESCache-ControlNOYES
Advanced NetworkCONFIGURABLECache-Control, CDN-Cache-ControlNOYES
VarnishYESCache-ControlYESYES

With a site on WP Engine, a correctly configured GraphQL client, and default Smart Cache settings, the three caching layers are the browser, Cloudflare, and Varnish.

The table columns show the cache status header returned at each layer:

LayerFirst request ever from Visitor ANth response from Visitor B
BrowserMISSMISS
CloudflareDYNAMICDYNAMIC
VarnishMISSHIT: N
ServerResponds

Neither visitor has a browser cache, so the request passes to Cloudflare. Cloudflare responds with DYNAMIC because JSON is considered uncacheable content (EFPC is not enabled). The first request misses Varnish and the server responds. Visitor B’s request hits the Varnish cache.

By default, Varnish caches the response for 600 seconds (10 minutes) or until a tag-based purge event occurs, whichever comes first. The Smart Cache TTL setting adjusts this value.

Subsequent requests within the TTL window are served from the browser cache. After expiry, the cycle restarts. New visitors continue to receive Varnish cache hits until the next invalidation.

Varnish response times are roughly an order of magnitude faster than the server (~2s => ~200ms). However, the browser and Varnish share the same TTL by default. This means the browser cache continues serving stale data for 600s even after Smart Cache invalidates data in Varnish via a purge event.

Key behaviors of the default config:

  • WP Engine’s Varnish does not allow TTLs lower than 600s (higher values are respected).
  • Setting a Smart Cache TTL to 60s results in a 60s browser cache but Varnish still uses 600s — quick navigations use browser cache, while subsequent requests get fresh data from Varnish.
  • Smart Cache sets headers as max-age={TTL}, s-maxage={TTL}, must-revalidate. There is no UI option to set s-maxage independently.
  • The graphql_response_headers_to_send filter can override these headers from PHP.

Separate browser and Varnish TTLs with Web Rules

Section titled “Separate browser and Varnish TTLs with Web Rules”

WP Engine Web Rules can add or modify headers after Varnish. This allows setting a Cache-Control header that affects Cloudflare and the browser without changing Varnish behavior.

  1. In the WP Engine User Portal, navigate to Web Rules for your environment.
  2. Create a new rule with a condition matching the GraphQL endpoint path (e.g., /graphql).
  3. Set the action to “Set Response Header”.
  4. Set the header name to Cache-Control and the value to your desired directive (e.g., max-age=300, s-maxage=700, public, stage-while-revalidate=60).
  5. Save and deploy the rule.

Web Rules configuration for Cache-Control headers

Smart Cache settings now only affect Varnish TTLs, so Varnish TTLs can be raised while browser/CDN TTLs are lowered — resulting in higher hit rates and fewer stale responses.

The different headers Smart Cache sets (e.g., X-GraphQL-Query-ID) can also be used in Web Rules conditions to configure different cache control values by post type, query, or other criteria.

With EFPC enabled and the same configuration as above, Cloudflare begins caching JSON responses.

LayerFirst request ever from Visitor ANth response from Visitor B
BrowserMISSMISS
CloudflareMISSHIT
VarnishMISSHIT: N
ServerResponds

Now that Cloudflare knows how to cache JSON with EFPC, responses will start receiving HIT status. EFPC fully relies on TTLs to invalidate data. Once the data goes stale, Cloudflare requests a fresh response from the server stack, where Varnish can respond.

After the TTL expires, Cloudflare returns a stale response and revalidates against the origin, where Varnish responds with a HIT. The request only reaches the WordPress server once Varnish itself receives a purge event or its TTL expires. Additional Cloudflare points of presence are also primed with data from the Varnish cache.

EFPC reduces latency by roughly another order of magnitude (~200ms => ~20ms). With this config, the browser and Cloudflare can be controlled separately using max-age and s-maxage respectively.

To target Cloudflare independently from other downstream shared caches, use the CDN-Cache-Control header. See the Cloudflare CDN-Cache-Control docs for details.

For most headless WordPress sites on WP Engine, the following configuration provides a good balance of freshness and performance:

SettingValueReason
Smart Cache TTL900s (15 min) or higherMaximizes Varnish hit rate
Web Rule max-age60sLimits stale browser cache after purge events
Web Rule s-maxage600sAllows shared caches (Cloudflare) longer TTL
EFPCEnabledAdds edge caching for much improved response times

The following are known behaviors and quirks of caching on the WP Engine platform with Smart Cache.

  • Purged by events or TTL, whichever comes first.
  • Cache-Control headers from WordPress are moved to x-orig-cache-control and overridden by Varnish.
  • Sending requests with x-wpe-no-cache: true generally disables the Varnish cache, but does not affect AN/EFPC. Varnish ignores Cache-Control: no-cache request directives.
  • Varnish resets TTLs from Smart Cache below 600s to 600s. TTLs over 600 are respected by Varnish.
  • Regardless of the configured TTL, the response header is modified to max-age: {ttl}, must-revalidate; all other directives are stripped.
  • Web Rules modify headers after Varnish. The cache control headers you set in Web Rules do not change Varnish behavior.
  • POST requests are never cached.
  • Editing a page configured as the WordPress “Home” page purges the entire domain in Varnish.
  • Not purged by events — only invalidated by TTL expiry.
  • Cache-Control, CDN-Cache-Control, and Cloudflare-CDN-Cache-Control headers can all configure EFPC behavior. Cloudflare uses only the most specific header, not a combination.
  • Cloudflare ignores Cache-Control: no-cache request directives.
  • POST requests are never cached.

For additional help with caching configuration, contact WP Engine support or join the Headless Discord.

Last updated: