Bali, Indonesia
Insights

Why WP_DEBUG Shows Nothing When WordPress Returns a 500 Error

October 26, 2026
0:00
You get a 500 error, open wp-config.php, set WP_DEBUG to true, reload, and get the same blank page. No message on screen, no wp-content/debug.log file. The usual conclusion is that debug mode is broken, or that the problem has nothing to do with PHP. Usually neither is true. The request failed at a stage WordPress never reached, so its debugging switches never got a chance to run. This comes up often in support because WP_DEBUG is the first tip people find when they search for a WordPress error. It is a useful switch, but it only covers part of the request path. Knowing where that part starts tells you which log to open instead.
A request for a WordPress page passes through several layers. Each one can fail on its own terms, and each one has its own log:
  1. The web server (Apache, LiteSpeed, or Nginx) accepts the connection and maps the URL to a file on disk. Apache and LiteSpeed also read every .htaccess file in the directories along that path.
  2. The server decides the file needs PHP and hands it to a PHP runtime. That runtime might be mod_php inside Apache, LSAPI on LiteSpeed, or a separate PHP-FPM process reached over a FastCGI socket.
  3. PHP compiles and runs index.php, which pulls in wp-blog-header.php, then wp-load.php, then wp-config.php.
  4. wp-config.php defines its constants and finally requires wp-settings.php. Early in wp-settings.php, WordPress calls a function named wp_debug_mode(). That function reads WP_DEBUG, WP_DEBUG_LOG, and WP_DEBUG_DISPLAY and changes PHP's error settings to match.
  5. Only after that do plugins, themes, and WordPress's own fatal error handler come into play.
WP_DEBUG does nothing by itself. It is a constant that wp_debug_mode() looks at in step 4. If the request dies in steps 1 to 3, or inside wp-config.php before the define line has executed, WordPress code never runs. Errors at that point are handled by the server's defaults. On most production servers, PHP's display_errors setting is off and errors go to a server or pool log, not to the page. This also explains the blank page. When PHP hits a fatal error with display_errors off and nothing has been sent to the browser yet, it typically responds with status 500 and an empty body. That empty response is what you see. There is one useful signal here. Newer WordPress versions show a page that says 'There has been a critical error on this website' when a plugin or theme crashes. If you see that message, WordPress did load, its handler caught the error, and debug.log or the recovery email is the right place to look. A completely blank 500, or the server's generic error page, points earlier in the chain.
Apache reads .htaccess files on every request, without a restart. That is convenient, but it also means a broken line takes effect immediately. If Apache cannot parse a directive in .htaccess, it stops processing that request and returns 500. PHP is never called, so no PHP setting can report anything. The quickest way to confirm this is to request a static file that you know exists, such as readme.html in the WordPress root or an image under wp-content/uploads. A static file does not touch PHP. If it also returns 500, the problem is in the web server layer, and .htaccess is the first suspect. Typical causes include:
  • php_value or php_flag lines on a server where PHP runs through FPM or CGI. Those directives belong to mod_php, so Apache does not recognize them.
  • Directives from modules that are not loaded, such as Header lines without mod_headers.
  • A typo in a block that a security or caching plugin wrote and then left behind.
Two details trip people up. First, apachectl configtest checks the main Apache configuration only, not .htaccess files, so a clean result does not clear .htaccess. Second, LiteSpeed reads .htaccess too, but it does not always treat invalid or unsupported directives the way Apache does. Check its own error log rather than assuming Apache behavior. On a pure Nginx server, .htaccess is ignored entirely, so this whole class of failure does not apply.
With PHP-FPM, PHP runs as a separate service. A master process manages a pool of worker processes. The web server connects to the pool over a Unix socket or a TCP port and sends the request using the FastCGI protocol. Several things can go wrong in that handoff without WordPress being involved:
  • FPM is stopped, or the socket path in the web server config does not match the pool's listen setting. The connection fails outright.
  • A worker is killed partway through a request, for example by a segmentation fault in a buggy extension or by the kernel's out-of-memory killer. The web server sees the connection close before a valid response arrives.
  • The request runs past request_terminate_timeout and the master kills the worker on purpose.
