Guide
WordPress plugin auto-updates that broke your site — how to detect them in time
1. Problem
You open your laptop at 8:47 AM and the support inbox is full. "Site is showing a white page." "Checkout is broken." "I get a 500 error on every page." You hit the homepage. White screen. You open wp-admin. White screen. You did not deploy anything. Nobody touched the site. And yet the WordPress plugin auto update broke site sometime overnight while everyone was asleep.
This is the most common version of "wordpress site down after plugin update overnight": auto-updates ran at 03:14 AM, one of them shipped an incompatibility, PHP started throwing fatal errors on the first request after the update, and by sunrise every page that touches the failing plugin returns 500. There is no email from WordPress. The hosting dashboard is green. The uptime monitor says the site responded 200 — once, hours ago, before the update — and is too slow to escalate.
If you are trying to wordpress detect bad plugin update right now, the question is simple: which plugin updated, when, and what fired in the logs immediately after. Everything else is noise.
2. Impact
Auto-updates are the silent killer of small WordPress sites. Since WordPress 5.5, plugin auto-updates are opt-in per plugin, and most site owners enable them once and forget. The intent is good: security patches arrive without you babysitting wp-admin. The cost is that an unattended update at 3 AM has the exact same blast radius as a manual update at 3 PM — except nobody is watching.
Concrete consequences:
- Revenue loss. A WooCommerce checkout that throws a fatal error during cart calculation does not collect orders. Eight hours of downtime overnight on a store doing €2k/day is roughly €600 gone, plus the customers who never come back.
- SEO damage. Googlebot crawls overnight. A site that returns 500 to the bot for hours gets re-evaluated. Rankings can drop within a single crawl cycle.
- Trust damage. Members see the broken site before you do. "I tried to log in last night and the whole thing was down" is the kind of message that costs renewals.
- Security risk. Many sites respond to "plugin updates broke us" by disabling auto-updates entirely, which then leaves CVEs unpatched for weeks. The cure becomes worse than the disease.
The real failure is not that an update broke the site. Updates break sites. The real failure is that the gap between "update applied" and "you found out" was eight hours instead of eight minutes.
3. Why It’s Hard to Spot
WordPress does not tell you the auto-update broke things. It tells you the update succeeded.
The auto_plugin_theme_update_email hook sends a "Some plugins were automatically updated" email — but only on success, and only to the admin email. There is no follow-up email saying "by the way, your site has been throwing 500s for six hours since that update." The default WP_DEBUG is off in production, so the fatal errors do not even render to the screen — they go straight to PHP's error log if one is configured, or /dev/null if not.
Uptime monitors catch the 5xx storm only if (a) they're hitting a path that triggers the broken code, and (b) they're configured tightly enough to escalate at 3 AM. A monitor that pings / every 5 minutes with a 3-failure threshold takes 15 minutes to alert — and only if / is broken. If only /checkout is broken, the monitor is happily green while the store bleeds.
The hosting control panel shows "site is up." It is. PHP-FPM is running. nginx is responding. The fact that every response is a 500 with Fatal error: in the body is invisible to infrastructure-level health checks, which were designed for "is the process alive" not "is the application correct."
And the dashboard? The first admin who logs in at 9 AM sees a white page on wp-admin itself if the failing plugin is loaded on admin pages. They cannot even open the plugins screen to deactivate it without SSH or SFTP.
This is the textbook silent failure: every signal that would explain it exists, none of them are being watched, and the one that would arrive — the admin email — does not contain the bad news.
4. Cause
Three signals tell the whole story, and they fire in a specific order.
wp.state_change is the primary signal. WordPress emits this whenever something mutates the site's installed state: a plugin is activated, deactivated, installed, deleted, or updated. The auto-update process internally calls Plugin_Upgrader::upgrade() and on success fires upgrader_process_complete with $hook_extra['type'] = 'plugin' and $hook_extra['action'] = 'update'. The Logystera WordPress plugin captures this hook and emits wp.state_change with payload fields including plugin_slug, from_version, to_version, auto_update: true, and the timestamp. This is the "what changed" record.
php.fatal is what fires next, usually within seconds of the first frontend request after the update completes. If the new plugin version calls a function that no longer exists, references a removed class, or hits a type mismatch under PHP 8.x strict mode, PHP terminates the request. The Logystera plugin captures these via register_shutdown_function looking at error_get_last() for E_ERROR, E_PARSE, E_CORE_ERROR, and E_COMPILE_ERROR, then ships them as php.fatal with the file, line, message, and stack context.
http.request 5xx is the user-visible consequence. Every request that hits the failing code path returns 500 (or sometimes 502 if PHP-FPM crashes hard enough). Logystera correlates these with the php.fatal entries by request ID and timestamp.
perf.hook_timing is the supporting signal that catches slower-but-still-fatal versions: a plugin that doesn't crash but pushes wp_loaded from 200ms to 12 seconds because the new version added a synchronous external API call on every page load. The site stays "up" but is functionally dead.
The chain is therefore: wp.state_change(action=update, auto_update=true) → php.fatal (or sustained perf.hook_timing regression) → http.request 5xx burst. When all three appear within a few minutes of each other, the diagnosis is mechanical.
5. Solution
5.1 Diagnose (logs first)
Skip the dashboard. Go to logs in this exact order.
Step 1 — Confirm what changed and when. WordPress writes update events to its internal options and (if configured) to PHP's error log. The fastest source of truth is the database itself or the audit log if one is installed.
wp option get auto_update_plugins --format=json
wp plugin list --status=active --fields=name,version,update --format=table
If you have the Logystera plugin or any audit logger installed, find the wp.state_change events:
grep -E "wp\.state_change.*action=update" /var/log/wp-audit.log | tail -20
Each match is a wp.state_change signal: plugin slug, version transition, timestamp, and auto_update: true. Note the timestamp of the most recent one — that is your incident anchor.
Step 2 — Find the fatals that fired after the update. The PHP error log is where php.fatal lives. On most managed hosts it is at one of:
tail -n 500 /var/log/php-fpm/error.log
tail -n 500 /var/log/php_errors.log
tail -n 500 /home/<site>/logs/php.error.log
Filter for fatals that occurred after the update timestamp:
grep -E "PHP Fatal error" /var/log/php-fpm/error.log | awk -v t="$(date -d '03:00' +%s)" '...'
A realistic match looks like:
[03-Apr-2026 03:14:42 UTC] PHP Fatal error: Uncaught Error:
Call to undefined method WP_Block_Type_Registry::get_all_registered()
in /var/www/html/wp-content/plugins/super-cache/inc/loader.php:88
Stack trace:
#0 /var/www/html/wp-includes/class-wp-hook.php(310): Super_Cache_Loader->boot()
That single line tells you (a) which plugin (super-cache), (b) which file/line, (c) the failure mode (calling a method that no longer exists). This is the php.fatal signal in raw form. Cross-reference the plugin slug with the wp.state_change from Step 1 — if they match, you have your culprit.
Step 3 — Confirm user-visible impact. Check the access log for the 5xx burst that follows the fatal:
grep ' 500 ' /var/log/nginx/access.log | awk '{print $4}' | sort | uniq -c | tail -20
awk '$9 ~ /^5/ {print $4, $7, $9}' /var/log/nginx/access.log | tail -50
Each 500 here is an http.request 5xx signal. The cluster pattern you want to see (or rule out) is: many 500s starting at the wp.state_change timestamp, on URLs that load the suspect plugin's code.
Step 4 — If there are no fatals but the site feels slow, check hook timing. A plugin that updated and now does a 10-second synchronous HTTP call inside init will not throw php.fatal — it will just make every page take 12 seconds and eventually time out as 504. Look at perf.hook_timing data or, lacking that, slow log:
grep -E "slow log" /var/log/php-fpm/slow.log | head -50
Anti-generic rule check: every command above ties to a named signal. wp.state_change for what changed, php.fatal for what broke, http.request 5xx for who saw it, perf.hook_timing for the slow-not-crashed variant.
5.2 Root Causes
(see root causes inline in 5.3 Fix)
5.3 Fix
The root causes for "auto-update broke the site" cluster into four patterns. Address them in this order.
1. Plugin incompatibility with current PHP version. Most common. The plugin shipped a release that uses syntax or functions removed in PHP 8.1+, but your host is on 8.2.
- Signal fingerprint:
wp.state_changefor plugin X at T →php.fatalfor the same plugin path at T+seconds, message contains "Call to undefined", "Cannot use", or "Uncaught TypeError". - Fix: Roll back the plugin to the previous version. Via WP-CLI:
wp plugin install super-cache --version=1.11.0 --force
wp plugin activate super-cache
Then disable auto-updates for that specific plugin until the maintainer ships a fix:
wp plugin auto-updates disable super-cache
2. Plugin conflict with another plugin. The update changed a hook priority or a class name and now collides with another plugin.
- Signal fingerprint:
wp.state_changeat T,php.fatalmentioning a different plugin's path in the stack trace. The fatal happens inside Plugin B because Plugin A changed something Plugin B depended on. - Fix: Roll back A as above. If both plugins are needed at current versions, contact maintainers — this is an integration bug, not your config.
3. Memory exhaustion. The new plugin version loads more in init and now exceeds WP_MEMORY_LIMIT.
- Signal fingerprint:
wp.state_changeat T →php.fatalwith "Allowed memory size of N bytes exhausted". Often correlates with amemory_near_limitwarning before the fatal. - Fix: Bump
memory_limitinphp.iniorwp-config.php(define('WP_MEMORY_LIMIT', '512M');). If raising it past 512M is required, the plugin has a leak — roll back instead.
4. Database schema drift. The plugin added a column or table on activation but the auto-update path didn't run the migration cleanly.
- Signal fingerprint:
wp.state_changeat T →php.fatalwith "Unknown column" or "Table doesn't exist". - Fix: Manually trigger the plugin's upgrade routine, usually by deactivating and reactivating:
wp plugin deactivate. If that fails, restore the plugin's schema from a backup taken before T.&& wp plugin activate
Whichever cause you land on, the immediate stabilization is always the same: deactivate the plugin via WP-CLI (you cannot reach wp-admin), confirm the site responds 200, then decide on rollback vs. fix-forward.
wp plugin deactivate <slug> --skip-plugins
curl -sI https://yoursite.com/ | head -1
5.4 Verify
The fix is verified when the signals you used to find it stop appearing. Concretely:
php.fatalshould go silent. Tail the PHP error log for 30 minutes under normal traffic. Zero new entries matching the failing plugin's path means the immediate fault is gone.
tail -F /var/log/php-fpm/error.log | grep --line-buffered "PHP Fatal error"
If a fatal reappears from a different plugin, that's a separate incident — do not declare victory yet.
http.request5xx rate should drop to baseline. Check the access log for the 15 minutes after the fix:
awk -v t="$(date -d '15 minutes ago' '+%d/%b/%Y:%H:%M')" '$4 > "["t && $9 ~ /^5/' /var/log/nginx/access.log | wc -l
A healthy small WordPress site sits below ~0.5% 5xx rate. Anything above 1% sustained means you are not done.
wp.state_changeshould show the rollback. A successful rollback emits its ownwp.state_changeevent with the old version number. That is your audit trail — confirm it landed.
perf.hook_timingshould return to baseline. Ifwp_loadedwas at 200ms before and 12s after, post-fix it should be back near 200ms. If it isn't, you fixed the crash but missed the regression.
The verification window is 30 minutes minimum under real traffic. Eyeballing the homepage once and calling it green is how the same incident reappears at 3 AM the next night.
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.state_change.
The hard part of "wordpress plugin auto update broke site" is not fixing it. The fix is mechanical once you know which plugin to roll back. The hard part is finding out within minutes instead of hours, on a process that runs while you are asleep, on a site that has no native mechanism to escalate failure.
Auto-updates do not trigger alerts by default. WordPress sends a success email. PHP writes fatals to a log nobody reads. nginx writes 5xx to another log nobody reads. The dashboard is green because the process is alive. The uptime monitor only notices if the homepage itself breaks within its sampling window.
Logs reveal it immediately. Every failure mode in this guide leaves a wp.state_change followed by php.fatal followed by http.request 5xx within the same minute. The pattern is unambiguous — a plugin update at T, fatals from that plugin's path at T+10s, 5xx burst at T+30s. That is not ambiguous. That is a fingerprint.
This type of issue surfaces as wp.state_change events correlated with a php.fatal burst, which Logystera detects and alerts on early. The rule pattern — "any wp.state_change with auto_update=true followed by ≥3 php.fatal in 5 minutes" — fires before the first user complaint, not after the morning support inbox fills up.
You do not need a new dashboard. You need the existing logs to escalate themselves.
7. Related Silent Failures
- WordPress 500 error after plugin update — finding the failing plugin (
php.fatalfrom an unattended update path; same root mechanism, different framing). - WordPress white screen of death (WSOD) — diagnosing which plugin caused it (the rendered consequence of
php.fatalon a frontend request). - WordPress memory_limit exhausted — when a plugin update doubles memory use (
memory_near_limit→php.fatalchain). - WordPress plugin silent activation — detecting unauthorized state changes (
wp.state_changewithout a corresponding admin action — the security cousin of this incident). - WordPress integrity changes — detecting modified core or plugin files (
wp.state_changewith file hash mismatch — what auto-update looks like when it isn't actually an auto-update).
See what's actually happening in your WordPress system
Connect your site. Logystera starts monitoring within minutes.