You updated WordPress (or a plugin, or PHP), refreshed the page, and your site turned into a crime scene. White screen. 500s. “Briefly unavailable for scheduled maintenance.” Checkout dead. CEO texting you like you personally unplugged the internet.
This is a production recovery playbook written for people who run real systems: you want fast diagnosis, safe rollback, and minimal heroics. You don’t need folklore. You need a stopwatch, logs, and a plan.
The rule of 30 minutes: triage beats perfection
In the first 30 minutes, your only job is to restore service safely, not to solve WordPress as a philosophical concept. That means making quick, reversible changes, taking notes, and resisting the temptation to “just tweak one more thing.”
Think like an SRE: pick the smallest intervention that restores availability while preserving evidence. Your future self will thank you when you’re doing the post-incident review and not guessing which of the eight “tiny edits” actually fixed it.
Operational priorities (in order)
- Stop the bleeding: restore a working version (even if temporarily degraded, like disabling a plugin).
- Confirm what’s broken: HTTP errors, PHP fatals, DB connectivity, permission errors, resource exhaustion.
- Stabilize: remove the trigger, verify caches, confirm background jobs.
- Only then do you chase root cause and plan a clean forward fix.
One quote that’s worth keeping on a sticky note: Paraphrased idea: “Hope is not a strategy.”
— commonly attributed in engineering circles to practitioners like Gene Kranz; the point is reliable either way.
Joke #1: Updates are like parachutes: if you test them only after you jump, you’re about to learn a lot, very quickly.
Fast diagnosis playbook (first/second/third checks)
This is the fastest path to the bottleneck. Don’t freestyle. Follow the sequence so you don’t waste 20 minutes in the wrong layer.
First: What is the user-facing failure?
- 503 “maintenance mode” or message about “Briefly unavailable”: likely
.maintenancefile stuck, or update aborted. - 500 / 502 / 504: web server ↔ PHP-FPM issue, PHP fatal, upstream timeout, resource exhaustion.
- “Error establishing a database connection”: DB creds, DB down, socket/host wrong, too many connections.
- White screen: PHP fatal with display off, or theme/plugin fatal early in bootstrap.
- Site loads but is slow: cache invalidation, autoloaded options bloat, object cache mismatch, DB slow queries.
Second: Check logs where the truth lives
- Web server error log (Nginx/Apache).
- PHP-FPM log (or systemd journal for php-fpm).
- WordPress debug log (if enabled).
- Database logs / status (if DB symptoms).
Third: Make the smallest safe rollback
- Disable the newest plugin(s) or switch theme.
- Roll back the code release (symlink or git deploy) if you have it.
- Restore from backup only if rollback isn’t possible or DB schema changed and you can’t reconcile.
Decision shortcut
If you can’t identify the failing layer within 5 minutes, stop clicking around and start capturing:
- Current HTTP status codes for
/and/wp-admin/. - Last 50 lines of error logs across web + PHP.
- Recent file changes (
mtime) inwp-content.
What “broke after an update” usually means
1) WordPress core update partially applied
A core update that gets interrupted (permissions, disk full, timeout) can leave mixed versions. Symptoms: fatal errors in core files, missing files, random behavior, and the classic “maintenance mode” lock.
2) Plugin update shipped a fatal error
Most common. A plugin assumes a PHP version, a function, or another plugin is present. Or it has a typo that never made it through someone’s CI. Symptoms: white screen, 500s, fatal errors referencing a plugin path.
3) Theme update or custom theme code incompatible
Same story as plugins, plus the added spice of bespoke functions.php code written in 2017 and last tested on PHP 7.4.
4) PHP version changed underneath you
OS updates, panel upgrades, or a “minor” image change can bump PHP. Symptoms: fatals like “Call to undefined function,” deprecated warnings turning into fatals (depending on config), or extensions missing (mysqli, intl, imagick).
5) Cache/object cache mismatch
Redis/Memcached object cache plugins can break when the backend changes, the plugin changes, or serialization formats shift. Symptoms: admin works but frontend fails, weird redirect loops, intermittent errors that vanish when you clear cache.
6) Database migrations and schema drift
Some plugins run migrations on update. If they fail halfway, you get broken admin screens, missing tables, or long-running queries that hammer the site.
7) The “everything is fine” lie: it’s actually storage or permissions
Updates write files. Writes need space, correct owner/group, and sane mount options. If your disk is full or permissions are wrong, you get partial updates, repeated failures, and a site that looks haunted.
Hands-on recovery tasks (commands, outputs, decisions)
These tasks are designed to be run in order. Each includes: command, realistic output, what it means, and the decision you make.
Assumptions: Linux host, Nginx or Apache, PHP-FPM, WordPress in /var/www/wordpress. Adjust paths, but don’t “wing it.” Confirm first.
Task 1: Confirm what the world sees (HTTP status, headers, timing)
cr0x@server:~$ curl -sS -D- -o /dev/null https://example.com/
HTTP/2 502
date: Fri, 27 Dec 2025 18:41:12 GMT
content-type: text/html
server: nginx
Meaning: Nginx is reachable, but upstream (PHP-FPM) is failing or timing out.
Decision: Stop touching WordPress files. Go to Nginx error log and PHP-FPM status/logs next.
Task 2: Identify the web stack (Nginx vs Apache) and service status
cr0x@server:~$ systemctl status nginx php8.2-fpm --no-pager
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Fri 2025-12-27 18:02:19 UTC; 38min ago
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled; preset: enabled)
Active: failed (Result: exit-code) since Fri 2025-12-27 18:39:58 UTC; 1min 14s ago
Meaning: PHP-FPM is down; WordPress isn’t even getting a chance to run.
Decision: Inspect PHP-FPM journal logs; likely config change, extension failure, or resource constraint.
Task 3: Read the PHP-FPM failure reason (journal)
cr0x@server:~$ sudo journalctl -u php8.2-fpm -n 80 --no-pager
Dec 27 18:39:58 server php-fpm8.2[21983]: ERROR: failed to open configuration file '/etc/php/8.2/fpm/php-fpm.conf': Permission denied (13)
Dec 27 18:39:58 server systemd[1]: php8.2-fpm.service: Main process exited, code=exited, status=78/CONFIG
Dec 27 18:39:58 server systemd[1]: php8.2-fpm.service: Failed with result 'exit-code'.
Meaning: This isn’t WordPress. It’s OS-level permissions on a config file. Usually caused by a misapplied hardening rule, wrong ownership, or broken file mode during a package upgrade.
Decision: Fix permissions on that file (or restore from package), then restart PHP-FPM. Don’t touch plugins yet.
Task 4: Validate config file permissions and restore sane modes
cr0x@server:~$ sudo ls -l /etc/php/8.2/fpm/php-fpm.conf
---------- 1 root root 4217 Dec 27 18:38 /etc/php/8.2/fpm/php-fpm.conf
Meaning: Mode is 000; nobody can read it, including PHP-FPM running as root at startup (depending on distro policy). That’s broken.
Decision: Restore to typical 644, restart, re-test HTTP. If that brings the site back, you still need to ask why it changed during the update.
cr0x@server:~$ sudo chmod 644 /etc/php/8.2/fpm/php-fpm.conf
cr0x@server:~$ sudo systemctl restart php8.2-fpm
cr0x@server:~$ systemctl status php8.2-fpm --no-pager
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled; preset: enabled)
Active: active (running) since Fri 2025-12-27 18:42:10 UTC; 3s ago
Meaning: PHP-FPM is healthy again.
Decision: Re-run curl. If still failing, now you pivot to application-level logs.
Task 5: Check Nginx error log for upstream clues
cr0x@server:~$ sudo tail -n 40 /var/log/nginx/error.log
2025/12/27 18:41:11 [error] 1132#1132: *889 upstream sent too big header while reading response header from upstream, client: 203.0.113.21, server: example.com, request: "GET / HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
Meaning: This is a real failure mode after updates: a plugin/theme changes cookies or headers, and the upstream response headers exceed Nginx buffer limits.
Decision: If this is the current blocker, adjust Nginx fastcgi buffers as a mitigation, but also search for the plugin generating enormous cookies/headers.
Task 6: Capture the actual PHP fatal error (enable logging without showing users)
Don’t turn on display_errors in production. Log it.
cr0x@server:~$ sudo grep -n "WP_DEBUG\|WP_DEBUG_LOG\|WP_DEBUG_DISPLAY" /var/www/wordpress/wp-config.php
88:define('WP_DEBUG', false);
Meaning: Debug logging is off; you may be blind.
Decision: Temporarily enable WP_DEBUG_LOG and keep display off. Then reproduce once, read log, revert later.
cr0x@server:~$ sudo sed -i "s/define('WP_DEBUG', false);/define('WP_DEBUG', true);\ndefine('WP_DEBUG_LOG', true);\ndefine('WP_DEBUG_DISPLAY', false);/g" /var/www/wordpress/wp-config.php
cr0x@server:~$ sudo -u www-data php -v
PHP 8.2.14 (cli) (built: Dec 5 2025 09:01:22) (NTS)
Meaning: You enabled debug logging. PHP CLI is present; good for running WP-CLI or quick tests.
Decision: Hit the failing endpoint once, then inspect wp-content/debug.log.
Task 7: Read WordPress debug log and pinpoint the offender
cr0x@server:~$ sudo tail -n 30 /var/www/wordpress/wp-content/debug.log
[27-Dec-2025 18:43:02 UTC] PHP Fatal error: Uncaught Error: Call to undefined function mb_strlen() in /var/www/wordpress/wp-content/plugins/newsletter-pro/includes/parser.php:117
Stack trace:
#0 /var/www/wordpress/wp-settings.php(453): include_once()
#1 /var/www/wordpress/wp-config.php(90): require_once('...')
#2 /var/www/wordpress/wp-load.php(50): require_once('...')
#3 /var/www/wordpress/wp-blog-header.php(13): require_once('...')
#4 /var/www/wordpress/index.php(17): require('...')
#5 {main}
thrown in /var/www/wordpress/wp-content/plugins/newsletter-pro/includes/parser.php on line 117
Meaning: Plugin code called mb_strlen(), but the PHP mbstring extension isn’t loaded. This commonly happens after a PHP upgrade or package change.
Decision: Restore service fast by disabling the plugin or installing the missing extension. Pick the least risky change for your environment.
Task 8: Confirm missing PHP extension
cr0x@server:~$ php -m | grep -i mbstring || echo "mbstring not loaded"
mbstring not loaded
Meaning: Extension is missing.
Decision: Install php-mbstring package for your distro/PHP version; then restart PHP-FPM. If you can’t install quickly (change control), disable the plugin first.
Task 9: Disable a plugin without wp-admin (rename directory)
cr0x@server:~$ cd /var/www/wordpress/wp-content/plugins
cr0x@server:~$ sudo mv newsletter-pro newsletter-pro.disabled
cr0x@server:~$ ls -1 | head
akismet
hello.php
newsletter-pro.disabled
woocommerce
Meaning: WordPress can’t load a renamed plugin directory; it will effectively deactivate it on next load.
Decision: Re-test the site. If it returns, you’ve confirmed the culprit and bought time to install mbstring and re-enable safely.
Task 10: Fix “stuck in maintenance mode” quickly
cr0x@server:~$ sudo ls -la /var/www/wordpress/.maintenance
-rw-r--r-- 1 www-data www-data 52 Dec 27 18:37 /var/www/wordpress/.maintenance
Meaning: WordPress left a maintenance lock file behind, usually from an interrupted update.
Decision: Remove it, then test again. If it comes back, updates are still failing (permissions, disk, or filesystem issues).
cr0x@server:~$ sudo rm -f /var/www/wordpress/.maintenance
cr0x@server:~$ curl -sS -o /dev/null -w "%{http_code}\n" https://example.com/
200
Task 11: Verify disk space and inode pressure (updates need writes)
cr0x@server:~$ df -h /var/www /tmp
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 39G 260M 100% /
tmpfs 1.9G 12M 1.9G 1% /tmp
Meaning: Root filesystem is full. Updates will fail in creative ways, including partial file writes and inability to create temp files.
Decision: Free space immediately (logs, old releases, caches). Then re-run the update later in a controlled window. Also check inodes if you have many small files.
cr0x@server:~$ df -i /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 262144 261900 244 100% /
Meaning: Inode exhaustion. Classic WordPress failure with too many cached files or backups stored locally.
Decision: Delete high-churn directories (cache, temp), move backups off-host, and set retention policies.
Task 12: Check file ownership and permissions in wp-content
cr0x@server:~$ sudo find /var/www/wordpress/wp-content -maxdepth 1 -type d -printf "%M %u:%g %p\n"
drwxr-xr-x root root /var/www/wordpress/wp-content
drwxr-xr-x root root /var/www/wordpress/wp-content/plugins
drwxr-xr-x root root /var/www/wordpress/wp-content/themes
drwxrwx--- www-data www-data /var/www/wordpress/wp-content/uploads
Meaning: If plugins/themes are owned by root and not writable by the web user, in-dashboard updates can fail mid-flight. That’s not inherently wrong (some shops want immutable code), but then you must deploy updates another way.
Decision: Choose a model: either immutable code (deploy pipeline; disable file edits), or allow WordPress to write (less safe). Don’t sit in the cursed middle where updates half-work.
Task 13: Use WP-CLI to list and deactivate plugins (clean and auditable)
cr0x@server:~$ cd /var/www/wordpress
cr0x@server:~$ sudo -u www-data wp plugin list --status=active
+-------------------+----------+--------+---------+
| name | status | update | version |
+-------------------+----------+--------+---------+
| woocommerce | active | none | 9.2.1 |
| wordpress-seo | active | none | 23.1 |
| newsletter-pro | active | none | 4.0.0 |
+-------------------+----------+--------+---------+
Meaning: You can manage plugins without wp-admin. This is the adult way to do emergency changes.
Decision: Deactivate the suspected plugin(s) first; test; then iterate.
cr0x@server:~$ sudo -u www-data wp plugin deactivate newsletter-pro
Plugin 'newsletter-pro' deactivated.
Task 14: Switch to a default theme without wp-admin
cr0x@server:~$ sudo -u www-data wp theme list
+----------------+----------+--------+---------+
| name | status | update | version |
+----------------+----------+--------+---------+
| twentytwentyfour | inactive | none | 1.2 |
| custom-theme | active | none | 3.8.7 |
+----------------+----------+--------+---------+
Meaning: If the theme update broke rendering, switching themes can restore frontend quickly (admin usually stays accessible).
Decision: Activate a default theme temporarily, then fix theme code offline.
cr0x@server:~$ sudo -u www-data wp theme activate twentytwentyfour
Success: Switched to 'Twenty Twenty-Four' theme.
Task 15: Diagnose DB connectivity vs app config
cr0x@server:~$ sudo -u www-data php -r "require '/var/www/wordpress/wp-config.php'; echo DB_HOST, PHP_EOL;"
localhost
Meaning: Confirms what WordPress thinks DB host is (useful when environment variables or includes are involved).
Decision: If DB errors appear, test TCP/socket connectivity and credentials directly.
cr0x@server:~$ mysql -h localhost -u wpuser -p -e "SELECT 1;"
Enter password:
1
1
Meaning: DB is reachable and creds work from this host.
Decision: If WordPress still says it can’t connect, suspect wrong socket, wrong DB_HOST (e.g., needs 127.0.0.1), or PHP missing mysqli extension.
Task 16: Check PHP extensions for MySQL
cr0x@server:~$ php -m | egrep -i "mysqli|pdo_mysql" || echo "no mysql extensions loaded"
mysqli
pdo_mysql
Meaning: PHP can talk to MySQL.
Decision: If DB still fails, check max connections and DB health.
Task 17: Check DB load and connection saturation
cr0x@server:~$ mysql -uroot -p -e "SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';"
Enter password:
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 198 |
+-------------------+-------+
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| max_connections | 200 |
+-----------------+-------+
Meaning: You’re at the edge. An update that increases queries (or breaks caching) can tip you into “too many connections,” which WordPress reports as DB connection failure.
Decision: Short term: restart PHP-FPM to drop idle connections, scale DB, or reduce concurrency. Medium term: fix the query explosion (often a plugin) and enable proper object caching.
Task 18: Confirm what changed recently (mtime scan)
cr0x@server:~$ sudo find /var/www/wordpress/wp-content -type f -mtime -1 -printf "%TY-%Tm-%Td %TH:%TM %p\n" | tail -n 10
2025-12-27 18:36 /var/www/wordpress/wp-content/plugins/newsletter-pro/includes/parser.php
2025-12-27 18:36 /var/www/wordpress/wp-content/plugins/newsletter-pro/newsletter-pro.php
2025-12-27 18:35 /var/www/wordpress/wp-content/themes/custom-theme/functions.php
Meaning: This narrows the suspect list to what actually changed.
Decision: Prioritize disabling/rolling back the most recently touched components.
Joke #2: Nothing motivates log hygiene like a 500 error and a dashboard that won’t load.
Three corporate mini-stories from the trenches
Mini-story 1: The incident caused by a wrong assumption
They ran a WordPress marketing site on a tidy little VM. The team “knew” it was simple: Nginx + PHP-FPM + MariaDB. The update in question was “just a security patch” for a popular form plugin.
The patch landed, and within minutes the site served intermittent 502s. Someone assumed it was a plugin bug and started rolling back plugin versions. Another person started increasing PHP-FPM workers. Traffic kept failing in waves.
The wrong assumption: “If it’s a 502, it’s PHP.” It wasn’t. The form plugin update increased the number of outbound HTTP requests (spam checks, API validation) and the VM had an outbound firewall policy that silently dropped those connections instead of rejecting them quickly. PHP workers piled up waiting on network timeouts. Nginx upstream timed out. 502.
The fix was boring: allow outbound to a small set of API endpoints, set sane cURL timeouts, and cap PHP-FPM request duration. The plugin wasn’t “broken.” The system was configured to fail slowly, which is the worst kind of failure in production.
The postmortem action item that mattered: when you see 502s, verify whether PHP is dying fast (fatals) or dying slow (timeouts). Your next step changes completely.
Mini-story 2: The optimization that backfired
A different org had a busy WordPress + WooCommerce stack. Someone decided to “optimize” by enabling aggressive full-page caching and caching logged-in users “because it seemed fine in staging.” They also flipped on Redis object cache with default settings. It was a Friday. Of course it was.
They then updated WooCommerce. The update added a few cookies and tweaked session logic. Suddenly customers saw each other’s carts. Not always. Just enough to be terrifying. Support tickets went nuclear.
The backfire was subtle: their caching layer didn’t vary correctly on cookies and authentication state after the update. The old logic happened to work with the previous cookie behavior; the new release exposed the incorrect assumption. Technically the cache did what it was told. Operationally, it committed a small crime.
Recovery was a rapid cache bypass for logged-in and cart-related paths, then a careful reintroduction of caching with correct vary rules. The deeper lesson: caching isn’t a toggle, it’s a product. And “it seems fine” is not a test plan.
Mini-story 3: The boring but correct practice that saved the day
A third company ran WordPress on a pair of app servers behind a load balancer, with the database on managed storage. They did weekly plugin updates. Nothing fancy. But they had two habits: (1) immutable app releases (symlinked directories), and (2) a nightly database backup with verified restore tests.
One update introduced a fatal error in a premium plugin. It took the frontend down immediately. The on-call engineer didn’t debug it live. They did the boring thing: pointed the symlink back to the previous release, cleared opcode cache, and confirmed 200s. Total outage: minutes.
Then they investigated. The plugin update required a newer PHP extension. In a dev environment, it was installed by default. In prod, it wasn’t. The fatal was legitimate, but it wasn’t urgent anymore because service was restored.
They rolled forward later the same day after installing the extension and pinning the plugin version until the vendor clarified requirements. The boring practice—releases you can revert in one command—didn’t feel glamorous. It felt like money.
Common mistakes: symptoms → root cause → fix
1) “Briefly unavailable for scheduled maintenance.” forever
Symptom: Frontend and admin show maintenance message hours after update.
Root cause: .maintenance file left behind after interrupted update (timeout, permissions, disk full).
Fix: Remove /var/www/wordpress/.maintenance. Then immediately check disk space and file permissions, or it will happen again.
2) 502 Bad Gateway after update
Symptom: Nginx returns 502; intermittent or constant.
Root cause: PHP-FPM down, PHP workers stuck, socket mismatch, or upstream buffer issues.
Fix: Check systemctl status php*-fpm, read journal, then check Nginx error log for “connect() failed”, “upstream timed out”, or “too big header”. Fix the actual message, not the vibe.
3) White screen of death (WSOD)
Symptom: Blank page, maybe 200 OK, no content.
Root cause: PHP fatal with error display disabled; often a plugin/theme fatal.
Fix: Enable WP_DEBUG_LOG temporarily and inspect wp-content/debug.log. Disable the failing plugin or theme. Revert debug afterwards.
4) “Error establishing a database connection”
Symptom: WordPress shows DB connection error immediately.
Root cause: DB down, wrong credentials, exhausted connections, DNS issues, or missing PHP MySQL extension after PHP upgrade.
Fix: Test DB login from the host, verify DB_HOST, check Threads_connected vs max_connections, ensure mysqli/pdo_mysql is loaded.
5) Admin works, frontend breaks (or vice versa)
Symptom: /wp-admin/ loads but homepage 500s, or homepage works but admin is broken.
Root cause: Theme-only failure, caching issues, or a plugin hooked only into frontend routes.
Fix: Switch to a default theme via WP-CLI, and temporarily disable caching/object cache plugins. Confirm with logs.
6) Random redirects / login loops after update
Symptom: Can’t stay logged in, redirects between http/https, or loops around wp-login.php.
Root cause: Mis-set siteurl/home options, reverse proxy SSL headers changed, or caching varying incorrectly on cookies.
Fix: Confirm proxy headers, ensure WordPress sees HTTPS correctly, check DB values for site URLs, purge caches, and fix vary rules.
7) Update UI says success, but code is half-updated
Symptom: Version numbers change in dashboard, but files are missing or old.
Root cause: Mixed permissions (some directories writable, others not), or disk full mid-extract.
Fix: Stop doing in-dashboard updates unless the server is configured for it. Use an atomic deploy method, or fix ownership consistently and monitor disk/inodes.
Checklists / step-by-step plan
The 30-minute recovery plan (do this, in this order)
- Start a timeline: write down the time, what was updated, and current symptom.
- Confirm status codes:
curlthe homepage and/wp-admin/. - Check service health: Nginx/Apache and PHP-FPM status.
- Read logs: web error log, PHP-FPM journal, WordPress debug log (enable logging if needed).
- Look for the obvious blockers:
.maintenance, disk full, inode full, permissions. - Rollback the smallest unit: disable the newest plugin or switch theme.
- Verify recovery: 200s, login works, checkout critical path works.
- Stabilize caching: purge page cache, reset object cache if it’s in the path.
- Capture evidence: save the fatal error stack trace and relevant config diffs.
- Revert temporary debug changes: turn off
WP_DEBUGand remove sensitive logs if required.
When to restore from backup (and when not to)
- Restore from backup if: DB schema migrations ran and you can’t undo them; files are missing across core; you have evidence of corruption; you don’t have a clean rollback mechanism.
- Do not restore from backup if: it’s just a single plugin fatal and you can deactivate it; you’re missing a PHP extension; a service is down; disk is full. Backups are for data loss, not for basic troubleshooting.
Safety rails for fixes in production
- Prefer disabling a plugin over “editing it live.”
- Make one change at a time. If you can’t explain what you changed, you can’t roll it back.
- If you must change config, capture a diff first.
- Anything involving DB writes should be treated as risky during incident response. Read-only investigations first.
Interesting facts and historical context (because it helps)
- WordPress started in 2003 as a fork of b2/cafelog, and its plugin ecosystem grew faster than most people’s dependency hygiene.
- Automatic background updates for minor core releases became standard years later, which reduced known-vulnerability windows but increased “surprise change” incidents in poorly observed environments.
- The “white screen of death” isn’t uniquely WordPress; it’s what you get when PHP fatals meet production configs that suppress output.
- PHP version jumps matter: changes like stricter typing behavior and deprecated features have repeatedly broken older themes and plugins, especially bespoke ones.
- Object caching in WordPress is optional by design; when enabled, it can dramatically reduce DB load—or amplify weirdness if serialization, TTLs, or cache keys are mishandled.
- Filesystem writes are part of the update mechanism: WordPress updates typically download zip archives, extract them, and swap files. That’s why disk/inode failures show up as “update issues.”
- Plugin updates can include database migrations, and those migrations often run under web request timeouts—an architectural constraint that can create half-applied states.
- The .maintenance lock file is a simple mechanism: WordPress drops it during upgrades and removes it afterward. If you crash mid-upgrade, it stays. Simple, effective, occasionally annoying.
FAQ
1) I can’t access wp-admin. What’s the fastest way to disable plugins?
Rename the plugin directory under wp-content/plugins or use WP-CLI: wp plugin deactivate plugin-name. Renaming works even when PHP is half-broken.
2) Is it safe to enable WP_DEBUG on production during an incident?
Yes, if you keep WP_DEBUG_DISPLAY false and use WP_DEBUG_LOG true temporarily. Turn it back off after you capture the error.
3) Why do I see 200 OK but a blank page?
PHP can fatal before generating output, and your server can still return 200 with an empty body. That’s why logs matter more than browser behavior.
4) Should I roll back WordPress core first or plugins first?
Plugins/themes first. Core rollbacks are riskier if the database update ran. Most breakages after “an update” are plugin/theme or PHP/environment changes.
5) What if the update changed the database schema?
Treat it carefully. Disable the plugin that triggered the migration, assess what tables/options changed, and prefer restoring a tested DB backup if you can’t confidently reverse it.
6) I fixed it by renaming a plugin folder. How do I re-enable it safely?
Don’t just rename it back and hope. First fix the underlying cause (missing PHP extension, PHP version mismatch). Then re-enable in a controlled way: low traffic window, logs open, quick rollback ready.
7) Why did an OS update break WordPress if I didn’t touch WordPress?
Because WordPress runs on PHP, web servers, and extensions. If PHP upgraded, extensions might be missing, ini defaults can change, and OPcache behavior can shift.
8) What’s the minimum monitoring that would have made this easier?
HTTP status checks on homepage and /wp-admin/, error rate alerts, PHP-FPM health (process up, queue length), disk/inode thresholds, and centralized error logs.
9) Can caching cause “everything broke” after an update?
Absolutely. Cached redirects, oversized headers, and incorrect vary rules can make a working app look broken. Always test with cache bypass and purge after a major update.
10) When should I suspect storage?
If updates fail repeatedly, if you see partial file changes, if you get permission errors, or if disk/inodes are near full. Storage failures often masquerade as “WordPress bugs.”
Next steps after you’re back online
Once the site is stable, don’t sprint back to business as usual. Use the calm to remove the conditions that made the incident possible.
- Write down the root trigger: which update, which component, which log message, which fix.
- Put updates on rails: either adopt immutable deployments (recommended) or make filesystem permissions consistent with in-dashboard updates (less recommended).
- Add a rollback mechanism: versioned releases, snapshots, or at least a tested restore procedure.
- Audit PHP extensions and versions: explicitly define what production must have, and enforce it.
- Test updates in a prod-like environment: same PHP version, same caching, same proxy headers, same plugin set.
- Set thresholds for disk and inodes: update failures due to full disks are embarrassing because they’re predictable.
The goal isn’t “never break.” The goal is breaking in a way that’s fast to diagnose, fast to roll back, and difficult to repeat.