How to Handle WordPress Site Health Tests That Pass But Mask Real Problems

WordPress Site Health is a diagnostic surface, not a guarantee. A green checkmark means a test ran and matched a threshold. It does not mean the underlying subsystem is healthy under production load, correctly configured for your hosting topology, or safe from silent failure. For small-to-mid publishing teams that maintain their own installs, the dangerous failures are the ones Site Health never flags: object cache backends that report connected but evict constantly, database tables that pass integrity checks while accumulating dead rows, cron jobs that fire but never complete, and block editor requests that return 200 with empty content. This article covers the specific tests that pass while masking real problems, how to reproduce each failure mode, and what to monitor instead.

Site Health sits in the WordPress admin under Tools → Site Health. It runs a set of server, database, and WordPress configuration checks. The checks are useful for baseline triage, but they are static. They do not simulate traffic, measure latency under concurrency, inspect query plans, or validate that scheduled events actually produce output. If you run a production install with real editorial deadlines, you need a second layer of checks that target the gaps.

Why a Passing Site Health Test Can Be a False Positive

Site Health tests are designed to be safe for shared hosting and low-privilege environments. That constraint limits what they can inspect. A test may check that a PHP extension is loaded, not that it is configured correctly. It may check that a database table exists, not that its indexes are being used. It may check that a cron event is scheduled, not that it ran successfully. The result is a set of green indicators that can coexist with real production failures.

For example, the object cache test reports whether a persistent object cache is in use. It does not report the cache hit rate, eviction rate, or whether the cache backend is actually faster than a database query. A misconfigured Redis instance with maxmemory set too low will pass the test while evicting nearly every key. The site will work, but every page load will fall back to the database, and the cache will add latency instead of removing it.

Similarly, the database test checks that tables are present and accessible. It does not check for index fragmentation, dead rows, or missing indexes on high-traffic queries. A table with millions of rows and no index on a frequently filtered column will pass the test while every query that touches it runs a full table scan.

Object Cache: Connected Is Not the Same as Effective

The Site Health object cache test checks whether a persistent object cache drop-in is active. It does not measure whether the cache is doing its job. To see the real state of your object cache, you need to inspect the backend directly.

Reproduce the Failure: Redis Evictions

If you use Redis, connect to the instance and run:

redis-cli INFO stats | grep -E 'evicted_keys|keyspace_hits|keyspace_misses'

If evicted_keys is climbing while keyspace_misses is high relative to keyspace_hits, your cache is thrashing. The fix is usually to raise maxmemory, switch to a more memory-efficient serialization, or reduce the number of large transient keys. Site Health will still show green because the drop-in is present.

For Memcached, the equivalent check is:

echo "stats" | nc 127.0.0.1 11211 | grep -E 'evictions|get_hits|get_misses'

A high eviction count with a low hit ratio means the cache is not reducing database load. You are paying for the cache in memory and latency, but getting none of the benefit.

What to Monitor Instead

Track cache hit rate, eviction rate, and average latency for cache operations. Set alerts when the hit rate drops below 80% for a sustained period or when evictions exceed a threshold per minute. These metrics are not visible in Site Health, but they are the ones that predict slow page loads and database saturation.

Database Tables: Present Is Not the Same as Healthy

The Site Health database test checks that all core tables exist and are accessible. It does not check for dead rows, index bloat, or missing indexes. In a busy publishing install, the wp_posts and wp_postmeta tables are the usual suspects.

Reproduce the Failure: Dead Rows in InnoDB

If you use MySQL or MariaDB with InnoDB, dead rows accumulate after deletes and updates. They are not removed until the table is optimized. To see the current state:

SELECT table_name, data_free FROM information_schema.tables WHERE table_schema = 'your_db_name' ORDER BY data_free DESC LIMIT 10;

If data_free is large relative to the table size, the table has significant dead space. This slows full table scans and increases disk usage. Site Health will not flag it because the table is present and queryable.

To reclaim the space, run OPTIMIZE TABLE wp_postmeta; during a low-traffic window. Be aware that OPTIMIZE TABLE locks the table on some MySQL versions. For large tables, consider pt-online-schema-change from Percona Toolkit if you need to avoid downtime.

Reproduce the Failure: Missing Index on a High-Traffic Query

Use the slow query log to find queries that run frequently and take a long time. Enable it in your MySQL configuration:

slow_query_log = 1
long_query_time = 1
log_queries_not_using_indexes = 1

Then inspect the log for queries against wp_postmeta that filter on meta_key and meta_value without an index. A common offender is a query that looks up posts by a custom field. The default wp_postmeta table has an index on meta_key, but not on meta_value. If your theme or a plugin filters by meta_value, every query scans the entire table.

Add a targeted index only if the query pattern is stable and the table is large enough to justify it. For example:

ALTER TABLE wp_postmeta ADD INDEX meta_key_value (meta_key(191), meta_value(191));

Test the query plan before and after with EXPLAIN. An index that is never used is just write overhead.

Cron: Scheduled Is Not the Same as Executed

WordPress cron is a cooperative scheduler. It runs when someone visits the site. If your site has low traffic or a page cache that serves visitors without loading WordPress, cron events can sit in the queue for hours or days. Site Health checks that the cron system is not disabled, but it does not check that events are actually running on time.

Reproduce the Failure: Stuck Cron Events

List the current cron queue with WP-CLI:

wp cron event list --fields=hook,next_run_relative,status

If you see events with a next_run_relative of now or a past timestamp that never clear, cron is not keeping up. The usual causes are a page cache that bypasses WordPress, a missing system cron job, or a long-running event that blocks the queue.

The fix is to disable WordPress pseudo-cron and run it from the system scheduler:

define('DISABLE_WP_CRON', true);

