GAP School Module 10 — Production Hardening Lesson 10.3

A deploy that doesn’t flush all relevant cache layers will leave users seeing stale content from one layer while fresh content arrives from another. The result looks like intermittent bugs that resolve themselves — which is the worst kind of bug, because it suggests a flaky codebase when the actual cause is a cache layer nobody flushed.


The situation

After a CSS update to the inventory grid, some mobile visitors saw the old grid layout for 45 minutes while desktop visitors saw the new one. WP object cache and CDN were flushed. Browser cache on mobile was not — mobile users had cached the CSS file from the previous version. The fix (bumping the CSS query string) was already in the process for subsequent deploys, but this one slipped through.


What I did

The 4 cache layers

Reference
Layer 1: PHP OPCache Owns: compiled PHP bytecode Flush trigger: any .php file change How: opcache_reset() via one-shot PHP script, then delete script Layer 2: WP Object Cache (in-memory / Redis / Memcached) Owns: WP query results, post data, option values Flush trigger: database data changes, option updates How: wp cache flush (WP-CLI) Layer 3: Full-Page Cache (Breeze, WP Rocket, Nginx FastCGI) Owns: cached HTML output of pages Flush trigger: content changes, template updates How: plugin-specific API or wp cache flush with plugin active Layer 4: Edge / CDN cache (Cloudflare, nginx proxy) Owns: static assets, sometimes HTML Flush trigger: asset version bumps, explicit API purge How: Cloudflare API purge or Cache-Control: no-cache headers

OPCache flush — the most-missed layer

OPCache is the most impactful and most forgotten cache layer. Changed PHP files run old bytecode until it’s flushed. The pattern: deploy a one-shot flush script, curl it, delete it:

PHP — opcache-flush.php (one-shot, delete after use)
<?php if ( opcache_reset() ) { echo json_encode( [ 'status' => 'flushed', 'time' => time() ] ); } else { http_response_code( 500 ); echo json_encode( [ 'status' => 'failed' ] ); }
Shell — in deploy script
rsync opcache-flush.php prod:/path/to/webroot/ curl -s https://[site]/opcache-flush.php ssh prod "rm /path/to/webroot/opcache-flush.php"

Browser cache: version strings

Browser cache can’t be manually purged for existing users. The only lever is making the asset URL different — every CSS and JS file that changes must have its query string bumped:

PHP — wp_enqueue_scripts
wp_enqueue_style( '[client]-main', get_template_directory_uri() . '/css/style.css', [], '20260512b' // Increment on every CSS change: YYYYMMDD + letter ); wp_enqueue_script( '[client]-site', get_template_directory_uri() . '/js/site.js', [ 'jquery' ], '20260429g', // Increment on every JS change true );

The full post-deploy flush sequence

Shell — complete flush sequence
# 1. OPCache (PHP bytecode) rsync opcache-flush.php prod:/path/ curl -s https://[site]/opcache-flush.php ssh prod "rm /path/to/opcache-flush.php" # 2. WP object cache ssh prod "cd /path/to/wp && wp cache flush" # 3. Full-page cache (Breeze example) ssh prod "cd /path/to/wp && wp breeze clear-all-cache" # 4. Cloudflare edge cache (full zone on major deploys) curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache" \ -H "Authorization: Bearer CF_TOKEN" \ -H "Content-Type: application/json" \ --data '{"purge_everything":true}' # 5. Verify — check a versioned URL loads the new asset curl -sI "https://[site]/css/style.css?v=20260512b" | grep "Last-Modified"

Why it matters

The 4 caches are independent. WP-CLI wp cache flush touches layers 2 and 3 (depending on plugin). It does nothing to OPCache or browser cache. A deploy process that runs wp cache flush and calls it done is leaving 2 of 4 layers unflushed.

Version strings on CSS/JS are the only cache control you have over browser caches for existing users. Ship a visual change without bumping the version string and some percentage of users will see the old UI for hours to days — and they won’t know to hard-refresh.


The Anchor build

Full 4-layer flush sequence scripted into every deploy. 0 stale-cache incidents after the sequence was formalized. Prior: 3 incidents in the first 3 months (all OPCache-related). The Cloudflare purge is scoped to individual assets on minor deploys and full-zone on major deploys — full-zone adds ~2 minutes of edge warm-up traffic but prevents serving stale HTML to the first post-deploy visitors.


Do this, not that

  • Flush OPCache after every PHP file deploy. wp cache flush does not touch OPCache. Treat them as completely separate systems because they are.
  • Bump CSS/JS version strings on every change, not just major ones. “Minor” CSS fixes are exactly the ones that stay cached longest because nobody hard-refreshes for a minor visual tweak.
  • Script the flush sequence, don’t run it manually. A manual sequence is one where you skip steps when you’re in a hurry. The deploy script runs the same steps every time.
  • Verify after flush, not before. curl -sI on a versioned asset URL confirms the CDN is serving the new version. “I think it’s flushed” is not a close-out.
When you’re ready to build

The lessons are yours. When you want it built, we’re here.

Every lesson stays free — no account, no paywall, no email gate, ever. But if you’d rather have this system standing on your business than wire all 48 lessons yourself, leave your email. We’ll send you a direct line to a build — and you’ll be first to hear when we add new tools to the curriculum.

None of this gates a single lesson. The curriculum was free before you got here and it stays that way.

We’ll use your email to send you a fast-track to a GAP build and occasional notes on how GAP builds digital sales departments. Lessons stay 100% free — no email required to read any of them. We never share or sell your information. Unsubscribe any time. Privacy policy at gapindustriesllc.com/privacy.html.

Done learning how it’s built? We’ll build it.

You came here to understand the system, and now you do. If you’d rather have it standing on your business than spend the next three months wiring it yourself, GAP Concierge is the same architecture from these lessons — a white-label AI agent that knows your catalog and captures your leads — set up for you, from $97/mo.

See GAP Concierge →