Guide

WordPress white screen of death — how to debug without admin access

You load your site and there is nothing. No header, no logo, no error, no 500 page. Just a flat white rectangle in the browser. View source shows an empty body, or in some hosts, literally zero bytes returned.

1. Problem

You load your site and there is nothing. No header, no logo, no error, no 500 page. Just a flat white rectangle in the browser. View source shows an empty body, or in some hosts, literally zero bytes returned. You try /wp-admin and get the same white nothing. The WordPress white screen of death is a blank page with no error message, and nine times out of ten the trigger is a php.fatal that PHP swallowed before it could reach the browser.

Refreshing does nothing. Logging into the dashboard is impossible because the dashboard itself is dead. Uptime monitors say the site is "up" because the server returned an HTTP 200. Your customers see a blank page. You see nothing useful in the browser. This is the WordPress wsod fix problem in its purest form: the failure has erased its own evidence from every place a non-engineer would think to look.

WordPress blank page no error is the worst class of WordPress failure precisely because the loudest part — the error message — is the part that is missing.

2. Impact

A WSOD is not a slow-loading site or a styling glitch. It is a complete production outage that does not page anyone, because:

  • Pingdom, UptimeRobot, and most uptime services treat HTTP 200 as success — even if the body is empty.
  • Cloudflare and CDNs happily cache a blank page if the origin returned 200, multiplying the outage to every visitor.
  • The admin dashboard is the same PHP application that crashed, so you cannot log in to disable plugins or check the error log.
  • Checkout pages, lead forms, and login flows return 200 and render nothing. Users assume the site is broken and leave.

For e-commerce, every minute of WSOD is direct revenue loss. For SaaS marketing sites, it is wasted ad spend — the campaign keeps running, the landing page keeps returning 200, and conversion silently drops to zero. For membership sites, locked-out customers fire support tickets that the support team also cannot triage, because the support admin lives in WordPress too.

The danger is not the bug. The danger is the silence around the bug.

3. Why It’s Hard to Spot

WSOD evades every detection system that does not read PHP logs:

  • Uptime checks pass. A blank 200 looks identical to a healthy 200 at the HTTP layer. Pingdom is happy. Your status page is green.
  • Application Performance Monitoring is often blind. Most APM agents instrument WordPress by hooking into init or plugins_loaded. A fatal during plugin bootstrap fires before those hooks run, so the APM agent never gets a chance to record the request.
  • The WordPress dashboard is dead. Site Health, the Plugin screen, and the debug log viewer all live inside the same broken PHP runtime. You cannot use them to diagnose the thing that broke them.
  • Hosts hide PHP logs. On managed hosts, /var/log/php-fpm/error.log is often invisible to the customer. cPanel exposes a watered-down "Errors" tab; managed WordPress hosts (Kinsta, WP Engine, Pressable) require ticket escalation.
  • The trigger is invisible. The fatal happens after a plugin auto-update, a PHP version bump, a wp-cron running in the background, or a deploy that touched composer.json. None of these produce a user-visible artifact at the moment they happen. The site looks fine until the next request hits the broken code path.

This is the textbook silent failure: the system is broken, the monitor says fine, and the only voice telling the truth is a log line nobody is reading.

4. Cause

A WordPress page request runs through index.phpwp-load.phpwp-settings.php, which loads the active theme's functions.php and every active plugin's bootstrap file. If any one of those PHP files throws a fatal error — Uncaught Error, Call to undefined function, Class not found, Allowed memory size exhausted — PHP halts execution immediately at that line.

That halt is what Logystera records as a php.fatal signal. The signal payload includes the error class, the message, the file path, the line number, and the PHP backtrace at the moment of the crash.

By default WordPress runs with display_errors = Off and a fatal error handler that emits wp_die() only for non-fatal conditions. A true fatal — anything that aborts the PHP process — bypasses wp_die() entirely. PHP-FPM closes the connection without writing a body. Nginx or Apache returns whatever it has, which is often nothing, sometimes a 500, sometimes a stale 200 from FastCGI buffering. The browser shows white.

The php.fatal signal is the only place where the actual error text — PHP Fatal error: Uncaught Error: Call to undefined function get_field() — is preserved. The browser does not see it. The HTTP response does not contain it. The WordPress database does not log it. It exists only in the PHP error log, and only if the host has the error log writing enabled in the right place.

If the fatal was preceded by climbing memory pressure, you will also see a memory_near_limit signal — typically a php.warning line like PHP Notice: Memory usage at 122M of 128M limit — fired in the seconds before the crash. That is your earliest warning, and the thing most engineers never look at until after the outage.

5. Solution

5.1 Diagnose (logs first)

