When WordPress shows a white screen, “There has been a critical error on this website”, or a plugin misbehaves, the actual error message is hidden by default. Debug mode makes WordPress record PHP errors, warnings and notices, so you can see what’s wrong. On a production site, the key is to log errors without displaying them to visitors.
The safe production debugging setup
In wp-config.php, above the line /* That's all, stop editing! */:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true ); // write to wp-content/debug.log
define( 'WP_DEBUG_DISPLAY', false ); // don't show errors on pages
@ini_set( 'display_errors', 0 );
Reproduce the problem (load the broken page), then read the log:
tail -n 50 /var/www/example.com/wp-content/debug.log
Typical findings:
PHP Fatal error: Uncaught Error: Call to undefined function …inplugins/some-plugin/…: an incompatible or broken plugin;Allowed memory size of … exhausted: raise the memory limit, or find the leak. See WordPress memory limit;- deprecation notices: usually harmless, but they tell you which plugin needs updating.
Keep debug.log private
wp-content/debug.log is publicly downloadable by default, and it can leak paths, queries and sometimes data. Either log somewhere outside the web root:
define( 'WP_DEBUG_LOG', '/var/log/wordpress/example-debug.log' );
sudo mkdir -p /var/log/wordpress && sudo chown www-data:www-data /var/log/wordpress
…or block it in Nginx:
location ~* /wp-content/debug\.log$ { deny all; }
See the ideal Nginx config for WordPress.
Other useful constants
| Constant | Purpose |
|---|---|
SCRIPT_DEBUG |
Load unminified core CSS/JS (for script problems) |
SAVEQUERIES |
Record database queries (performance cost; for development) |
WP_DISABLE_FATAL_ERROR_HANDLER |
Disable the “critical error” screen and recovery mode emails (for development) |
WP_ENVIRONMENT_TYPE |
production, staging, development or local: lets plugins adjust behaviour |
When WordPress can’t even start: server logs
If PHP crashes before WordPress loads, debug.log may stay empty. Check the server logs:
sudo tail -n 50 /var/log/nginx/error.log
sudo journalctl -u php8.3-fpm --since "15 min ago"
sudo tail -n 50 /var/log/php8.3-fpm.log
Nginx’s error log shows PHP fatal errors passed through FastCGI (FastCGI sent in stderr: "PHP message: …"), timeouts (504), and permission problems. See Nginx access and error logs and journalctl.
Debug with WP-CLI
WP-CLI shows errors directly in the terminal:
wp plugin list # fatal errors appear here too
wp plugin deactivate --all # isolate a plugin problem
wp theme activate twentytwentyfive
wp eval 'echo get_option("home");'
If WP-CLI itself crashes because of a plugin, skip loading plugins and themes:
wp --skip-plugins --skip-themes plugin deactivate broken-plugin
See WP-CLI guide.
Query Monitor for performance and errors
The Query Monitor plugin adds a toolbar panel (for administrators only) with PHP errors, slow database queries, HTTP API calls, hooks and conditionals, per page. It’s the best tool for finding what makes a page slow. Deactivate it when you’re done.
Recovery mode
For fatal errors, WordPress (5.2+) emails the admin a recovery mode link. It lets you log in with the broken plugin or theme paused, so you can deactivate it. If the email doesn’t arrive (mail not configured), use WP-CLI or rename the plugin’s folder via SSH:
mv wp-content/plugins/broken-plugin wp-content/plugins/broken-plugin.off
Turn it off afterwards
Leaving WP_DEBUG on in production fills the disk with notices, and slows things down slightly. Set it back:
define( 'WP_DEBUG', false );
…and delete or rotate the log file.
Quick reference
| Goal | Setting |
|---|---|
| Log errors, hide from visitors | WP_DEBUG true, WP_DEBUG_LOG true, WP_DEBUG_DISPLAY false |
| Custom log path | define('WP_DEBUG_LOG', '/var/log/…'); |
| Server-level errors | /var/log/nginx/error.log, PHP-FPM log |
| Isolate plugins | wp plugin deactivate --all |
| Profile a page | Query Monitor |
Related: WordPress white screen of death and WordPress 500 error.