Caching layers: three caches, three different promises.

Page, object and browser caches solve different problems. Learn what each one keeps, what makes it stale, and how we publish the site you are reading.

Three nested caching layers around the database Three concentric rounded frames seen slightly from above - BROWSER on the outside with a clock and max-age, EDGE in the middle with a CDN arc on its rim, a hanging cache-key tag whose cookie segment is outlined amber, and one stale page marked 86400s, and OBJECT CACHE inside with stacked plates - nested around a small database cylinder at the center. A request enters from the top left and is answered at the edge with a green check. BROWSER max-age EDGE 86400s host path query cookie OBJECT CACHE
Three frames, one request. It stops at the first cache that can answer.

Cache decisions, in numbers

1 year

Configured HTML lifetime in one incident, not a year of observed downtime.

24 hours

How long a poisoned redirect could remain cached without a purge.

270/day

Individual purge requests during a nightly sync, before they were batched.

60s

A suggested starting TTL for public HTML, paired with deliberate invalidation.

Each one makes a different promise.

Three caches, each with a different promise. A page cache promises a whole response. An object cache promises a query result. A browser cache promises nothing to anyone but one visitor. Treat them as one thing called "the cache" and you will clear the wrong one at two in the morning.

Cache layerWhat it answersWhat goes wrongWhat to check
Edge page cacheA CDN keeping whole responses, keyed by what it was told matters.Anonymous page views, assets, feeds and bots, answered before PHP runs.A stale page that looks fine, served confidently to everyone until something purges it.The key does not match what the origin varies on, or nothing purges what changed.
Object cacheRedis or Memcached in front of the database, holding query results, options and transients.The repetitive reads that would otherwise make the database the bottleneck on every request.Settings that will not save, jobs stuck at two percent, and one pod serving yesterday's layout.Separate per-pod caches can disagree. Shared caches still need correct invalidation and isolation between environments.
Browser cacheEach visitor's own copy, governed by max-age and the asset name.Repeat visits and every static file with a hashed name, for free.A visitor who cannot see the fix for a day no matter what you purge.A long max-age on HTML, or an asset name that did not change when its contents did.

What the edge thinks "the same request" means.

A cache key is the list of request attributes the edge uses to decide that two requests are the same. A useful key preserves real response differences without splitting identical pages into needless copies.

A real request on this site

https://udx.io/wordpress-on-linux/caching?json
Hostudx.io
Path/wordpress-on-linux/caching
Variant?json

Open it to see the layout data behind this chapter. Remove ?json and the same path returns HTML. Those are different responses, so they need different cache entries.

Host + pathidentify the page

On this site, udx.io and /wordpress-on-linux/caching identify the resource. Keep hostnames separate when the responses differ.

Query stringkeep meaningful variants

The ?json flag changes this page from HTML to layout data. It belongs in the key. Tracking parameters should not create another copy of identical content.

Cookieshandle personal traffic

Authentication or cart cookies need deliberate bypass rules. Removing them from a key is not enough if personalized responses can still reach a shared cache.

Headersvary only when needed

Our deployed page key currently includes Accept, Origin and Host. Review each against actual response variation before removing it.

HTML + JSONpurge both

This React-based theme requests a page and its layout data separately. Invalidating the HTML alone can leave the rendered page stale.

Forwardingmatch the key

If an unkeyed attribute is forwarded and changes the origin response, the cache can reuse the wrong answer. Review forwarding and key policies together.

Every change, against every object it must clear.

A purge map connects each content change to the cached responses it affects. This simplified before-and-after map comes from the operating record. A global purge covers every page even when there is no separate URL-level purge.

Scroll across the map on smaller screens.

Before purge coverage
Content changeThe permalinkHomepageArchives and feedsSitemapEvery page
Post edited
Menu changed
Theme option saved
Post trashed
Deploy
Nightly sync

Before: the save hook purged the edited permalink and nothing else. HTML lived at the edge for a day.

✓ Direct purge↗ Batched wildcard− No direct purge

The sync uses one wildcard invalidation instead of one request per event. The map records what each hook does; it does not say that every change requires every target.

One crawler, one redirect, eight percent of a homepage.