You cannot debug WSOD from the browser. You debug it from the PHP error log, and the signal you are looking for is php.fatal.

Step 1 — locate the PHP error log. Common paths:

/var/log/php-fpm/www-error.log         # PHP-FPM default
/var/log/php/error.log                 # Debian/Ubuntu
/var/log/httpd/error_log               # Apache + mod_php
/var/log/nginx/error.log               # Nginx surfaces some FPM errors here
/home/<user>/logs/error.log            # cPanel, Plesk
/srv/users/<user>/log/<site>/php.log   # ServerPilot
wp-content/debug.log                   # if WP_DEBUG_LOG = true

If you have shell access:

sudo find /var/log -name '*.log' -mtime -1 | xargs sudo grep -l "PHP Fatal"

Step 2 — grep for the fatal. This produces the php.fatal signal:

sudo grep -E "PHP Fatal|Uncaught" /var/log/php-fpm/www-error.log | tail -50

A real entry looks like this:

[27-Apr-2026 14:02:11 UTC] PHP Fatal error:  Uncaught Error: Call to undefined function get_field()
in /var/www/wp-content/themes/storefront-child/functions.php:42
Stack trace:
#0 /var/www/wp-includes/class-wp-hook.php(310): require_once()
#1 /var/www/wp-includes/plugin.php(517): WP_Hook->apply_filters()
#2 {main}
  thrown in /var/www/wp-content/themes/storefront-child/functions.php on line 42

That single line tells you: which file (storefront-child/functions.php), which line (42), which symbol is missing (get_field — the ACF plugin function), and the call chain. It is the entire investigation in twelve lines. This grep produces the php.fatal signal directly.

Step 3 — check for the precursor. Memory exhaustion fatals look different. They produce the memory_near_limit signal first, then a php.fatal:

sudo grep -E "Allowed memory size|memory_limit" /var/log/php-fpm/www-error.log | tail -20

Example:

[27-Apr-2026 14:01:47 UTC] PHP Fatal error: Allowed memory size of 134217728 bytes exhausted
(tried to allocate 20480 bytes) in /var/www/wp-includes/class-wp-query.php on line 3201

134217728 is 128 MB. If you see this, the cause is not a plugin bug but resource starvation — different fix path.

Step 4 — correlate with the blank 200. Tail the web server access log for the same timestamp:

sudo tail -f /var/log/nginx/access.log | grep -v -E "200 [1-9]"

A WSOD request shows as 200 0 or 200 31 (zero bytes or near-zero). That is the http.request signal returning a blank 200 body. Cross-referencing the timestamp with the php.fatal line in step 2 confirms which request triggered the crash.

Step 5 — if you have no shell access. Add this to wp-config.php over SFTP or the host's file manager (above the / That's all / line):

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );

Reproduce the blank page once. Then read wp-content/debug.log:

tail -100 wp-content/debug.log | grep -E "Fatal|Uncaught"

WP_DEBUG_LOG captures the same php.fatal lines that PHP-FPM would log, and writes them inside the WordPress directory where you can reach them via SFTP. Turn this off again once you have the line you need — leaving it on in production is its own security signal.

5.2 Root Causes

(see root causes inline in 5.3 Fix)

5.3 Fix

Match the cause to the log line you found in section 5. Each cause maps to a specific signal pattern.

Cause 1: Plugin or theme calls an undefined function. Most common after an auto-update where a dependency was removed or renamed. Signal pattern: php.fatal with Call to undefined function or Class not found, often correlated with a recent wp.state_change event from the auto-updater. Fix: rename the offending plugin folder over SFTP — wp-content/plugins/broken-pluginwp-content/plugins/broken-plugin.off. WordPress sees the missing folder, deactivates the plugin on the next request, and the site returns. Then either roll the plugin back to the previous version or restore its missing dependency (the ACF example above means ACF itself was deactivated or removed).

Cause 2: Memory exhaustion. Signal pattern: memory_near_limit followed by php.fatal with Allowed memory size … exhausted. Cause is usually a plugin (WooCommerce reports, backup plugins, page builders) or a runaway query. Immediate fix: raise memory_limit in wp-config.php:

define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );

This is a stopgap. The real fix is to find which request is consuming 128 MB and either deactivate the plugin causing it or fix the query. Memory leaks always come back.

Cause 3: PHP version mismatch. Host upgraded PHP from 7.4 to 8.2 overnight. Old plugin uses removed syntax (each(), create_function, mysql_*). Signal pattern: php.fatal with Call to undefined function each() or Uncaught Error: ... is no longer supported. Fix: roll PHP back to the previous minor version in the host control panel while you update the offending plugin.