In each case the PHP process either never started the script or died before it could write a log line. A killed process cannot run an error handler. The status code you see depends on the stack. Nginx typically reports 502 or 504, and Apache with mod_proxy_fcgi often reports 503. A CDN or reverse proxy in front may replace that code with its own error page. That is why one site might show 502 for the same fault that another site shows as 500. The evidence is split across logs. The web server error log records the connection side, such as a failed connect() to the socket or an upstream that closed early. The FPM master log records what happened to the worker. If memory is the suspect, the kernel log shows whether the OOM killer chose a PHP process.
This case confuses people the most, because the error is in the same file where they just turned on debugging. PHP compiles a whole file before it executes any line in it. If wp-config.php has a syntax error anywhere, such as a missing semicolon, an unclosed quote, or a stray character pasted from a chat window, compilation fails. Not a single line runs, including the define('WP_DEBUG', true) line, even if it sits above the typo. The parse error goes to PHP's own error log. A runtime fatal error near the top of the file has the same effect. An example is a require of a file that no longer exists after a migration. The error happens before the define line runs. Placement matters too. wp_debug_mode() runs inside wp-settings.php, so any define placed below the require_once line for wp-settings.php comes too late. Duplicates cause similar confusion. If WP_DEBUG is defined twice, the first definition wins and the second only triggers a warning. Many default wp-config.php files already contain define('WP_DEBUG', false) further up, so setting it to true at the bottom has no effect. A working arrangement looks like this:
PHP
// Place these above the 'stop editing' comment, and remove any earlier WP_DEBUG define
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );      // writes to wp-content/debug.log
define( 'WP_DEBUG_DISPLAY', false ); // keeps errors off the public page

/* That's all, stop editing! Happy publishing. */
require_once ABSPATH . 'wp-settings.php';
Even with correct placement, this only helps once the file compiles. That is why the next step is checking syntax, not toggling debug again.
Work from the outside in, following the request path. These commands are read-only. Paths depend on the distro and control panel, so adjust them to your setup:
BASH
# Check wp-config.php for syntax errors without executing it
php -l /path/to/wordpress/wp-config.php

# Web server error log (pick the one that matches your stack)
tail -n 50 /var/log/apache2/error.log    # Apache on Debian/Ubuntu
tail -n 50 /var/log/httpd/error_log      # Apache on RHEL-family systems
tail -n 50 /var/log/nginx/error.log      # Nginx

# Kernel messages, to spot PHP workers killed for memory
journalctl -k --since '1 hour ago' | grep -i -E 'oom|killed process'
The PHP-FPM log location depends on how PHP was packaged. Check the error_log setting in the FPM main config, or the service's journal, for example journalctl -u followed by the FPM unit name on your system. Keep in mind that php -l uses the command-line PHP binary, which may be a different version from the one FPM runs for the site. A file that passes the lint check could still fail under a different version. On shared hosting without shell access, look in the control panel's error log view. Many cPanel setups also write PHP errors to a file named error_log in the same directory as the script that failed. Whether that happens depends on the PHP handler in use. The lines you are looking for usually resemble these:
TEXT
Illustrative log format (paths, PIDs, and line numbers will differ)

/var/www/html/.htaccess: Invalid command 'php_value', perhaps misspelled or defined by a module not included in the server configuration
WARNING: [pool www] child 12345 exited on signal 11 (SIGSEGV)
upstream prematurely closed connection while reading response header from upstream
PHP Parse error:  syntax error, unexpected identifier in /var/www/html/wp-config.php on line 42
Each line points to a different layer. The first is the web server rejecting .htaccess. The second is FPM losing a worker. The third is Nginx noticing that the worker went away. The last is PHP refusing to compile wp-config.php. None of them would ever appear in debug.log. When WP_DEBUG shows nothing, treat that as information rather than a dead end. It tells you the failure happened before WordPress loaded. Test a static file first to separate web server problems from PHP problems. Lint wp-config.php. Then read the web server log, the FPM log, and the kernel log in that order. Turn WP_DEBUG back off once the site loads again, since debug output on a live site can expose file paths and other details you would rather keep private.