This is a case study about our own site. We’re publishing it because the most useful technical writing is the kind where someone shows their work — including the embarrassing parts.

In April 2026 we audited codingheads.com and found a problem we’d quietly tolerated for years: our Cloudflare cache hit rate was sitting at 13.2%. For a site that runs on a serious WordPress stack with LiteSpeed Cache, Cloudflare Pro features turned on, and a Redis object cache underneath, that number was indefensible. Here’s how we diagnosed it, the changes we made, and the trap we fell into on the first attempt.

The starting state

The audit baseline pulled from the Cloudflare GraphQL Analytics API showed:

  • Cache hit rate: 13.2% (averaged over 14 days)
  • Byte hit rate: ~55% (much higher because static assets were cached)
  • Origin requests: ~7,000/day
  • Average TTFB on uncached pages: ~480ms

The byte hit rate gave away the diagnosis. Static assets were being cached fine. HTML responses weren’t. Every page view was hitting the origin.

The diagnostic query

We pulled a per-status breakdown for the previous 12 hours:

none      40%   uncacheable methods (POST, OPTIONS)
dynamic   40%   origin sent cookies / no-cache
hit        9%   served from edge
miss       7%   cache eligible, will be hit next time
bypass     2%   our own bypass rule for /wp-admin
revalidated 1%

The “dynamic” 40% was the entire problem. Cloudflare was looking at the response, seeing WordPress cookies, and refusing to cache. Our existing cache rule said “cache everything on this host” — but the rule was getting overridden by cookie-based bypass at the edge.

Attempt 1: a smarter cache rule

First fix was a three-rule replacement of the old single rule:

  1. Bypass cache for /wp-admin, /wp-login, and preview query strings.
  2. Cache static assets aggressively (30 days edge, 30 days browser, override origin).
  3. Cache HTML for 4 hours edge with override origin.

We deployed, purged cache, and walked away expecting the hit rate to climb.

It got worse.

Why attempt 1 backfired

Two days later the hit rate had dropped from 13% to ~11%. Looking at the cache status breakdown again, the “dynamic” share was still ~40%. The new rules were technically correct but missed a subtler problem: set_cache_settings with override_origin overrides the TTL header from origin, but it does not override responses where the origin attaches a Set-Cookie header. Those still get marked dynamic.

WordPress sets cookies on a lot of responses you wouldn’t expect — including basic page loads via the wordpress_test_cookie mechanism. Until we addressed that at the cache-key level, the rules were performative.

Attempt 2: cookie-aware bypass + cache deception armor

The second iteration moved the cookie check from the response side to the request side:

The logic flip was the key. Anonymous users — the vast majority of traffic — get cached HTML because they don’t have any of the bypass cookies. Logged-in users get fresh content because their request cookies trigger the bypass rule before the response is even generated.

The origin side

Even with edge rules dialed in, the origin shouldn’t be sabotaging itself. We bumped LiteSpeed Cache’s Browser Cache TTL from the default 7,200 seconds (2 hours) to 31,557,600 (1 year). That doesn’t change Cloudflare’s behavior directly — but it ensures that when Cloudflare passes a static asset response back to a browser, the browser actually keeps it instead of re-requesting it on the next page view.

We also verified the LiteSpeed exclude lists were clean. No URI exclusions, no cookie exclusions, no bloated query-string allowlists. The drop-query-string list had fbclid gclid utm* — exactly what you want.

The other things we found along the way

Audits always surface things you weren’t looking for. While we were in the dashboard:

  • Minimum TLS Version was still set to 1.0. Bumped to 1.2.
  • HSTS was disabled. Enabled with 6-month max-age, includeSubDomains, preload.
  • Bot Fight Mode was off. Turned on, plus AI Bot Block.
  • Browser Integrity Check was off. Turned on.
  • Security Level was on “Low.” Bumped to Medium.
  • Two legacy 404 URLs in Search Console (/our-team/ and /curs) — fixed with Cloudflare dynamic redirect rules instead of waiting for the next deploy.

None of those were the original problem. All of them needed fixing anyway.

The expected outcome

We’re publishing this 48 hours after the fix. The cache is still warming. Current trajectory has us climbing past 50% hit rate within the week — we’ll update this post with the final number once it stabilizes. The byte hit rate is already up to 70%+ on the new rules.

The lessons

  • Audit your own stuff. We’d been telling clients to check their cache hit rates for years. We hadn’t checked ours in months. The cobbler’s children always have the worst shoes.
  • Cache rules with cache: true are not enough. You also have to control what triggers the bypass, and that’s almost always a cookie problem on WordPress.
  • Cookie-based bypass is the right pattern. Check request cookies, not response cookies. Treat anonymous users and logged-in users as fundamentally different traffic.
  • Always purge after a rule change. Otherwise you’re measuring stale state for hours.
  • Don’t fix one thing if you’re already in the dashboard. Bundle the small wins (TLS, HSTS, Bot Fight) — they cost nothing on top of an audit you’re already doing.

Thirteen percent to seventy percent isn’t magic. It’s a stack of small, boring decisions made in the right order. And yes — we were embarrassed by the starting number. Publishing it anyway is the point.