How to Enable WordPress Debug Mode (Without Showing Errors to Visitors)

WordPress ships with a built-in debug system that surfaces PHP errors, database query problems, and deprecated function warnings the moment they happen. Most tutorials show you a one-line snippet to enable it, then leave you with a wall of warnings dumped onto the front-end of your site. That’s not how anyone actually debugs production WordPress.

This guide walks through how to enable debug mode without showing errors to visitors, all five debug constants worth knowing, the modern Recover Mode that ships in WordPress 5.2+, Query Monitor (the best free debug tool in the ecosystem), and how to log your own messages without breaking your site. The pattern at the end is what professional developers actually run on production.

The five debug constants

WordPress’s debug system is controlled by five constants in your wp-config.php file:

  • WP_DEBUG — the master switch. Enables PHP error reporting and surfaces deprecated function warnings.
  • WP_DEBUG_LOG — writes errors to wp-content/debug.log instead of (or in addition to) displaying them.
  • WP_DEBUG_DISPLAY — controls whether errors render on the page. Set to false on production.
  • SCRIPT_DEBUG — tells WordPress to load the un-minified versions of core CSS and JavaScript. Useful when you’re debugging admin scripts or theme JS.
  • SAVEQUERIES — logs every database query that runs, with timing data, into a $wpdb->queries array you can inspect.

You can use any combination of these. The mistake most tutorials make is showing only WP_DEBUG; on a real site, you almost always want it paired with at least WP_DEBUG_LOG and WP_DEBUG_DISPLAY false.

The production-safe debug block

WP_DEBUG section header — deep navy blue background with abstract code-bracket pattern and bold white WP_DEBUG text

Drop this into wp-config.php above the /* That's all, stop editing! */ comment. It enables full debugging, writes everything to a log, and hides errors from visitors so they never see a PHP warning on the front-end:

// Production-safe debug block
define( 'WP_DEBUG',         true );
define( 'WP_DEBUG_LOG',     true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );
define( 'SCRIPT_DEBUG',     true );
define( 'SAVEQUERIES',      false ); // Set to true only when you need it

Why all six lines matter:

  • WP_DEBUG_LOG sends warnings and errors to wp-content/debug.log. You can read them at your leisure without exposing anything publicly.
  • WP_DEBUG_DISPLAY false tells WordPress not to print errors. The @ini_set( 'display_errors', 0 ) tells PHP itself not to print errors, in case something runs before WP_DEBUG_DISPLAY is checked.
  • SCRIPT_DEBUG loads non-minified core scripts so stack traces in your browser console point to readable line numbers instead of mangled minified output.
  • SAVEQUERIES is off by default because it adds memory and CPU overhead. Turn it on temporarily when you’re chasing a slow page; turn it off again when you’re done.

This block is safe to leave on a staging site permanently. On production, only enable it when you’re actively troubleshooting, then disable it again.

Where the debug log lives

By default, WP_DEBUG_LOG writes to wp-content/debug.log. You can also pass a custom path:

// Write the debug log to a custom location outside wp-content
define( 'WP_DEBUG_LOG', '/var/log/wordpress/my-site.log' );

Custom paths are a good idea for two reasons. First, wp-content/debug.log is publicly accessible if your web server doesn’t deny direct file access (most do, but not all). Second, an unbounded debug.log on a noisy site can grow into gigabytes and take down the partition it lives on. A path like /var/log/wordpress/my-site.log can be rotated by logrotate like any other system log.

Quick block-direct-access protection if you keep the default path: add this to your root .htaccess:

<Files debug.log>
    Require all denied
</Files>

Query Monitor: the best free debug tool

Once WP_DEBUG is on, install the Query Monitor plugin. It adds a panel to the wp-admin toolbar that shows everything happening on the current request:

  • Every database query, with timing and the function that triggered it
  • Every PHP error, warning, and notice (with stack traces)
  • Every hook fired, with the functions attached to each one
  • HTTP API calls (external requests your site made)
  • Template loading, conditional tags that matched, the active theme template hierarchy
  • Memory usage and total request time

For 90% of debugging tasks, Query Monitor’s UI is faster than tailing the debug log. Install it on every staging site you build. On production, install it but restrict its visibility to admin users only (Settings → Query Monitor → “Authentication”); the front-end overhead is negligible when admins aren’t logged in.

WP_ENVIRONMENT_TYPE for conditional debugging

WordPress 5.5+ understands the concept of an environment type: production, staging, development, or local. Set it in wp-config.php:

define( 'WP_ENVIRONMENT_TYPE', 'staging' );

WordPress and well-behaved plugins read this value through wp_get_environment_type() and adjust behavior accordingly. You can use it to enable debugging only on non-production environments without hardcoding it:

