diff --git src/wp-admin/css/list-tables.css src/wp-admin/css/list-tables.css
index 8d78983993..8bef97bae2 100644
--- src/wp-admin/css/list-tables.css
+++ src/wp-admin/css/list-tables.css
@@ -1301,6 +1301,24 @@ ul.cat-checklist {
 	text-decoration: underline;
 }
 
+.plugins tr.paused th.check-column {
+	border-left: 4px solid #d54e21;
+}
+
+.plugins tr.paused th,
+.plugins tr.paused td {
+	background-color: #fef7f1;
+}
+
+.plugins tr.paused .plugin-title,
+.plugins .paused .dashicons-warning {
+	color: #dc3232;
+}
+
+.plugins .resume-link {
+	color: #dc3232;
+}
+
 .plugin-card .update-now:before {
 	color: #f56e28;
 	content: "\f463";
diff --git src/wp-admin/includes/admin-filters.php src/wp-admin/includes/admin-filters.php
index bfc9f51cfc..0686b9b8c2 100644
--- src/wp-admin/includes/admin-filters.php
+++ src/wp-admin/includes/admin-filters.php
@@ -119,6 +119,7 @@ add_action( 'load-plugins.php', 'wp_plugin_update_rows', 20 ); // After wp_updat
 add_action( 'load-themes.php', 'wp_theme_update_rows', 20 ); // After wp_update_themes() is called.
 
 add_action( 'admin_notices', 'update_nag', 3 );
+add_action( 'admin_notices', 'paused_plugins_notice', 5 );
 add_action( 'admin_notices', 'maintenance_nag', 10 );
 
 add_filter( 'update_footer', 'core_update_footer' );
diff --git src/wp-admin/includes/class-wp-plugins-list-table.php src/wp-admin/includes/class-wp-plugins-list-table.php
index 6e130f94b4..e169a8f245 100644
--- src/wp-admin/includes/class-wp-plugins-list-table.php
+++ src/wp-admin/includes/class-wp-plugins-list-table.php
@@ -99,6 +99,7 @@ class WP_Plugins_List_Table extends WP_List_Table {
 			'upgrade'            => array(),
 			'mustuse'            => array(),
 			'dropins'            => array(),
+			'paused'            => isset( $GLOBALS['_paused_plugins'] ) ? $GLOBALS['_paused_plugins'] : array(),
 		);
 
 		$screen = $this->screen;
@@ -647,6 +648,10 @@ class WP_Plugins_List_Table extends WP_List_Table {
 						/* translators: %s: plugin name */
 						$actions['deactivate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Deactivate' ) . '</a>';
 					}
+					if ( current_user_can( 'resume_plugin' ) && is_plugin_paused( $plugin_file ) ) {
+						/* translators: %s: plugin name */
+						$actions['resume'] = '<a class="resume-link" href="' . wp_nonce_url( 'plugins.php?action=resume&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'resume-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Resume execution of %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Resume execution' ) . '</a>';
+					}
 				} else {
 					if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
 						/* translators: %s: plugin name */
@@ -753,6 +758,11 @@ class WP_Plugins_List_Table extends WP_List_Table {
 			$class .= ' update';
 		}
 
+		$paused = is_plugin_paused( $plugin_file );
+		if ( $paused ) {
+			$class .= ' paused';
+		}
+
 		$plugin_slug = isset( $plugin_data['slug'] ) ? $plugin_data['slug'] : sanitize_title( $plugin_name );
 		printf(
 			'<tr class="%s" data-slug="%s" data-plugin="%s">',
@@ -836,7 +846,16 @@ class WP_Plugins_List_Table extends WP_List_Table {
 					$plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status );
 					echo implode( ' | ', $plugin_meta );
 
-					echo '</div></td>';
+					echo '</div>';
+
+					if ( $paused ) {
+						echo sprintf(
+							'<p><span class="dashicons dashicons-warning"></span> <strong>%s</strong></p>',
+							__( 'This plugin failed to load properly and was paused within the admin backend.' )
+						);
+					}
+
+					echo '</td>';
 					break;
 				default:
 					$classes = "$column_name column-$column_name $class";
diff --git src/wp-admin/includes/plugin.php src/wp-admin/includes/plugin.php
index c898fc5169..4acf694d0f 100644
--- src/wp-admin/includes/plugin.php
+++ src/wp-admin/includes/plugin.php
@@ -444,6 +444,8 @@ function _get_dropins() {
 		'install.php'        => array( __( 'Custom installation script.' ), true ), // auto on installation
 		'maintenance.php'    => array( __( 'Custom maintenance message.' ), true ), // auto on maintenance
 		'object-cache.php'   => array( __( 'External object cache.' ), true ), // auto on load
+		'php-error.php'      => array( __( 'Custom PHP error message.' ), true ), // auto on error
+		'shutdown-handler'   => array( __( 'Custom PHP shutdown handler.' ), true ), // auto on error
 	);
 
 	if ( is_multisite() ) {
@@ -496,6 +498,34 @@ function is_plugin_inactive( $plugin ) {
 	return ! is_plugin_active( $plugin );
 }
 
+/**
+ * Determines whether a plugin is technically active but was paused while
+ * loading.
+ *
+ * For more information on this and similar theme functions, check out
+ * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
+ * Conditional Tags} article in the Theme Developer Handbook.
+ *
+ * @since 5.0.0
+ *
+ * @param string $plugin Path to the plugin file relative to the plugins directory.
+ *
+ * @return bool True, if in the active plugins list. False, not in the list.
+ */
+function is_plugin_paused( $plugin ) {
+	if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
+		return false;
+	}
+
+	if ( ! is_plugin_active( $plugin ) || is_plugin_active_for_network( $plugin ) ) {
+		return false;
+	}
+
+	$plugin_path = WP_CONTENT_DIR . '/plugins/' . $plugin;
+
+	return in_array( $plugin_path, $GLOBALS['_paused_plugins'], true );
+}
+
 /**
  * Determines whether the plugin is active for the entire network.
  *
@@ -693,6 +723,11 @@ function deactivate_plugins( $plugins, $silent = false, $network_wide = null ) {
 			continue;
 		}
 
+		// Clean up the database before deactivating the plugin.
+		if ( is_plugin_paused( $plugin ) ) {
+			resume_plugin( $plugin );
+		}
+
 		$network_deactivating = false !== $network_wide && is_plugin_active_for_network( $plugin );
 
 		if ( ! $silent ) {
@@ -887,6 +922,11 @@ function delete_plugins( $plugins, $deprecated = '' ) {
 			uninstall_plugin( $plugin_file );
 		}
 
+		// Clean up the database before removing the plugin.
+		if ( is_plugin_paused( $plugin_file ) ) {
+			resume_plugin( $plugin_file );
+		}
+
 		/**
 		 * Fires immediately before a plugin deletion attempt.
 		 *
@@ -959,6 +999,44 @@ function delete_plugins( $plugins, $deprecated = '' ) {
 	return true;
 }
 
+/**
+ * Resumes a single plugin.
+ *
+ * Resuming the plugin basically means removing its entry from the
+ * `pause_on_admin` database option.
+ *
+ * @since 5.0.0
+ *
+ * @param string $plugin Single plugin to resume.
+ *
+ * @return bool|WP_Error True on success, false if `$plugin` was not paused, `WP_Error` on failure.
+ */
+function resume_plugin( $plugin ) {
+	$pause_on_admin = get_option( 'pause_on_admin', array() );
+
+	if ( ! array_key_exists( 'plugin', $pause_on_admin ) ) {
+		return false;
+	}
+
+	list( $plugin ) = explode( '/', $plugin );
+
+	if ( ( $index = array_search( $plugin, $pause_on_admin['plugin'], true ) ) !== false ) {
+		unset( $pause_on_admin['plugin'][ $index ] );
+	}
+
+	if ( count( $pause_on_admin['plugin'] ) === 0 ) {
+		unset( $pause_on_admin['plugin'] );
+	}
+
+	$result = update_option( 'pause_on_admin', $pause_on_admin );
+
+	if ( ! $result ) {
+		return new WP_Error( 'could_not_resume_plugin', __( 'Could not resume execution of the plugin.' ) );
+	}
+
+	return true;
+}
+
 /**
  * Validate active plugins
  *
@@ -2066,3 +2144,36 @@ function wp_add_privacy_policy_content( $plugin_name, $policy_text ) {
 
 	WP_Privacy_Policy_Content::add( $plugin_name, $policy_text );
 }
+
+/**
+ * Renders an admin notice in case some plugins have been paused due to errors.
+ *
+ * @since 5.0.0
+ *
+ * @return void
+ */
+function paused_plugins_notice() {
+	if ( 'plugins.php' === $GLOBALS['pagenow'] ) {
+		return;
+	}
+
+	if ( ! current_user_can( 'deactivate_plugins' ) ) {
+		return;
+	}
+
+	if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) {
+		return;
+	}
+
+
+	echo sprintf(
+		'<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p>%s</p></div>',
+		__( 'One or more plugins failed to load properly.' ),
+		__( 'You can find more details and make changes on the Plugins screen.' ),
+		sprintf(
+			'<a href="%s">%s</a>',
+			admin_url( 'plugins.php' ),
+			'Go to the Plugins screen'
+		)
+	);
+}
diff --git src/wp-admin/plugins.php src/wp-admin/plugins.php
index b482f474c0..b21254adc9 100644
--- src/wp-admin/plugins.php
+++ src/wp-admin/plugins.php
@@ -389,6 +389,27 @@ if ( $action ) {
 			}
 			break;
 
+		case 'resume':
+			if ( ! current_user_can( 'resume_plugin', $plugin ) ) {
+				wp_die( __( 'Sorry, you are not allowed to resume execution of this plugin.' ) );
+			}
+
+			if ( is_multisite() && ! is_network_admin() && is_network_only_plugin( $plugin ) ) {
+				wp_redirect( self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ) );
+				exit;
+			}
+
+			check_admin_referer( 'resume-plugin_' . $plugin );
+
+			$result = resume_plugin( $plugin );
+
+			if ( is_wp_error( $result ) ) {
+				wp_die( $result );
+			}
+
+			wp_redirect( self_admin_url( "plugins.php?resume=true&plugin_status=$status&paged=$page&s=$s" ) );
+			exit;
+
 		default:
 			if ( isset( $_POST['checked'] ) ) {
 				check_admin_referer( 'bulk-plugins' );
@@ -532,6 +553,8 @@ elseif ( isset( $_GET['deleted'] ) ) :
 	<div id="message" class="updated notice is-dismissible"><p><?php _e( 'Selected plugins <strong>deactivated</strong>.' ); ?></p></div>
 <?php elseif ( 'update-selected' == $action ) : ?>
 	<div id="message" class="updated notice is-dismissible"><p><?php _e( 'All selected plugins are up to date.' ); ?></p></div>
+<?php elseif ( isset( $_GET['resume'] ) ) : ?>
+	<div id="message" class="updated notice is-dismissible"><p><?php _e( 'Execution of plugin <strong>resumed</strong>.' ); ?></p></div>
 <?php endif; ?>
 
 <div class="wrap">
diff --git src/wp-includes/capabilities.php src/wp-includes/capabilities.php
index 592995a120..315ae6e556 100644
--- src/wp-includes/capabilities.php
+++ src/wp-includes/capabilities.php
@@ -455,6 +455,7 @@ function map_meta_cap( $cap, $user_id ) {
 		case 'deactivate_plugins':
 		case 'activate_plugin':
 		case 'deactivate_plugin':
+		case 'resume_plugin':
 			$caps[] = 'activate_plugins';
 			if ( is_multisite() ) {
 				// update_, install_, and delete_ are handled above with is_super_admin().
diff --git src/wp-includes/load.php src/wp-includes/load.php
index 81014fdde8..f4ee1a3dad 100644
--- src/wp-includes/load.php
+++ src/wp-includes/load.php
@@ -687,6 +687,33 @@ function wp_get_active_and_valid_plugins() {
 			$plugins[] = WP_PLUGIN_DIR . '/' . $plugin;
 		}
 	}
+
+	/*
+	 * Remove plugins from the list of active plugins when we're on an admin or
+	 * login screen and the plugin appears in the `pause_on_admin` list.
+	 */
+	if ( 'wp-login.php' === $GLOBALS['pagenow']
+	     || ( is_admin() && ! wp_doing_ajax() ) ) {
+		$pause_on_admin  = (array) get_option( 'pause_on_admin', array() );
+
+		if ( ! array_key_exists( 'plugin', $pause_on_admin ) ) {
+			return $plugins;
+		}
+
+		foreach ( $plugins as $index => $plugin ) {
+			$parts = explode(
+				'/',
+				str_replace( WP_CONTENT_DIR . '/', '', $plugin )
+			);
+
+			if ( in_array( $parts[1], $pause_on_admin['plugin'], true ) ) {
+				unset( $plugins[ $index ] );
+				// Store list of paused plugins for displaying an admin notice.
+				$GLOBALS['_paused_plugins'][] = $plugin;
+			}
+		}
+	}
+
 	return $plugins;
 }
 
@@ -1250,3 +1277,113 @@ function wp_finalize_scraping_edited_file_errors( $scrape_key ) {
 	}
 	echo "\n###### wp_scraping_result_end:$scrape_key ######\n";
 }
+
+/**
+ * Wraps the shutdown handler function so it can be made pluggable at a later
+ * stage.
+ *
+ * @since 5.0.0
+ *
+ * @return void
+ */
+function wp_shutdown_handler_wrapper() {
+	if ( defined( 'WP_EXECUTION_SUCCEEDED' ) && WP_EXECUTION_SUCCEEDED ) {
+		return;
+	}
+
+	// Load the pluggable shutdown handler in case we found one.
+	if ( function_exists( 'wp_handle_shutdown' ) ) {
+		$stop_propagation = (bool) wp_handle_shutdown();
+
+		if ( $stop_propagation ) {
+			return;
+		}
+	}
+
+	$error = error_get_last();
+
+	// No error, just skip the error handling code.
+	if ( null === $error ) {
+		return;
+	}
+
+	/*
+	 * If the option API has not been loaded yet, we cannot persist our
+	 * discovery, so there's no point in moving forward.
+	 */
+	if ( ! function_exists( 'get_option' ) ) {
+		return;
+	}
+
+	// For now, we only trigger our safe mode on parse errors.
+	if ( ! isset( $error['type'] ) || E_PARSE !== $error['type'] ) {
+		return;
+	}
+
+	try {
+		$path = str_replace( WP_CONTENT_DIR . '/', '', $error['file'] );
+
+		$parts     = explode( '/', $path );
+		$type      = rtrim( array_shift( $parts ), 's' );
+		$extension = array_shift( $parts );
+
+		$pause_on_admin   = get_option( 'pause_on_admin', array() );
+
+		if ( ! array_key_exists( $type, $pause_on_admin ) ) {
+			$pause_on_admin[ $type ] = array();
+		}
+
+		if ( ! in_array( $extension, $pause_on_admin[ $type ], true ) ) {
+			$pause_on_admin[ $type ][] = $extension;
+		}
+
+		update_option( 'pause_on_admin', $pause_on_admin );
+
+		// Load custom PHP error template, if present.
+		if ( is_readable( WP_CONTENT_DIR . '/php-error.php' ) ) {
+			include WP_CONTENT_DIR . '/php-error.php';
+			die();
+		}
+
+		$message = sprintf(
+			'<p>%s</p>',
+			__( 'The site is experiencing technical difficulties.' )
+		);
+
+		if ( function_exists( 'get_admin_url' ) ) {
+			$url = get_admin_url();
+			$message .= sprintf(
+				'<hr><p><em>%s <a href="%s">%s</a></em></p>',
+				__( 'Are you the site owner?' ),
+				$url,
+				__( 'Log into the admin backend to fix this.' )
+			);
+		}
+
+		if ( function_exists( 'apply_filters' ) ) {
+			/**
+			 * Filters the message that the default PHP error page displays.
+			 *
+			 * @since 5.0.0
+			 *
+			 * @param string $message HTML error message to display.
+			 */
+			$message = apply_filters( 'wp_technical_issues_display', $message );
+		}
+
+		wp_die( $message );
+	} catch ( Exception $exception ) {
+		// Catch exceptions and remain silent.
+	}
+}
+
+/**
+ * Registers the WordPress premature shutdown handler.
+ *
+ * @since 5.0.0
+ *
+ * @return void
+ */
+function wp_register_premature_shutdown_handler() {
+	register_shutdown_function( 'wp_shutdown_handler_wrapper' );
+}
diff --git src/wp-settings.php src/wp-settings.php
index eb632238f0..c633f05b1d 100644
--- src/wp-settings.php
+++ src/wp-settings.php
@@ -20,6 +20,9 @@ require( ABSPATH . WPINC . '/load.php' );
 require( ABSPATH . WPINC . '/default-constants.php' );
 require_once( ABSPATH . WPINC . '/plugin.php' );
 
+// Make sure we register the premature shutdown handler as soon as possible.
+wp_register_premature_shutdown_handler();
+
 /*
  * These can't be directly globalized in version.php. When updating,
  * we're including version.php from another installation and don't want
@@ -40,6 +43,16 @@ global $blog_id;
 // Set initial default constants including WP_MEMORY_LIMIT, WP_MAX_MEMORY_LIMIT, WP_DEBUG, SCRIPT_DEBUG, WP_CONTENT_DIR and WP_CACHE.
 wp_initial_constants();
 
+/*
+ * Allow an optional shutdown handler to be included through a pluggable file.
+ * This file should register a function `wp_handle_shutdown( $context )` that
+ * returns a boolean value. If the return value evaluates to false, the default
+ * shutdown handler will not be executed.
+ */
+if ( is_readable( WP_CONTENT_DIR . '/shutdown-handler.php' ) ) {
+	include WP_CONTENT_DIR . '/shutdown-handler.php';
+}
+
 // Check for the required PHP version and for the MySQL extension or a database drop-in.
 wp_check_php_mysql_versions();
 
@@ -482,3 +495,12 @@ if ( is_multisite() ) {
  * @since 3.0.0
  */
 do_action( 'wp_loaded' );
+
+/*
+ * Store the fact that we could successfully execute the entire WordPress
+ * lifecycle. This is used to skip the premature shutdown handler, as it cannot
+ * be unregistered.
+ */
+if ( ! defined( 'WP_EXECUTION_SUCCEEDED' ) ) {
+	define( 'WP_EXECUTION_SUCCEEDED', true );
+}
