Glowing desk phone in a dimly lit office beside an open laptop late at night, illustrating how a properly configured CDN cache absorbs after-hours calls to a clinic website.

CDN, cache, and the calls you don’t want to take at 9 PM

The worst calls I have ever taken about a website happened at 9 PM on a weeknight. Not because the site was down. Because the staff had just gotten home, opened the site to check something, and found that the change they made at 4 PM was not showing up. Three hours later. From a different browser. After clearing their cache. After restarting their phone. This is what a CDN cache problem looks like from the outside.

This is almost always a caching problem. The site is fine. The change is saved. It just isn’t being served. And the reason it isn’t being served is that somewhere in the stack between the database and the browser, one of three or four layers of cache has a stale copy and won’t let go of it.

The CDN cache and the layers behind it, in order

On a typical WordPress clinic site, the request flow looks like this. The browser asks for a page. The CDN, if there is one, checks its cache first. If it has a copy, it serves that. If not, it asks the origin server. The origin server’s reverse proxy (usually Nginx) checks its own page cache. If hit, it serves the static file. If miss, it forwards the request to PHP. PHP starts WordPress, which checks its object cache (Redis, usually). If the data is there, it skips the database. If not, it queries MySQL.

So a page request can be answered at five different layers: browser cache, CDN, Nginx FastCGI cache, Redis object cache, or the database. The further out the answer comes from, the faster the response. And the more places a stale copy can hide.

Where stale content actually comes from

The 9 PM call almost always traces back to one of three culprits.

The CDN. If you are using Cloudflare or similar, your HTML may be cached for hours, even when WordPress thinks the page changed. The default settings usually exclude HTML from caching, but “Cache Everything” page rules and similar shortcuts override that. If a previous developer enabled aggressive caching to chase a Lighthouse score, the staff change at 4 PM may not appear publicly until the TTL expires.

Nginx FastCGI cache. On a tuned VPS or managed host, Nginx caches the full rendered HTML output of WordPress pages. The Nginx Helper plugin is supposed to purge those files when a post is updated. Sometimes it does. Sometimes the purge rule misses a custom post type. Sometimes the path map is wrong because the site moved hosts and nobody updated the configuration. If the cache directory has files older than your last edit, that is where to look.

Browser cache. The boring answer, but real. If the staff member is on their personal phone, looking at the site they bookmarked a year ago, the service worker from an old version of the theme may still be serving them a cached shell. The fix is to hard-reload, but you can’t troubleshoot that over the phone with a stressed front desk at 9 PM.

What CDNs do well, and what they don’t

A CDN’s job is to serve your static assets, the CSS, JavaScript, images, and fonts, from a server geographically close to the visitor. That part is uncontroversial and worth doing for any site that gets traffic from outside its host’s region.

Where it gets complicated is when the CDN also caches HTML. The performance gain is real. Time to first byte drops from 300ms to 30ms. But HTML changes more often than CSS, and the staff’s mental model of “I saved the page, it should be live” doesn’t account for an edge cache they can’t see.

For a small clinic site, my default is: cache static assets aggressively at the CDN, cache HTML at the origin only, and never enable “Cache Everything” on Cloudflare unless you have automated purge wired up correctly. The performance difference is small. The peace of mind is large.

Purge rules that actually work

The Nginx Helper plugin (the one I use on most sites I manage) handles purging from the WordPress side. When a post is updated, it tells Nginx to invalidate the cached file for that URL. The default rule purges the home page and the post’s own permalink. That is correct for 80% of sites.

The 20% where it fails: when the change is to a sidebar widget that shows on every page, or a footer that needs to update site-wide, or a custom post type with a custom permalink structure. In those cases, you need to expand the purge rule or accept that some pages will lag until the cache naturally expires.

For a small practice, I usually set Nginx FastCGI cache TTL to two hours. Long enough to absorb the bulk of repeat traffic. Short enough that a missed purge corrects itself by the time anyone notices. A manual purge button in the admin bar (which Nginx Helper provides) is the escape hatch.

What this looks like configured well

A boring stack, by design. Cloudflare in front, set to standard caching for static assets and bypass for HTML. Nginx on the origin with FastCGI cache enabled, two-hour TTL, purge on post update. Redis object cache enabled for WordPress, which speeds up admin pages and reduces database load. No browser-side service worker unless there is a specific reason for one.

This is the stack I run on most clinic sites I manage. It is the stack underneath the page speed advice I wrote about earlier. The whole thing fits on a single VPS with a small Cloudflare account in front, and it survives a Monday morning rush without breaking a sweat.

If you are getting 9 PM calls about stale content, the answer is almost never “we need a faster site.” It is “we need a cache stack the staff can predict.” If you want help getting there, the Plans page is where I structure that kind of work.

Similar Posts