// Enable debug only on local and staging
if ( in_array( wp_get_environment_type(), [ 'local', 'staging', 'development' ], true ) ) {
    define( 'WP_DEBUG',         true );
    define( 'WP_DEBUG_LOG',     true );
    define( 'WP_DEBUG_DISPLAY', false );
    @ini_set( 'display_errors', 0 );
}

Now your wp-config.php can be deployed identically across environments and debug mode follows the environment, not the file.

Recover Mode (WordPress 5.2+)

Recover Mode section header — warm amber background with abstract geometric pattern and bold white RECOVER MODE text

This is the most under-discussed debugging feature in WordPress. When a fatal PHP error happens (a plugin throws an exception, a theme function crashes, etc.), WordPress doesn’t immediately white-screen anymore. As of WordPress 5.2, it enters Recover Mode automatically: it emails the site admin a special login link that bypasses the broken plugin or theme, lets you log in to a working dashboard, and gives you a chance to deactivate the offending code.

You don’t have to enable Recover Mode; it ships on by default. But you do need to make sure your site can send email so the recovery link arrives. If wp_mail() is broken (a common cause of “I can’t recover from this error”), Recover Mode can’t reach you. Test your site’s email delivery before you need it.

The admin email used by Recover Mode is the one set in Settings → General → Administration Email Address. Make sure that mailbox is one you actually check.

Debugging via WP-CLI

If you have SSH access, WP-CLI handles every common debug operation without editing files:

# Enable debug mode in one command
wp config set WP_DEBUG true --raw
wp config set WP_DEBUG_LOG true --raw
wp config set WP_DEBUG_DISPLAY false --raw

# Tail the debug log live
tail -f wp-content/debug.log

# Run arbitrary PHP against your site (great for poking at globals)
wp eval 'var_dump( get_option( "siteurl" ) );'

# Drop into an interactive PHP shell with WordPress fully loaded
wp shell

# Disable debug when you're done
wp config delete WP_DEBUG
wp config delete WP_DEBUG_LOG
wp config delete WP_DEBUG_DISPLAY

wp shell is especially useful. It loads WordPress, gives you a PHP REPL, and lets you call any function (get_post( 42 ), wp_get_current_user(), etc.) and inspect the result without writing a temporary file or test page.

How to read the debug log

The debug log is plain text. Each entry has a timestamp, an error level, the message, and (for PHP fatals) a stack trace. A typical entry looks like:

[27-Apr-2026 14:32:01 UTC] PHP Notice:  Undefined variable: foo in /var/www/html/wp-content/themes/mytheme/functions.php on line 42
[27-Apr-2026 14:32:08 UTC] PHP Fatal error:  Uncaught Error: Call to undefined function bar() in /var/www/html/wp-content/plugins/myplugin/main.php:18
Stack trace:
#0 /var/www/html/wp-includes/class-wp-hook.php(308): MyPlugin->init()
#1 /var/www/html/wp-includes/plugin.php(205): WP_Hook->apply_filters('', Array)
...

What to look for, in order of priority:

  1. PHP Fatal error entries. Anything that says “Fatal” is what crashed the page. The file path tells you which plugin or theme is responsible.
  2. PHP Warning entries. These don’t crash the page but indicate something is broken. Common causes: a function returning false instead of an array, a missing file include, an undefined index access.
  3. PHP Notice entries (lowest priority). Often noise (undefined variables, deprecated functions). Worth fixing in your own code, ignorable from third-party plugins.

If the log is huge, filter it. grep -i "fatal" wp-content/debug.log shows only the entries that actually broke the site. grep -v "PHP Notice" wp-content/debug.log drops the noise.

Logging your own messages

When you’re debugging your own code, you don’t have to hunt for a way to print. PHP’s error_log() writes straight to your debug log:

function smartwp_track_login( $user_login ) {
    error_log( "User logged in: {$user_login} at " . current_time( 'mysql' ) );
}
add_action( 'wp_login', 'smartwp_track_login' );

For arrays and objects, wrap them with print_r( ..., true ):

error_log( 'User data: ' . print_r( $user, true ) );

For production-only logging that doesn’t pollute the log on dev environments, write a small helper that respects WP_DEBUG:

function smartwp_log( $message ) {
    if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
        return;
    }
    if ( is_array( $message ) || is_object( $message ) ) {
        $message = print_r( $message, true );
    }
    error_log( '[smartwp] ' . $message );
}

// Usage anywhere in your code
smartwp_log( 'Hit the contact form handler' );
smartwp_log( $_POST );

The [smartwp] prefix makes your messages easy to grep for in a busy log. Drop the helper into a site-specific plugin and call smartwp_log() anywhere you’d otherwise reach for var_dump().