Cause 4: Corrupted core file. Signal pattern: php.fatal with require_once: Failed opening or syntax error, unexpected end of file in a wp-includes/*.php path. Cause is usually an interrupted update or a malware scanner that quarantined a core file. Fix: re-upload wp-admin/ and wp-includes/ from a clean WordPress download of the same version. Do not overwrite wp-content/.

Cause 5: Misconfigured wp-config.php. Signal pattern: php.fatal with Parse error or unexpected '<' pointing at wp-config.php. Usually a botched edit (missing semicolon, stray HTML tag from a copy-paste). Fix: open wp-config.php over SFTP and revert the last change.

Cause 6: .htaccess or opcache corruption (Apache). Signal pattern: site shows blank 200 but php.fatal log is empty. Fix: rename .htaccess, run opcache_reset() or restart PHP-FPM. If the blank goes away, the cache was serving a stale broken bytecode.

Work the list top-down. Cause 1 explains the majority of WSODs after a plugin update. Cause 2 explains most of the rest.

5.4 Verify

The signal that must stop appearing is php.fatal. Healthy looks like the absence of new Fatal error lines in the PHP error log under live traffic.

Run this immediately after applying a fix:

sudo tail -f /var/log/php-fpm/www-error.log | grep -E "PHP Fatal|Uncaught|Allowed memory"

Then load the homepage, the admin dashboard, the checkout page, and any URL that triggered the original outage. If the tail produces zero new lines for 30 minutes under normal traffic, the php.fatal signal has stopped firing and the fix is holding.

Cross-check the http.request signal too:

sudo tail -200 /var/log/nginx/access.log | awk '$9 == 200 && $10 < 1000 {print}'

A healthy WordPress page is 20 KB to 200 KB of HTML. If you still see 200 responses with bodies under 1 KB, the fatal is gone but a soft failure remains — usually a plugin error that escaped to the browser as a blank . Investigate before declaring victory.

For memory-related fixes, watch for memory_near_limit to disappear:

sudo grep -c "memory_limit" /var/log/php-fpm/www-error.log

If the count was rising every hour and is now flat, the leak is contained. If it is still rising, the memory limit raise was a band-aid and the underlying plugin still leaks.

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 php.fatal.

Fixing a WSOD is not the hard part. Knowing it happened is the hard part. The site returned HTTP 200. The uptime monitor was happy. The plugin auto-update succeeded as far as WordPress is concerned. Every dashboard you own said "fine" while customers saw a blank page.

This is what php.fatal is for. The signal exists at the moment PHP halts, before any uptime check runs, before any user complains, before the host's monitoring even sees a blip. Watching it gives you minutes of head start instead of hours of customer tickets.

Logystera ingests the WordPress PHP error log via the WP plugin and the wp.state_change audit stream. A php.fatal signal triggers an alert with the file, line, error class, and the plugin or theme that produced it. A preceding memory_near_limit triggers earlier — often before the first crash — so you can intervene before the site goes white. A blank http.request 200 with body size under 1 KB is itself a signal, and Logystera correlates it with the corresponding php.fatal so you see cause and effect in one alert, not two disconnected dashboards.

You do not need a new monitoring stack to detect WSOD. You need someone reading the PHP error log in real time, with the discipline to alert on php.fatal immediately and the context to tell you which deploy or auto-update caused it. That is the job Logystera was built for.

7. Related Silent Failures

  • Plugin auto-update breakage. A wp.state_change event for plugin update, followed seconds later by php.fatal on every request. The dashboard says "updated successfully," the site says blank page. Same diagnostic playbook as above.
  • Memory limit creep. A slow climb in memory_near_limit over days — not a sudden crash but a steady erosion of headroom — usually a plugin caching aggressively or a query growing with the database. Detect it before it becomes a php.fatal.
  • 5xx errors that are really WSOD in disguise. When PHP-FPM fully crashes (not just a fatal in one request), Nginx returns 502 or 504. Different http.request signal, same root cause family. Often preceded by php.fatal storms.
  • REST API blank responses. /wp-json/ returns {} or empty body. JavaScript editors (Gutenberg, headless front ends) break with no visible error. Same php.fatal mechanism, just on an API endpoint instead of a page.
  • Cron silently broken. If wp-cron.php fatals, scheduled posts stop publishing, transactional emails stop sending, and nothing in the admin UI tells you. Watch for wp.cron missed_schedule events alongside php.fatal in the cron context.

See what's actually happening in your WordPress system

Connect your site. Logystera starts monitoring within minutes.

Logystera Logystera
Monitoring for WordPress and Drupal sites. Install a plugin or module to catch silent failures — cron stalls, failed emails, login attacks, PHP errors — before users report them.
Company
Copyright © 2026 Logystera. All rights reserved.