Guide
WordPress cache hit ratio degraded — why your site got slower without a code change
1. Problem
Yesterday the site felt fine. Today every page takes an extra second, the admin dashboard stutters, and PHP-FPM workers are visibly busier in htop. You ran git log on the theme and plugin folders — nothing in the last 48 hours. You restarted PHP-FPM, you flushed the object cache, you cleared the page cache. The site felt better for ten minutes and then went slow again. You search "wordpress cache hit ratio dropping" and "wordpress cache not working after plugin update" and find threads from 2017 telling you to install another caching plugin.
This is the signature of cache degradation: a wp_cache_hit_ratio that has silently fallen from 0.95 to 0.40 over a day, dragging wp_cache_misses_total, wp_request_peak_memory_mb, and eventually wp_slow_requests_total up with it. No PHP fatal. No 500 error. No deployment. Just a site that "feels slow" — and a leading indicator that's been screaming for hours before users noticed.
This guide walks through why that ratio drops without a code change, how to read the four-signal sequence that confirms it, and how to fix the three common causes: a plugin calling wp_cache_flush() on every request, a logged-in user explosion bypassing the page cache, and an object cache backend that has silently disconnected.
2. Impact
A cache hit ratio drop is not cosmetic. WordPress without a healthy object cache regenerates the same option autoloads, term queries, user metadata, and post meta on every request. A site that was serving 100 req/s on a t3.medium at 0.95 hit ratio will not serve 100 req/s at 0.40. It will drop requests, queue connections, and burn database CPU on queries that should never have left PHP.
The economic impact is direct. Page generation time is the dominant input to LCP (Largest Contentful Paint), and Core Web Vitals regressions translate to organic traffic loss within weeks. WooCommerce checkouts that rendered the cart in 400ms now take 1.4s, and cart abandonment rises non-linearly past one second. Editors hit "Save Draft" and watch the request hang because autosave is competing with frontend traffic for the same exhausted DB connection pool.
The deeper risk is the cascade. Cache misses raise per-request memory because more objects are materialized. Peak memory raises the chance of an OOM in PHP-FPM, which kills the worker, which leaves the connection pool half-occupied, which slows the next request. By the time you notice "the site is slow", you're three hours into a degradation that started as a 5% hit ratio drop nobody alerted on.
3. Why It’s Hard to Spot
The reason this failure mode is silent for hours is structural. WordPress has no native cache observability. There is no admin screen that says "your hit ratio is 0.4". wp-admin does not warn you when wp_cache_flush() is being called from init. The Site Health screen reports "your site is healthy" while every request is doing five times the database work it did yesterday.
Uptime monitors miss it completely. A status check returns 200 OK whether the page took 80ms or 1800ms. Synthetic monitoring tools that hit a small set of static URLs from a CDN edge miss it for days because those URLs are page-cached at the edge — the slowness only manifests on logged-in or dynamic requests.
Hosting dashboards report on the wrong layer. CloudWatch shows RDS CPU rising, but doesn't say the cause is a 60% drop in object cache hit ratio. New Relic shows a generic "MySQL" segment growing — without explaining that the queries themselves are unchanged, just running far more often than before.
And the most insidious property: the site still works. Pages render. Forms submit. Logins succeed. The site is just slower. Slowness is the easiest symptom for an organization to dismiss as "the internet today", which is why cache degradations regularly run for 24-72 hours before anyone takes them seriously.
4. Cause
The wp_cache_hit_ratio signal is the fraction of wp_cache_get() calls in a request that return a hit rather than a miss. WordPress records every cache lookup against the object cache backend (Memcached, Redis, or the in-process default). The ratio is computed per request and emitted with labels entity_id, entity_name, cache_group, and a request-level rollup. A healthy WordPress install with a persistent object cache sits between 0.85 and 0.98 on the request rollup, depending on traffic mix.
When wp_cache_hit_ratio drops, one of three mechanical things has happened.
First, the cache is being invalidated faster than it can be populated. wp_cache_flush() wipes the entire object cache. If a plugin calls it on init, every request starts cold. The signal collapses to near zero hit ratio for the first uncached lookups, and wp_cache_misses_total (a monotonic counter of cache misses across all requests) climbs in lockstep. You'll see the misses counter increment by hundreds per request instead of dozens.
Second, the request is bypassing the page cache because it carries a logged-in cookie or a WooCommerce session cookie. Page caches (Redis full-page, Varnish, hosting-level caches) skip authenticated traffic by design. If the share of authenticated requests jumps — for instance, a WooCommerce promotion sends thousands of users through login, or a membership plugin starts issuing session cookies to anonymous visitors — the object cache hit ratio still looks fine but the aggregate cache effectiveness collapses. wp_request_peak_memory_mb is the giveaway here: authenticated requests materialize the user's session, cart, capabilities, and meta, and peak memory per request rises by 20-40MB.
Third, the object cache backend has disconnected. Redis dropped the connection, Memcached is timing out, or the persistent cache plugin has fallen back to the in-process array cache (which lasts only for the duration of one request). Hit ratio plummets, miss counter explodes, and there is no error in wp-content/debug.log because the object cache "drop-in" plugins typically swallow connection errors silently to keep the site up.
wp_slow_requests_total is the trailing indicator. As cache misses pile up, requests cross your slow threshold (commonly 1000ms or 2000ms), and this counter climbs. By the time it does, the user-facing slowness is already reported.
5. Solution
5.1 Diagnose (logs first)
Start at the ratio, work outward to misses, then to memory and slow requests. The signals are designed to be read in that order.
Step 1: confirm the hit ratio drop is real and ongoing.
grep "wp_cache_hit_ratio" /var/log/wordpress/logystera.log \
| jq 'select(.value < 0.7) | {ts, entity, value, request_id}' \
| tail -50
Each hit is a wp_cache_hit_ratio emission. If the recent values are clustered between 0.3 and 0.6 and yesterday's were above 0.9, you have a real degradation, not a single anomalous request. Note the entity label — multi-site or multi-environment hosts often degrade only on one entity.
Step 2: confirm misses are climbing in absolute terms.
grep "wp_cache_misses_total" /var/log/wordpress/logystera.log \
| jq -r 'select(.entity == "site-prod") | "\(.ts) \(.value)"' \
| tail -100
This produces the wp_cache_misses_total counter samples. Take the delta between samples ten minutes apart. If yesterday's delta was 4,000 misses/10min and today's is 80,000 misses/10min, the cache is being skipped or invalidated — not just slightly less effective.
Step 3: identify whether the culprit is invalidation, bypass, or disconnect.
For invalidation, search the WordPress-side debug output for explicit flush calls:
grep -E "wp_cache_flush|Cache cleared|Object cache flushed" \
/var/log/wordpress/debug.log /var/log/php-fpm/www.error.log
A burst of these aligned with the ratio drop points at a plugin doing the flushing. The wp.state_change audit signal often correlates — a plugin update or activation in the last 48 hours that introduced the offending flush.
For bypass (logged-in traffic explosion), correlate with wp_request_peak_memory_mb:
grep "wp_request_peak_memory_mb" /var/log/wordpress/logystera.log \
| jq 'select(.value > 96) | {ts, request_id, user_id, url}' \
| tail -50
A surge of high-memory requests with non-null user_id confirms authenticated traffic is bypassing the page cache. This signal pairs with the ratio drop to distinguish "cache broke" from "cache is being intentionally skipped for valid reasons".
For backend disconnect, hit Redis or Memcached directly:
redis-cli -h cache.internal -p 6379 INFO clients
redis-cli -h cache.internal -p 6379 DBSIZE
If connected_clients is far below your PHP-FPM worker count, or DBSIZE is near zero on what should be a populated cache, the backend has dropped or the WordPress side has disconnected. Check the object cache drop-in:
ls -la /var/www/html/wp-content/object-cache.php
tail -200 /var/log/php-fpm/www.error.log | grep -i "redis\|memcached\|cache"
A missing or stale object-cache.php drop-in causes WordPress to silently fall back to the in-process array cache. In that mode, the hit ratio for a single request still looks reasonable but wp_cache_misses_total increases at 5-10x the normal rate because nothing persists across requests.
Step 4: confirm the slow request signal is climbing.
grep "wp_slow_requests_total" /var/log/wordpress/logystera.log \
| jq -r '"\(.ts) \(.value)"' | tail -50
If the wp_slow_requests_total counter delta has roughly tripled since yesterday and the timeline aligns with the hit ratio drop, you have the full chain: ratio collapsed, misses climbed, memory rose, slow requests followed. That is the diagnostic you take to the fix.
5.2 Root Causes
(see root causes inline in 5.3 Fix)
5.3 Fix
Three causes account for almost all real cases. Walk them in order of likelihood, since the wrong fix wastes a deploy cycle.
Cause 1: a plugin is calling wp_cache_flush() too often.
This is the most common cause when the regression follows a plugin install or update. Some SEO, sitemap, and "performance optimizer" plugins flush the entire cache on every admin page load, every cron tick, or — worst case — on every front-end request that hits a particular hook. You'll see this as a wp_cache_hit_ratio collapse correlated with a recent wp.state_change (plugin activation/update). Identify the offender with:
grep -rn "wp_cache_flush\|wp_cache_flush_group" /var/www/html/wp-content/plugins/ | grep -v "/vendor/"
Then audit the call sites for hooks like init, wp_loaded, template_redirect, or wp — these run on every request. Move the flush to a more selective trigger (save_post, switch_theme, plugin settings save) or remove it entirely. After the fix, wp_cache_misses_total deltas should drop within minutes; wp_cache_hit_ratio recovers as the cache repopulates.
Cause 2: logged-in user explosion bypassing page cache.
This is common after a membership-plugin promotion, a WooCommerce login wall, or a contact form plugin that issues a session cookie to anonymous visitors. The fix is rarely "make page cache serve logged-in users" — that's dangerous. Instead:
- Audit your session cookie issuance. If anonymous visitors are receiving
wordpress_logged_in_orwp_session_cookies they shouldn't have, find the plugin doing it and disable that behavior. - Ensure your page cache is correctly configured to serve logged-out traffic from cache and only bypass for genuinely authenticated requests. Cache configurations that bypass on the existence of any cookie (e.g. Google Analytics consent cookies) defeat themselves.
- Tune the object cache for the new logged-in baseline: bump Redis/Memcached memory so the higher-cardinality user sessions don't evict each other.
After the fix, wp_request_peak_memory_mb should fall back into its prior band, and the share of requests with non-null user_id should match the actual logged-in user fraction.
Cause 3: object cache backend disconnected or drop-in missing.
Reconnect or reinstall:
# Verify Redis/Memcached is reachable from the PHP-FPM host
redis-cli -h cache.internal -p 6379 PING
# Reinstall the drop-in if it's missing
wp redis enable --force # for Redis Object Cache plugin
# Or for W3 Total Cache / LiteSpeed: re-save the cache backend settings
Then confirm:
wp eval 'var_dump(wp_cache_get("test_key"));'
wp eval 'wp_cache_set("test_key", "ok"); var_dump(wp_cache_get("test_key"));'
If the second call returns string(2) "ok", persistent cache is functioning. If it returns bool(false), the drop-in is loaded but the backend isn't accepting writes — check Redis maxmemory-policy, network ACLs, and TLS settings.
5.4 Verify
The fix is verified by the four signals reverting, in this order:
wp_cache_hit_ratioreturns to its baseline band (typically 0.85-0.98) within 5-15 minutes as the cache repopulates under live traffic.- The
wp_cache_misses_totalrate of increase drops back to the prior baseline. Compare today's 10-minute delta against last week's same hour. wp_request_peak_memory_mbp95 drops by 20-40MB if the issue was logged-in bypass, or by 5-15MB if it was object cache disconnect.wp_slow_requests_totaldelta returns to baseline within 30-60 minutes once the cache is warm and the upstream causes are removed.
A practical verification grep:
grep "wp_cache_hit_ratio" /var/log/wordpress/logystera.log \
| jq -r '"\(.ts) \(.value)"' | tail -200 | awk '{ sum+=$2; n++ } END { print sum/n }'
If the rolling average over the last 200 samples is above 0.85, the primary signal is healthy. If it stays under 0.7 for an hour after the fix is deployed, the fix didn't address the actual cause — go back to the diagnosis steps and look for a second flush site, a second plugin issuing cookies, or a second cache layer that's still disconnected.
6. How to Catch This Early
Fixing it is straightforward once you know the cause. The hard part is knowing it happened at all.
This issue surfaces as wp_cache_hit_ratio.
The hard part of this failure isn't fixing it. The hard part is knowing it happened before users complain. WordPress does not alert on a falling cache hit ratio. Hosting dashboards do not alert on it. Uptime monitors return 200 OK while the site is twice as slow as yesterday. The degradation runs for hours, then surfaces as a vague "the site feels slow today" message in Slack — at which point the team starts looking at the wrong layer.
This is the leading-indicator profile Logystera is built for. wp_cache_hit_ratio falling below a threshold is detectable from the first ten minutes of degradation, well before wp_slow_requests_total climbs and well before users notice. Logystera matches the four signals — wp_cache_hit_ratio, wp_cache_misses_total, wp_request_peak_memory_mb, wp_slow_requests_total — into one coherent picture per entity, and alerts on the leading one. By the time the trailing slow-request counter would trigger an alert in a generic stack, the ratio rule has already fired and pointed at the entity, the time window, and the cause class (invalidation, bypass, or disconnect).
The point isn't that you couldn't reconstruct this manually from logs. You can. The point is that nobody does it on a Tuesday afternoon when nothing appears wrong — and that is exactly when this failure starts.
7. Related Silent Failures
- WordPress slow queries — finding the plugin responsible. When cache misses, the DB absorbs the load;
db.slow_queryandperf.hook_timingattribute the slowness to a callback. - WordPress memory limit — detecting before crash. Cache degradation drives
wp_request_peak_memory_mbup; the next failure mode is aphp.fatalfor "Allowed memory size exhausted". - WordPress plugin auto-update broke site. The most common trigger for a sudden hit ratio collapse is a plugin update that introduces an aggressive
wp_cache_flush()call. - WordPress database deadlocks. Cascade endpoint when uncached writes pile up;
db.deadlockfollows a sustained miss-rate climb. - WordPress scheduled posts not publishing. Degraded cache slows WP-Cron past
max_execution_time, manifesting aswp.cron type=missed_schedule.
See what's actually happening in your WordPress system
Connect your site. Logystera starts monitoring within minutes.