diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index 1f11365..c0f3c31 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -52,6 +52,27 @@ class ModuleInstaller implements ModuleInstallerInterface {
   protected $uninstallValidators;
 
   /**
+   * The name of the module we are currently attempting to (un)install.
+   *
+   * @var string
+   */
+  protected $module = '';
+
+  /**
+   * The active module installer.
+   *
+   * @var $this
+   */
+  protected static $activeInstaller = NULL;
+
+  /**
+   * Flag of whether we've registered our shutdown handler to do rollbacks.
+   *
+   * @var boolean
+   */
+  protected static $handlerRegistered = FALSE;
+
+  /**
    * Constructs a new ModuleInstaller instance.
    *
    * @param string $root
@@ -68,6 +89,13 @@ public function __construct($root, ModuleHandlerInterface $module_handler, Drupa
     $this->root = $root;
     $this->moduleHandler = $module_handler;
     $this->kernel = $kernel;
+
+    // Register the handler only once no matter how many times this class or the
+    // install method gets called.
+    if (!static::$handlerRegistered) {
+      drupal_register_shutdown_function([get_class(), 'rollbackModuleInstallFailureHandler']);
+      static::$handlerRegistered = TRUE;
+    }
   }
 
   /**
@@ -140,6 +168,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
       $source_storage = $config_installer->getSourceStorage();
     }
     $modules_installed = array();
+
     foreach ($module_list as $module) {
       $enabled = $extension_config->get("module.$module") !== NULL;
       if (!$enabled) {
@@ -155,6 +184,12 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         // exceptions if the configuration is not valid.
         $config_installer->checkConfigurationToInstall('module', $module);
 
+        // From here out we're making changes that may require rollback. Set
+        // the active installer to this object and mark which module we are
+        // working on.
+        static::$activeInstaller = $this;
+        $this->module = $module;
+
         // Save this data without checking schema. This is a performance
         // improvement for module installation.
         $extension_config
@@ -278,6 +313,11 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
 
         // Record the fact that it was installed.
         \Drupal::logger('system')->info('%module module installed.', array('%module' => $module));
+
+        // Release active installer - preventing the module from being
+        // uninstalled on script closure.
+        static::$activeInstaller = NULL;
+        $this->module = '';
       }
     }
 
@@ -291,6 +331,210 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
   }
 
   /**
+   * Roll back a failed install attempt.
+   *
+   * It is not uncommon for novice programmers using Drupal to install modules
+   * they haven't tested at all, with the predictable result that they'll
+   * occassionally hit parse errors or make bad function calls during their
+   * install hook, crashing the system. register_shutdown_function provides us
+   * one opportunity to clean house in this event.
+   *
+   * We do not run any hooks from this method. We cannot risk a second fatal
+   * error which would leave the system in an unrecoverable state.
+   *
+   * @see https://www.drupal.org/node/2474363
+   */
+  protected function rollback() {
+    try {
+      $this->deleteEntityBundles();
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+
+    try {
+      module_load_install($this->module);
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+
+    try {
+      // Remove all configuration belonging to the module.
+      \Drupal::service('config.manager')->uninstall('module', $this->module);
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+
+    try {
+      $this->deleteEntityTypes();
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+    // Remove the schema.
+    try {
+      drupal_uninstall_schema($this->module);
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+    // Remove the module's entry from the config. Don't check schema when
+    // uninstalling a module since we are only clearing a key.
+    try {
+      \Drupal::configFactory()->getEditable('core.extension')->clear("module.{$this->module}")->save(TRUE);
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+    // Update the kernel to exclude the uninstalled modules.
+    try {
+      \Drupal::configFactory()->getEditable('core.extension')->clear("module.{$this->module}")->save(TRUE);
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+
+    try {
+      $this->updateKernel($this->unregisterFromModuleHandler());
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+
+    try {
+      $this->uncacheModule();
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+
+    try {
+      $this->refreshThemes();
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+
+    try {
+      $this->removeModuleSchema();
+    }
+    catch (\Exception $e) {
+      watchdog_exception('system.module.rollback', $e);
+    }
+
+    \Drupal::logger('system')->warning('%module module rolled back after attempted install.', array('%module' => $this->module));
+  }
+
+  /**
+   * Handler called by register_shutdown_function.
+   *
+   * This gets called whenever the script exits whether or not there was an
+   * error. Therefore we only keep the activeInstaller set during the time that
+   * the system is vulnerable to crashing and leave it NULL at all other times.
+   */
+  public static function rollbackModuleInstallFailureHandler() {
+    if (!is_null(static::$activeInstaller)) {
+      static::$activeInstaller->rollback();
+    }
+  }
+
+  /**
+   * Remove a module's schema from the system.
+   *
+   * @todo Part of overall refactor in https://www.drupal.org/node/2490666
+   */
+  protected function removeModuleSchema() {
+    $schema_store = \Drupal::keyValue('system.schema');
+    $schema_store->delete($this->module);
+  }
+
+  /**
+   * Update the theme registry to include or remove a module.
+   *
+   * @todo Part of overall refactor in https://www.drupal.org/node/2490666
+   */
+  protected function refreshThemes() {
+    drupal_theme_rebuild();
+
+    // Modules can alter theme info, so refresh theme data.
+    // @todo ThemeHandler cannot be injected into ModuleHandler, since that
+    //   causes a circular service dependency.
+    // @see https://drupal.org/node/2208429
+    \Drupal::service('theme_handler')->refreshInfo();
+  }
+
+  /**
+   * Remove any potential cache bins provided by the module.
+   *
+   * @todo Part of overall refactor in https://www.drupal.org/node/2490666
+   */
+  protected function uncacheModule() {
+    $this->removeCacheBins($this->module);
+
+    // Clear the static cache of system_rebuild_module_data() to pick up the
+    // new module, since it merges the installation status of modules into
+    // its statically cached list.
+    drupal_static_reset('system_rebuild_module_data');
+
+    // Clear plugin manager caches.
+    \Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
+  }
+
+  /**
+   * Update the module handler to remove the module.
+   *
+   * The current ModuleHandler instance is obsolete with the kernel rebuild.
+   *
+   * @todo Part of overall refactor in https://www.drupal.org/node/2490666
+   */
+  protected function unregisterFromModuleHandler() {
+    $module_filenames = $this->moduleHandler->getModuleList();
+    unset($module_filenames[$this->module]);
+    $this->moduleHandler->setModuleList($module_filenames);
+    return $module_filenames;
+  }
+
+  /**
+   * Clean up all entity bundles of module being uninstalled.
+   *
+   * Clean up all entity bundles (including fields) of every entity type
+   * provided by the module that is being uninstalled.
+   *
+   * @todo Clean this up in https://www.drupal.org/node/2350111.
+   * @todo Part of overall refactor in https://www.drupal.org/node/2490666
+   */
+  protected function deleteEntityBundles() {
+    $entity_manager = \Drupal::entityManager();
+    foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
+      if ($entity_type->getProvider() == $this->module) {
+        foreach (array_keys($entity_manager->getBundleInfo($entity_type_id)) as $bundle) {
+          $entity_manager->onBundleDelete($bundle, $entity_type_id);
+        }
+      }
+    }
+  }
+
+  /**
+   * Notify components that this module's entity types are being deleted.
+   *
+   * For example, a SQL-based storage handler can use this as an
+   * opportunity to drop the corresponding database tables.
+   *
+   * @todo Clean this up in https://www.drupal.org/node/2350111.
+   * @todo Part of overall refactor in https://www.drupal.org/node/2490666
+   */
+  protected function deleteEntityTypes() {
+    $entity_manager = \Drupal::entityManager();
+    foreach ($entity_manager->getDefinitions() as $entity_type) {
+      if ($entity_type->getProvider() == $this->module) {
+        $entity_manager->onEntityTypeDelete($entity_type);
+      }
+    }
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
diff --git a/core/modules/system/src/Tests/Module/InstallTest.php b/core/modules/system/src/Tests/Module/InstallTest.php
index 1068b60..38a804b 100644
--- a/core/modules/system/src/Tests/Module/InstallTest.php
+++ b/core/modules/system/src/Tests/Module/InstallTest.php
@@ -80,4 +80,34 @@ public function testModuleNameLength() {
     }
   }
 
+  /**
+   * Tests enabling a bugged module.
+   *
+   * The module to be enabled will produce a PHP SynatxError during install.
+   * It must not break the website.
+   */
+  public function testEnableBadModule() {
+
+    // Create and log as admin user.
+    $this->adminUser = $this->drupalCreateUser(array('administer modules'));
+    $this->drupalLogin($this->adminUser);
+
+    // Enable bad_module module.
+    $edit['modules[Testing][bad_module][enable]'] = TRUE;
+    $this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
+
+    // Check the syntax error occured.
+    $this->assertText("PHP syntax exception !");
+
+    // Check that the module is not enabled.
+    $this->drupalGet('admin/modules');
+    $this->assertNoFieldChecked('edit-modules-testing-bad-module-enable', 'The module bad_module is not enabled.');
+    $this->drupalGet('admin/modules/uninstall');
+    $this->assertNoText('Bad module', 'The module bad_module is not in module uninstall list.');
+
+    // The exceptions are expected. Do not interpret them as a test failure.
+    // Not using File API; a potential error must trigger a PHP warning.
+    unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
+  }
+
 }
diff --git a/core/modules/system/tests/modules/bad_module/bad_module.info.yml b/core/modules/system/tests/modules/bad_module/bad_module.info.yml
new file mode 100644
index 0000000..96e71e0
--- /dev/null
+++ b/core/modules/system/tests/modules/bad_module/bad_module.info.yml
@@ -0,0 +1,6 @@
+name: 'Bad module'
+type: module
+description: 'Don&#39;t activate me, I have a PHP syntax error.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/system/tests/modules/bad_module/bad_module.module b/core/modules/system/tests/modules/bad_module/bad_module.module
new file mode 100644
index 0000000..01d36de
--- /dev/null
+++ b/core/modules/system/tests/modules/bad_module/bad_module.module
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * Implements hook_install().
+ */
+function bad_module_install() {
+  // Prevent test from collecting errors.
+  define('SIMPLETEST_COLLECT_ERRORS', FALSE);
+
+  // The unit tests cannot deal with a bad function call test, so this needs
+  // to be ran by hand and left commented out in other situations.
+  #call_to_no_func();
+
+  // They also cannot negotiate parse errors.
+  #Random parse()-error generating text
+
+  // This simulates a PHP syntax error or whatever unexpected behavior that
+  // might happen during the install process of a module.
+  // @see \Drupal\system\Tests\Module\InstallTest
+  throw new Exception("PHP syntax exception !");
+}
