WordPress recovery mode is a session that lets an administrator into wp-admin after a plugin or theme has caused a fatal error. You reach it through a link in an email core sends to the admin address, and inside that session the offending plugin is paused so the dashboard loads.
Two things about it are worth knowing before you need them. The pause applies to your browser only, so the public site stays broken, and core refuses to send the email at all in several situations that nothing tells you about.
Everything below is from the WordPress core source in wordpress-develop trunk, read on 2026-09-21.
What recovery mode actually pauses
When you are inside a recovery session and a plugin fatals, WP_Recovery_Mode::store_error() calls wp_paused_plugins()->set(), which writes to an option named "{$session_id}_paused_extensions" (class-wp-paused-extensions-storage.php).
That option name is the important part. The session id comes from your recovery cookie, so the paused list is keyed to your browser session and nothing else reads it.
A visitor hitting the front page has no recovery cookie, so they get no session id, so nothing is paused for them. They still get the critical error page.
Recovery mode buys you a working dashboard. It does not take the site off the floor, and the only thing that does is deactivating the plugin properly from the Plugins screen or with wp plugin deactivate <slug>.
Leaving the session with the "Exit Recovery Mode" button runs exit_recovery_mode(), which calls delete_all() on both paused stores and clears the email rate limit. If you exit without deactivating anything, you are back where you started.
Why the recovery email never arrived
This is the common case, and there are five distinct reasons in the source. Each one narrows down what is wrong.
The error was not in a plugin or theme file. get_extension_for_error() matches the crashing file path against WP_PLUGIN_DIR and the registered theme directories. A fatal in wp-config.php, in a must-use plugin, in a drop-in, or in core itself returns false, and handle_error() bails with invalid_source. No email.
The crash happened on the front end. If a recovery session is not already active, core checks is_protected_endpoint() before doing anything (class-wp-recovery-mode.php). That function returns true only for wp-login.php, for is_admin() requests that are not Ajax, and for a short allowlist of Ajax actions in is_protected_ajax_action(): activate-plugin, update-plugin, install-plugin, edit-theme-plugin-file, heartbeat and three more.
So a plugin that only fatals on the front end will never mail you. Load /wp-admin/ yourself to make core notice.
You are on multisite. WP_Fatal_Error_Handler::handle() guards the whole thing with if ( ! is_multisite() && wp_recovery_mode()->is_initialized() ). Recovery mode does not exist on a network, and the error page even swaps in different wording that tells you to contact your site administrator.
Related, and true on single sites too: is_network_plugin() excludes network-activated plugins from recovery mode.
One was already sent today. maybe_send_recovery_mode_email() reads the recovery_mode_email_last_sent option and sends only if time() > $last_sent + $rate_limit. The rate limit is DAY_IN_SECONDS, filterable through recovery_mode_email_rate_limit.
A site fatalling every few minutes gets one email per day, not one per crash. If you already deleted the first one, wp option delete recovery_mode_email_last_sent makes core send a fresh link on the next crash.
The handler is off, or mail is broken. WP_DISABLE_FATAL_ERROR_HANDLER in wp-config.php disables it outright (wp_is_fatal_error_handler_enabled()), and a number of managed hosts set it. The mail itself goes to RECOVERY_MODE_EMAIL if that constant is a valid address, and otherwise to the admin_email option, which on a site handed over from an agency is often a mailbox nobody reads.
There is a sixth case that is not a failure. The handler returns immediately if wp_is_maintenance_mode() is true, so a site that crashed mid-update stays quiet by design. That is a different problem, covered in WordPress stuck in maintenance mode.
You cannot enter recovery mode without the link
Several popular guides suggest visiting wp-login.php?action=entered_recovery_mode to get in without the email. That URL does not grant anything.
WP_Recovery_Mode_Link_Service::handle_begin_link() requires action=enter_recovery_mode plus an rm_token and an rm_key in the query string, and passes them to validate_recovery_mode_key(), which checks the key against a stored wp_fast_hash() value and rejects it past the TTL. Only then does it set the cookie and redirect to ?action=entered_recovery_mode, which is a landing page, not a door.
The key TTL is max( $valid_for, $rate_limit ), so one day by default, filterable with recovery_mode_email_link_ttl. A link from last week is dead.
The cookie that the valid link sets lasts WEEK_IN_SECONDS (recovery_mode_cookie_length in class-wp-recovery-mode-cookie-service.php), which is why a session you forgot about can still be live days later.
If the email is genuinely unreachable, stop chasing the link. Rename the plugin directory over SFTP or run wp plugin deactivate --all, then read the fatal in the log. The order to work through is in debugging a WordPress white screen.
Reproduce it on a throwaway site first
None of the above is worth learning for the first time while a client site is down. It is cheap to trigger deliberately.
Create a disposable WordPress install, turn on debug mode so WP_DEBUG_LOG is writing and the debug.log viewer is available, then write a one-file plugin that fatals:
<?php
/* Plugin Name: Fatal Test */
add_action( 'init', function () {
if ( is_admin() ) {
undefined_function_on_purpose();
}
} );
Drop it in over SSH or SFTP, activate it, and load /wp-admin/. You get the critical error page, and the admin address gets the recovery email.
Then test the parts that matter to you. Move the is_admin() check to the front end and confirm no email arrives. Crash twice in an hour and confirm the second one sends nothing. Check the option directly:
wp option get recovery_mode_email_last_sent
wp option list --search='*_paused_extensions'
WP-CLI has no wp recovery-mode command, so options and wp plugin deactivate are the whole toolkit from the shell.
A sandbox is the right place for this and staging is not, because you are deliberately putting the site into a state you would never want a staging environment to be in. When you are finished, delete it rather than cleaning it up.
When recovery mode is the wrong tool
If the crash only started after an update, the faster path is usually the rollback, not the recovery session. Core keeps the previous version in wp-content/upgrade-temp-backup/ for plugin and theme updates, which is covered in WordPress update failed and rolling a plugin back.
If you are on multisite, recovery mode is not available at all, so go straight to the log.
And if you are testing an update before it reaches production, none of this applies. Clone the live site with the Cloner, run the update there, and let a sandbox absorb the fatal instead of your visitors. That workflow is upgrade testing.