Debug plugins as an alternative

If editing wp-config.php isn’t an option (managed host that locks the file, no SSH access, no FTP comfort), several plugins toggle the debug constants for you through a UI:

The WP Debugging plugin (by Andy Fragen) is the most minimal option. It just enables the debug constants for you. Activating it turns debug on; deactivating it turns debug off. No settings, no UI bloat.

Debug Bar is an older option that still works. It adds a dropdown bar in wp-admin showing PHP errors, queries, and request data. Useful if you want a quick admin-bar overlay without setting up Query Monitor’s panels, but most of its functionality has been superseded.

Query Monitor (mentioned above) is the most comprehensive option in the ecosystem. Free, well-maintained, and significantly more useful than the older debug-bar generation. If you can install only one debug-related plugin on a site, this is it.

For most modern setups, Query Monitor alone covers what you need. The wp-config approach plus Query Monitor is the combination most professional WordPress developers run.

Disable debug mode in production

This is the part most tutorials forget to emphasize. Debug mode running on production:

  • Slows the site down (logging has overhead, especially with SAVEQUERIES on)
  • Exposes file paths in error messages, which is useful information for an attacker probing your stack
  • Lets debug.log grow unbounded if it’s never rotated

When you’re done debugging, set the constants back to false or remove the block entirely:

define( 'WP_DEBUG',         false );
define( 'WP_DEBUG_LOG',     false );
define( 'WP_DEBUG_DISPLAY', false );

Better still: use the WP_ENVIRONMENT_TYPE conditional from earlier so production never has debug enabled by default.

Frequently asked questions

What does WordPress debug mode do?

It enables PHP’s full error reporting and surfaces messages WordPress would normally suppress: PHP notices, warnings, deprecated function calls, and database query problems. With WP_DEBUG_LOG enabled, those messages get written to a file you can read after the fact. Combined with Query Monitor, debug mode is how you find the actual cause of a slow page or a broken plugin instead of guessing.

Where is the WordPress debug log located?

By default, wp-content/debug.log. You’ll only see this file after errors have actually been logged; until then, the file doesn’t exist. You can override the location by passing a custom path: define( 'WP_DEBUG_LOG', '/var/log/wordpress/my-site.log' ). A custom path outside the web root is more secure and easier to integrate with system log rotation.

Is it safe to enable debug mode on a live site?

Only with the production-safe pattern: WP_DEBUG true, WP_DEBUG_LOG true, WP_DEBUG_DISPLAY false, plus @ini_set( 'display_errors', 0 ). Errors get logged to a file but never render to visitors. Without those last two, debug mode prints PHP warnings on the front-end of your site, which leaks paths to attackers and shows a broken-looking page to real users. Always pair the four lines.

How do I disable debug mode?

Set WP_DEBUG to false in wp-config.php, or remove the debug block entirely. From the command line, wp config set WP_DEBUG false --raw handles it in one shot. The cleanest pattern is to wrap the debug block in a WP_ENVIRONMENT_TYPE check so production never enables debug to begin with.

What’s the difference between WP_DEBUG and WP_DEBUG_LOG?

WP_DEBUG is the master switch that turns on error reporting. WP_DEBUG_LOG tells WordPress what to do with those errors: send them to a log file. Without WP_DEBUG_LOG, errors are still generated but never persisted, which means you can only catch them if they happen while you’re looking at the screen. The two constants are usually used together; WP_DEBUG alone is rarely useful in practice.

My site went down with a ‘critical error’ message. How do I recover?

Check your admin email inbox. Since WordPress 5.2, fatal errors trigger Recover Mode: WordPress emails a special login link to the site administrator that bypasses the broken plugin or theme. Click the link, log into a working dashboard, deactivate the offending code, and you’re back. If the email never arrives, your site’s mail delivery is broken; that’s a separate problem to solve, usually by configuring an SMTP plugin so wp_mail() actually sends.

Wrap-up

Enable the production-safe debug block from the top of this guide, install Query Monitor, and (optionally) use WP_ENVIRONMENT_TYPE so debug mode follows the environment instead of the file. From there, error_log() handles ad-hoc logging when you’re chasing your own bugs, and wp shell lets you poke at WordPress directly without writing a test page. The combination is what professional WordPress developers actually run; it’s faster than every “enable debug mode” tutorial out there because it skips the part where errors leak to your visitors.

Picture of Andy Feliciotti

Andy Feliciotti

Andy has been a full time WordPress developer for over 15 years. Through his years of experience has built 100s of sites and learned plenty of tricks along the way. Found this article helpful? Buy Me A Coffee

Leave a Reply

Your email address will not be published. Required fields are marked *

WordPress Tips Monthly
Get the latest from SmartWP to your inbox.