One incident deserves the full sequence, because it is the purest example of a key that disagreed with the origin, and because a healthy origin did not mean every edge location served the right answer.

  1. A crawler asks for a junk legacy URL

    /?utm_old=... on the homepage path, one of 95 such requests from 73 addresses that month.

  2. WordPress answers with a canonical redirect

    A 301 to the clean homepage. Correct behavior for one request.

  3. The edge stores it under the bare homepage key

    The policy ignored query strings in the key but forwarded them to the origin. The 301 was filed as the homepage, for 86,400 seconds.

  4. Eight percent of visitors loop

    At the affected edge locations the homepage redirected to itself. Two visitors looped 378 and 150 times. Every monitor stayed green because the monitors asked a different edge.

The fixForward only whitelisted parameters, then purge everything, because fixing the policy does not evict what is already poisoned.

You are reading the example.

This handbook is served by the system it describes. Here is what we verified in the deployed configuration and the responses from udx.io.

01 / Edge

CloudFront before WordPress.

The CDN handles public requests first. HTML and the ?json layout response are distinct variants, so publishing has to invalidate both.

02 / Application

Linux, WordPress, React.

WordPress runs in Linux containers on Kubernetes. The theme renders HTML, then its React front end loads the page layout as JSON.

03 / Object cache

Memcached behind the page.

A persistent WordPress object-cache drop-in reduces repeated database work. It is a different cache from CloudFront and has a separate invalidation lifecycle.

04 / Media

Files outside the app.

WP-Stateless stores media in Google Cloud Storage; Imgix delivers image variants. The WordPress container does not need to resize every image for every visitor.

Eight decisions to make deliberately.

Use these as starting decisions, then test them against the application and its freshness requirements. The deployed setup on udx.io is documented separately above.

Public HTML

Start around 60 seconds where freshness matters. Configure the CDN minimum, default and maximum TTL deliberately, then verify the headers and observed expiry.

Hashed assets

One year, immutable. The name changes when the content does, so the lifetime can be long.

Images

Honor the image service's cache policy. Use versioned URLs or explicit invalidation when an image changes at the same address.

Cache key

Include the attributes that actually change the response: host, path and meaningful variants. Keep forwarding consistent with the key.

Logged-in traffic

Bypasses the page cache entirely. The object cache still applies.

Purges

Batch invalidations and cover the HTML plus its JSON variant. Map edits, menus, options and releases to every affected URL.

Object cache

Share a coherent cache across application replicas where appropriate, with separate namespaces or services per environment. Invalidate deliberately; do not rely on a flush in just one pod.

Verification

The exact requests the application makes, from outside the network, not a human in a browser on the office connection.

A longer TTL can be appropriate when invalidation is dependable. Verify the deployed policy, not just the intended settings in a repository.

Asked at two in the morning.

Do I need a caching plugin if I have a CDN?

A CDN can cache anonymous HTML if configured for it; many CDNs cache only static assets by default. An origin page cache can still reduce work on misses. A persistent object cache helps repeated database reads, including requests that cannot use a shared page cache. Choose the layers from the traffic and freshness requirements, then verify which requests each actually serves.

Why do I still see old content after clearing the cache?

Check which response is stale before clearing everything. Compare the HTML, the ?json layout variant and response headers. A per-pod object cache may disagree with its siblings; a browser may still hold a response under its own lifetime. Purge the affected variants and verify from outside the application network.

What is a cache key and why does it matter?

The key is the set of request attributes the cache uses to decide whether two requests are the same. If the origin varies a response on something the key ignores, one visitor's answer is served to another. If the key includes something the origin ignores, such as a tracking parameter, the cache fragments into copies that never hit. The key must match exactly what the origin varies on, and nothing else.

Redis or Memcached for the WordPress object cache?

Either can work. Coherent invalidation, environment isolation and observability matter more than the brand. This site uses a Memcached-backed WordPress object-cache drop-in. For a replicated application, verify whether the cache is actually shared or local to each replica before assuming one flush reaches everything.

How long should WordPress pages be cached at the edge?

A short lifetime such as sixty seconds is a useful starting point for public HTML when freshness matters. Longer lifetimes require reliable invalidation for every dependent page. The deployed default page policy on udx.io currently uses a one-day TTL; our publishing workflow explicitly invalidates affected paths. That is the current implementation, not the sixty-second recommendation.

Clearing the cache and still seeing the old page?

Tell us what changed, what you purged and what visitors still see. Talk with an engineer about cache invalidation, release reliability and the work needed to fix them.