Then add a system cron entry:

*/5 * * * * wp cron event run --due-now --path=/var/www/your-site --quiet

This guarantees that due events run every five minutes, regardless of traffic. Site Health will still show green either way, because the cron system is technically enabled.

What to Monitor Instead

Track the age of the oldest due cron event. If it exceeds 10 minutes, something is wrong. Also track the number of failed cron runs. A scheduled post that goes out late is a visible editorial failure, and it is almost never caught by Site Health.

Block Editor: HTTP 200 Is Not the Same as Usable

The block editor relies on a series of REST API requests. If a plugin or theme filter breaks the response shape, the editor can load with a blank canvas or missing blocks while the underlying HTTP requests return 200. Site Health does not test the block editor at all.

Reproduce the Failure: Empty Block List

Open the block editor and watch the network tab. Look for requests to /wp-json/wp/v2/block-types and /wp-json/wp/v2/block-patterns/patterns. If the response is 200 but the body is empty or missing expected fields, the editor will appear to load but will not show any blocks.

A common cause is a filter that modifies the REST response for block types. For example, a plugin that removes block types for certain user roles may accidentally remove all of them. To test from the command line:

wp eval 'print_r( WP_Block_Type_Registry::get_instance()->get_all_registered() );'

If the registry is empty, the editor has nothing to render. The fix is to find the filter that is emptying the registry and remove or correct it.

What to Monitor Instead

Add a smoke test that loads the block editor as an editor user and checks that the block list is non-empty. You can do this with a headless browser or a simple script that hits the REST endpoint and validates the JSON shape. This is the kind of check that catches failures before an editor sits down to write.

Site Health Score: A Number Without Context

The Site Health score is a weighted sum of individual test results. It is useful for spotting obvious misconfigurations, but it is not a performance metric. A site can score 100% while serving pages in three seconds and dropping scheduled posts. The score measures compliance with a checklist, not the health of the system under real use.

If you maintain a production install, treat Site Health as a starting point. Run it after major changes, but do not rely on it for ongoing monitoring. The checks that matter are the ones you write yourself, because they target the specific failure modes of your stack.

Building a Second Layer of Checks

The most useful checks are small, specific, and tied to a known failure mode. Here are three that cover the gaps described above.

Check 1: Object Cache Hit Rate

Run this every five minutes and alert if the hit rate drops below 80% for more than 15 minutes:

redis-cli INFO stats | awk -F: '/keyspace_hits/{hits=$2} /keyspace_misses/{misses=$2} END {if (hits+misses > 0) print hits/(hits+misses)*100}'

This is a single metric that tells you whether your cache is actually reducing database load.

Check 2: Oldest Due Cron Event

Run this every five minutes and alert if the oldest due event is older than 10 minutes:

wp cron event list --fields=next_run_relative --format=csv | tail -n +2 | sort | head -n 1

This catches the scheduled-post failure mode before an editor notices that their article did not go live.

Check 3: Block Editor Smoke Test

Run this every hour and alert if the block list is empty:

curl -s -H "Authorization: Bearer YOUR_APP_PASSWORD" https://your-site.com/wp-json/wp/v2/block-types | jq 'length'

If the length is zero, the editor is broken. This is a five-minute check that prevents a full day of editorial downtime.

When Site Health Is Actually Useful

Site Health is good at catching configuration errors that are easy to verify: missing PHP extensions, outdated WordPress core, insecure file permissions, and missing SSL certificates. These are real problems, and the tests for them are reliable. The issue is not that Site Health is useless; it is that it is incomplete. Use it for what it is good at, and build your own checks for everything else.

For a small publishing team, the highest-value checks are the ones that protect the editorial workflow. A scheduled post that goes out late is a visible failure. A block editor that loads blank is a visible failure. A slow page load is a visible failure. Site Health will not catch any of them, but a handful of targeted checks will.

FAQ

Why does my Site Health show green when my site is slow?

Site Health does not measure page load time, database query latency, or cache effectiveness. It checks that required components are present and configured to a minimum standard. A slow site can pass every test if the slowness comes from missing indexes, cache thrashing, or slow external requests.

How do I know if my object cache is actually working?

Check the cache backend directly. For Redis, run redis-cli INFO stats and look at keyspace_hits, keyspace_misses, and evicted_keys. A healthy cache has a high hit ratio and a low eviction rate. If evictions are high, the cache is not reducing database load.

Can I rely on WordPress cron for scheduled posts?

Only if your site has consistent traffic that loads WordPress on every visit. If you use a full-page cache or have low traffic, cron events can be delayed indefinitely. Disable WordPress pseudo-cron and run it from the system scheduler to guarantee that due events run on time.

What is the most common block editor failure that Site Health misses?

The most common failure is an empty block list caused by a filter that removes all registered block types. The editor loads, but no blocks are available. Site Health does not test the block editor, so the failure goes unnoticed until an editor tries to write.

Next Steps for Your Production Install

Start with the three checks above. They cover the failure modes that most often affect publishing teams: cache thrashing, stuck cron, and a broken block editor. Once those are in place, add checks for slow queries and table bloat. The goal is not to replace Site Health, but to fill the gaps it leaves open.

If you are dealing with a site that returns Nothing Found on new posts, the problem is usually a rewrite or query issue, not a Site Health failure. See What to Fix First When a New WordPress Site Says Nothing Found for a step-by-step diagnosis.

For ongoing monitoring, keep a log of every check that fires. The pattern of failures over time tells you more than any single alert. A cache that thrashes once a week is a different problem than one that thrashes every afternoon. The log is your evidence base for deciding what to fix next.

Server rack with network cables in a data center
Close-up of a database server hard drive activity light
Person typing on a laptop while reviewing code on a monitor