diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index eac2f97..f1c3a64 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2234,6 +2234,8 @@ function _drupal_exception_handler($exception) {
  * Sets up the script environment and loads settings.php.
  */
 function _drupal_bootstrap_configuration() {
+  // Load the procedural configuration system helper functions.
+  require_once DRUPAL_ROOT . '/core/includes/config.inc';
   // Set the Drupal custom error handler.
   set_error_handler('_drupal_error_handler');
   set_exception_handler('_drupal_exception_handler');
@@ -2249,9 +2251,6 @@ function _drupal_bootstrap_configuration() {
 
   // Activate the class loader.
   drupal_classloader();
-
-  // Load the procedural configuration system helper functions.
-  require_once DRUPAL_ROOT . '/core/includes/config.inc';
 }
 
 /**
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 50fb9b6..409a997 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -146,14 +146,15 @@ function install_state_defaults() {
     // The last task that was completed during the previous installation
     // request.
     'completed_task' => NULL,
-    // This becomes TRUE only when a valid config directory is created or
-    // detected.
+    // TRUE when there are valid config directories.
     'config_verified' => FALSE,
-    // This becomes TRUE only when Drupal's system module is installed.
-    'database_tables_exist' => FALSE,
-    // This becomes TRUE only when a valid database connection can be
-    // established.
+    // TRUE when there is a valid database connection.
     'database_verified' => FALSE,
+    // TRUE when a valid settings.php exists (containing both database
+    // connection information and config directory names).
+    'settings_verified' => FALSE,
+    // TRUE when the base system is ready to operate.
+    'base_system_verified' => FALSE,
     // An array of forms to be programmatically submitted during the
     // installation. The keys of each element indicate the name of the
     // installation task that the form submission is for, and the values are
@@ -192,10 +193,6 @@ function install_state_defaults() {
     // $_SERVER array via drupal_override_server_variables(). Used by
     // non-interactive installations only.
     'server' => array(),
-    // This becomes TRUE only when a valid settings.php file is written
-    // (containing both valid database connection information and a valid
-    // config directory).
-    'settings_verified' => FALSE,
     // Installation tasks can set this to TRUE to force the page request to
     // end (even if there is no themable output), in the case of an interactive
     // installation. This is needed only rarely; for example, it would be used
@@ -274,88 +271,92 @@ function install_begin_request(&$install_state) {
   // This must go after drupal_bootstrap(), which unsets globals!
   global $conf;
 
-  require_once DRUPAL_ROOT . '/core/modules/system/system.install';
-  require_once DRUPAL_ROOT . '/core/includes/common.inc';
+  // Load include files containing procedural service helpers.
+  require_once DRUPAL_ROOT . '/core/includes/cache.inc';
+  require_once DRUPAL_ROOT . '/core/includes/database.inc';
+
+  // Required for module_list() override in early installer environment.
   require_once DRUPAL_ROOT . '/core/includes/file.inc';
+  require_once DRUPAL_ROOT . '/core/includes/module.inc';
+
+  // Required for various installer operations.
   require_once DRUPAL_ROOT . '/core/includes/install.inc';
-  require_once DRUPAL_ROOT . '/core/includes/schema.inc';
-  require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'core/includes/path.inc');
 
-  // Load module basics (needed for hook invokes).
-  include_once DRUPAL_ROOT . '/core/includes/module.inc';
-  include_once DRUPAL_ROOT . '/core/includes/session.inc';
-  require_once DRUPAL_ROOT . '/core/includes/entity.inc';
+  // Required for running install tasks.
+  require_once DRUPAL_ROOT . '/core/includes/form.inc';
+  require_once DRUPAL_ROOT . '/core/includes/ajax.inc';
+  require_once DRUPAL_ROOT . '/core/includes/batch.inc';
+
+  // Determine bootstrap status.
+  if (!empty($install_state['parameters']['bootstrap'])) {
+    $install_state['base_system_verified'] = (int) $install_state['parameters']['bootstrap'];
+  }
 
-  // Determine whether the configuration system is ready to operate.
+  // Determine whether base system services are ready to operate.
   $install_state['config_verified'] = install_verify_config_directory(CONFIG_ACTIVE_DIRECTORY) && install_verify_config_directory(CONFIG_STAGING_DIRECTORY);
+  $install_state['database_verified'] = install_verify_database_settings();
+  $install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified'];
 
-  // If it is not, replace the configuration storage with the InstallStorage
-  // implementation, for the following reasons:
-  // - The first call into drupal_container() will try to set up the regular
-  //   runtime configuration storage, using the CachedStorage by default. It
-  //   calls config_get_config_directory() to retrieve the config directory to
-  //   use, but that throws an exception, since $config_directories is not
-  //   defined since there is no settings.php yet. If there is a prepared
-  //   settings.php already, then the returned directory still cannot be used,
-  //   because it does not necessarily exist. The installer ensures that it
-  //   exists and is writeable in a later step.
-  // - The installer outputs maintenance theme pages and performs many other
-  //   operations, which try to load configuration. Since there is no active
-  //   configuration yet, and because the configuration system does not have a
-  //   notion of default values at runtime, data is missing in many places. The
-  //   lack of data does not trigger errors, but results in a broken user
-  //   interface (e.g., missing page title, etc).
-  // - The actual configuration data to read during installation is essentially
-  //   the default configuration provided by the installation profile and
-  //   modules (most notably System module). The InstallStorage therefore reads
-  //   from the default configuration directories of extensions.
-  // This override is reverted as soon as the config directory has been set up
-  // successfully.
-  // @see drupal_install_config_directories()
-  if (!$install_state['config_verified']) {
+  // If they are not, replace services with in-memory implementations and
+  // specialized installer implementations. This override is reverted as soon as
+  // the install_system_rebuild() phase is reached.
+  if (!$install_state['base_system_verified']) {
     // @todo Move into a proper Drupal\Core\DependencyInjection\InstallContainerBuilder.
     $container = new ContainerBuilder();
 
+    // The actual configuration data to read during installation is essentially
+    // the default configuration provided by the installation profile and
+    // modules (most notably System module). The InstallStorage therefore reads
+    // from the default configuration directories of extensions.
     $container->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher');
-
     $container->register('config.storage', 'Drupal\Core\Config\InstallStorage');
     $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
       ->addArgument(new Reference('config.storage'))
       ->addArgument(new Reference('dispatcher'));
-    drupal_container($container);
-  }
 
-  // Set up $language, so t() caller functions will still work.
-  drupal_language_initialize();
+    $conf['keyvalue_default'] = 'keyvalue.memory';
+    $container
+      ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory')
+      ->addArgument(new Reference('service_container'));
+    $container
+      ->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory');
 
-  require_once DRUPAL_ROOT . '/core/includes/ajax.inc';
-  // Override the module list with a minimal set of modules.
-  $module_list['system']['filename'] = 'core/modules/system/system.module';
-  $module_list['user']['filename']   = 'core/modules/user/user.module';
-  module_list(NULL, $module_list);
-  drupal_load('module', 'system');
-  drupal_load('module', 'user');
-
-  // Load the cache infrastructure using a "fake" cache implementation that
-  // does not attempt to write to the database. We need this during the initial
-  // part of the installer because the database is not available yet. We
-  // continue to use it even when the database does become available, in order
-  // to preserve consistency between interactive and command-line installations
-  // (the latter complete in one page request and therefore are forced to
-  // continue using the cache implementation they started with) and also
-  // because any data put in the cache during the installer is inherently
-  // suspect, due to the fact that Drupal is not fully set up yet.
-  require_once DRUPAL_ROOT . '/core/includes/cache.inc';
-  $conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\InstallBackend');
+    $conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\InstallBackend');
+    $conf['lock_backend'] = 'Drupal\Core\Lock\NullLockBackend';
 
-  // The install process cannot use the database lock backend since the database
-  // is not fully up, so we use a null backend implementation during the
-  // installation process. This will also speed up the installation process.
-  // The site being installed will use the real lock backend when doing AJAX
-  // requests but, except for a WSOD, there is no chance for a a lock to stall
-  // (as opposed to the cache backend) so we can afford having a null
-  // implementation here.
-  $conf['lock_backend'] = 'Drupal\Core\Lock\NullLockBackend';
+    drupal_container($container);
+
+    // Override the module list with a minimal set of modules.
+    $module_list['system']['filename'] = 'core/modules/system/system.module';
+    $module_list['user']['filename'] = 'core/modules/user/user.module';
+    module_list(NULL, $module_list);
+    drupal_load('module', 'system');
+    drupal_load('module', 'user');
+
+    // Set up $language, so t() caller functions will still work.
+    drupal_language_initialize();
+  }
+  // We have reached the install_system_rebuild() install task and need to
+  // install System module as well as all the rest of the system in a full
+  // environment.
+  else {
+    require_once DRUPAL_ROOT . '/core/includes/common.inc';
+    // DRUPAL_BOOTSTRAP_SESSION is incompatible with the installer, as it tries
+    // to read and write sessions before the {users} table has been installed.
+    // Therefore, the install_base_system() task needs a custom bootstrap that
+    // avoids session initialization but is otherwise still a full environment.
+    // @todo Convert session handling into a swappable service.
+    // @see http://drupal.org/node/335411
+    if ($install_state['parameters']['bootstrap'] == DRUPAL_BOOTSTRAP_VARIABLES) {
+      drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
+      _drupal_bootstrap_code();
+    }
+    else {
+      drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
+    }
+    $kernel = new DrupalKernel('prod', TRUE, NULL);
+    $kernel->boot();
+  }
 
   // Prepare for themed output. We need to run this at the beginning of the
   // page request to avoid a different theme accidentally getting set. (We also
@@ -364,34 +365,8 @@ function install_begin_request(&$install_state) {
   // accessing the database before it is set up yet.)
   drupal_maintenance_theme();
 
-  // Check existing settings.php.
-  $install_state['database_verified'] = install_verify_database_settings();
-  $install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified'];
-
-  if ($install_state['database_verified']) {
-    // Initialize the database system. Note that the connection
-    // won't be initialized until it is actually requested.
-    require_once DRUPAL_ROOT . '/core/includes/database.inc';
-
-    // Verify the last completed task in the database, if there is one.
-    $task = install_verify_completed_task();
-  }
-  else {
-    $task = NULL;
-
-    // Since previous versions of Drupal stored database connection information
-    // in the 'db_url' variable, we should never let an installation proceed if
-    // this variable is defined and the settings file was not verified above
-    // (otherwise we risk installing over an existing site whose settings file
-    // has not yet been updated).
-    if (!empty($GLOBALS['db_url'])) {
-      throw new Exception(install_already_done_error());
-    }
-  }
-
   // Modify the installation state as appropriate.
-  $install_state['completed_task'] = $task;
-  $install_state['database_tables_exist'] = !empty($task);
+  $install_state['completed_task'] = install_verify_completed_task();
 
   // Add the list of available profiles to the installation state.
   $install_state['profiles'] += drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles');
@@ -435,8 +410,8 @@ function install_run_tasks(&$install_state) {
     if (!$install_state['task_not_complete']) {
       $install_state['tasks_performed'][] = $task_name;
       $install_state['installation_finished'] = empty($tasks_to_perform);
-      if ($install_state['database_tables_exist'] && ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished'])) {
-        variable_set('install_task', $install_state['installation_finished'] ? 'done' : $task_name);
+      if ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished']) {
+        state()->set('system.install_task', $install_state['installation_finished'] ? 'done' : $task_name);
       }
     }
     // Stop when there are no tasks left. In the case of an interactive
@@ -464,7 +439,6 @@ function install_run_task($task, &$install_state) {
   $function = $task['function'];
 
   if ($task['type'] == 'form') {
-    require_once DRUPAL_ROOT . '/core/includes/form.inc';
     if ($install_state['interactive']) {
       // For interactive forms, build the form and ensure that it will not
       // redirect, since the installer handles its own redirection only after
@@ -509,7 +483,7 @@ function install_run_task($task, &$install_state) {
   elseif ($task['type'] == 'batch') {
     // Start a new batch based on the task function, if one is not running
     // already.
-    $current_batch = variable_get('install_current_batch');
+    $current_batch = state()->get('system.install_current_batch');
     if (!$install_state['interactive'] || !$current_batch) {
       $batch = $function($install_state);
       if (empty($batch)) {
@@ -522,7 +496,7 @@ function install_run_task($task, &$install_state) {
       // task is currently running. Otherwise, we need to make sure the batch
       // will complete in one page request.
       if ($install_state['interactive']) {
-        variable_set('install_current_batch', $function);
+        state()->set('system.install_current_batch', $function);
       }
       else {
         $batch =& batch_get();
@@ -535,7 +509,6 @@ function install_run_task($task, &$install_state) {
     // If we are in the middle of processing this batch, keep sending back
     // any output from the batch process, until the task is complete.
     elseif ($current_batch == $function) {
-      include_once DRUPAL_ROOT . '/core/includes/batch.inc';
       $output = _batch_page();
       // Because Batch API now returns a JSON response for intermediary steps,
       // but the installer doesn't handle Response objects yet, just send the
@@ -551,7 +524,7 @@ function install_run_task($task, &$install_state) {
       // longer requesting a batch ID.
       if ($output === FALSE) {
         // Return nothing so the next task will run in the same request.
-        variable_del('install_current_batch');
+        state()->delete('system.install_current_batch');
         return;
       }
       else {
@@ -646,10 +619,13 @@ function install_tasks($install_state) {
       // since the form submit handler is where settings.php is rewritten.
       'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
     ),
-    'install_base_system' => array(
+    'install_system_schema' => array(
+      'run' => $install_state['base_system_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
     ),
-    'install_bootstrap_full' => array(
-      'run' => INSTALL_TASK_RUN_IF_REACHED,
+    'install_system_rebuild' => array(
+    ),
+    // All tasks below are executed in a regular, full Drupal environment.
+    'install_base_system' => array(
     ),
     'install_profile_modules' => array(
       'display_name' => count($install_state['profiles']) == 1 ? st('Install site') : st('Installation profile'),
@@ -876,15 +852,78 @@ function install_verify_requirements(&$install_state) {
 }
 
 /**
+ * Install task: Installs the System module schema.
+ */
+function install_system_schema(&$install_state) {
+  require_once DRUPAL_ROOT . '/core/includes/schema.inc';
+  require_once DRUPAL_ROOT . '/core/modules/system/system.install';
+  drupal_install_schema('system');
+}
+
+/**
+ * Install task: Performs a full bootstrap into the new environment.
+ */
+function install_system_rebuild(&$install_state) {
+  global $conf;
+
+  // If we are in the interactive installer, all we need to do is to set the
+  // 'bootstrap' parameter. Doing so will trigger a redirect and the next page
+  // request will perform a full boostrap into the new environment.
+  $install_state['base_system_verified'] = TRUE;
+  $install_state['parameters']['bootstrap'] = DRUPAL_BOOTSTRAP_VARIABLES;
+  if ($install_state['interactive']) {
+    return;
+  }
+
+  // Otherwise, this phase is reached within the early-installer environment.
+  // We need to unset all overrides and rebuild all necessary facilities to get
+  // into a regular bootstrap state.
+  unset($conf['cache_classes']);
+  unset($conf['keyvalue_default']);
+  unset($conf['lock_backend']);
+
+  // Ensure that custom service definitions in a prepared settings.php are
+  // applied. This effectively allows to install Drupal with custom Cache,
+  // KeyValue, and Lock backends in the non-interactive installer.
+  $conf_path = conf_path();
+  if (is_readable(DRUPAL_ROOT . '/' . $conf_path . '/settings.php')) {
+    include DRUPAL_ROOT . '/' . $conf_path . '/settings.php';
+  }
+
+  // Rebuild and replace the container with working service implementations.
+  drupal_container(NULL, TRUE);
+
+  // Clear out module list and hook implementation statics.
+  module_list_reset();
+  module_load_all(FALSE, TRUE);
+  system_list_reset();
+  module_implements_reset();
+
+  // Bootstrap, but avoid session initialization.
+  // @see install_begin_request()
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
+  _drupal_bootstrap_code();
+  $kernel = new DrupalKernel('prod', TRUE, NULL);
+  $kernel->boot();
+}
+
+/**
  * Installation task; install the base functionality Drupal needs to bootstrap.
  *
  * @param $install_state
  *   An array of information about the current installation state.
  */
 function install_base_system(&$install_state) {
-  // Install system.module.
+  // Install System module.
+  // This cannot use module_enable(), since System module's database tables have
+  // to be created separately in install_system_schema(), so we are able to
+  // switch to a full Drupal environment in install_system_rebuild().
   drupal_install_system();
 
+  // Something in the installer bootstrap primes file_get_stream_wrappers() too
+  // early. Resetting that static alone doesn't resolve it.
+  drupal_static_reset();
+
   // Call file_ensure_htaccess() to ensure that all of Drupal's standard
   // directories (e.g., the public files directory and config directory) have
   // appropriate .htaccess files. These directories will have already been
@@ -901,13 +940,17 @@ function install_base_system(&$install_state) {
   // Save the list of other modules to install for the upcoming tasks.
   // variable_set() can be used now that system.module is installed.
   $modules = $install_state['profile_info']['dependencies'];
-
   // The installation profile is also a module, which needs to be installed
   // after all the dependencies have been installed.
   $modules[] = drupal_get_profile();
-
   variable_set('install_profile_modules', array_diff($modules, array('system')));
-  $install_state['database_tables_exist'] = TRUE;
+
+  // Now that the {users} table exists, the session can be initialized.
+  $install_state['parameters']['bootstrap'] = DRUPAL_BOOTSTRAP_CODE;
+  if ($install_state['interactive']) {
+    return;
+  }
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
 }
 
 /**
@@ -918,16 +961,8 @@ function install_base_system(&$install_state) {
  *   is already installed.
  */
 function install_verify_completed_task() {
-  try {
-    if ($result = db_query("SELECT value FROM {variable} WHERE name = :name", array('name' => 'install_task'))) {
-      $task = unserialize($result->fetchField());
-    }
-  }
-  // Do not trigger an error if the database query fails, since the database
-  // might not be set up yet.
-  catch (Exception $e) {
-  }
-  if (isset($task)) {
+  $task = state()->get('system.install_task');
+  if ($task) {
     if ($task == 'done') {
       throw new Exception(install_already_done_error());
     }
@@ -1103,23 +1138,12 @@ function install_settings_form_submit($form, &$form_state) {
   );
 
   drupal_rewrite_settings($settings);
+  $install_state['database_verified'] = TRUE;
+  $install_state['settings_verified'] = TRUE;
 
   // Add the config directories to settings.php.
   drupal_install_config_directories();
-
-  // We have valid configuration directories in settings.php.
-  // Reset the service container, so the config.storage service will use the
-  // actual active storage for installing configuration.
-  drupal_container(NULL, TRUE);
-
-  // Indicate that the settings file has been verified, and check the database
-  // for the last completed task, now that we have a valid connection. This
-  // last step is important since we want to trigger an error if the new
-  // database already has Drupal installed.
-  $install_state['settings_verified'] = TRUE;
   $install_state['config_verified'] = TRUE;
-  $install_state['database_verified'] = TRUE;
-  $install_state['completed_task'] = install_verify_completed_task();
 }
 
 /**
@@ -1494,23 +1518,6 @@ function install_load_profile(&$install_state) {
 }
 
 /**
- * Performs a full bootstrap of Drupal during installation.
- *
- * @param $install_state
- *   An array of information about the current installation state.
- */
-function install_bootstrap_full(&$install_state) {
-  // Clear the module list that was overriden earlier in the process.
-  // This will allow all freshly installed modules to be loaded.
-  module_list_reset();
-
-  // Instantiate the kernel.
-  $kernel = new DrupalKernel('prod', FALSE, NULL);
-  $kernel->boot();
-  drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
-}
-
-/**
  * Installs required modules via a batch process.
  *
  * @param $install_state
@@ -1643,15 +1650,6 @@ function install_configure_form($form, &$form_state, &$install_state) {
   // work during installation.
   drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail'))), 'setting');
 
-  // Cache a fully-built schema. This is necessary for any invocation of
-  // index.php because: (1) setting cache table entries requires schema
-  // information, (2) that occurs during bootstrap before any module are
-  // loaded, so (3) if there is no cached schema, drupal_get_schema() will
-  // try to generate one but with no loaded modules will return nothing.
-  //
-  // @todo Move this to the 'install_finished' task?
-  drupal_get_schema(NULL, TRUE);
-
   // Return the form.
   return _install_configure_form($form, $form_state, $install_state);
 }
@@ -1695,6 +1693,7 @@ function install_finished(&$install_state) {
   // Flush all caches to ensure that any full bootstraps during the installer
   // do not leave stale cached data, and that any content types or other items
   // registered by the installation profile are registered correctly.
+  // @todo This appears to be unnecessary.
   drupal_flush_all_caches();
 
   drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_distribution_name())), PASS_THROUGH);
@@ -1730,6 +1729,7 @@ function _install_module_batch($module, $module_name, &$context) {
 function _install_profile_modules_finished($success, $results, $operations) {
   // Flush all caches to complete the module installation process. Subsequent
   // installation tasks will now have full access to the profile's modules.
+  // @todo This appears to be unnecessary.
   drupal_flush_all_caches();
 }
 
@@ -2040,5 +2040,5 @@ function install_configure_form_submit($form, &$form_state) {
   user_login_finalize();
 
   // Record when this install ran.
-  variable_set('install_time', $_SERVER['REQUEST_TIME']);
+  state()->set('system.install_time', $_SERVER['REQUEST_TIME']);
 }
diff --git a/core/includes/install.inc b/core/includes/install.inc
index ee501ba..bb21175 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -7,6 +7,7 @@
 
 use Drupal\Core\Database\Database;
 use Drupal\locale\Gettext;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Requirement severity -- Informational message only.
@@ -413,40 +414,68 @@ function drupal_verify_profile($install_state) {
 }
 
 /**
- * Installs the system module.
+ * Installs the System module.
  *
- * Separated from the installation of other modules so core system
- * functions can be made available while other modules are installed.
+ * This is the exact same procedure as in module_enable(). The only difference
+ * is that System module's database schema has been installed in
+ * install_system_schema() already. Due to that, module_enable() can no longer
+ * be used.
+ *
+ * @see install_base_system()
+ * @see install_system_schema()
  */
 function drupal_install_system() {
-  // Create tables.
-  drupal_install_schema('system');
-
-  $system_path = drupal_get_path('module', 'system');
-  require_once DRUPAL_ROOT . '/' . $system_path . '/system.install';
-  $system_versions = drupal_get_schema_versions('system');
-  $system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED;
-  drupal_container()
-    ->get('keyvalue')
-    ->get('system.schema')
-    ->set('system', $system_version);
-
-  // System module needs to be enabled and the system/module lists need to be
-  // reset first in order to allow config_install_default_config() to invoke
-  // config import callbacks.
-  // @todo Installation profiles may override the system.module config object.
-  config('system.module')
-    ->set('enabled.system', 0)
+  $module = 'system';
+
+  $schema_store = drupal_container()->get('keyvalue')->get('system.schema');
+  $module_config = config('system.module');
+  $disabled_config = config('system.module.disabled');
+
+  $module_config
+    ->set("enabled.$module", 0)
     ->save();
+  $disabled_config
+    ->clear($module)
+    ->save();
+  // Load the module's code.
+  drupal_load('module', $module);
 
-  // Clear out module list and hook implementation statics.
+  // Refresh the module list to include it.
   system_list_reset();
-  module_list_reset();
   module_implements_reset();
+  _system_update_bootstrap_status();
+  // Update the kernel to include it.
+  // @todo The if statement is here because install_begin_request() creates
+  //   a container without a kernel. It probably shouldn't.
+  if ($kernel = drupal_container()->get('kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
+    $kernel->updateModules(module_list());
+  }
+
+  // Refresh the schema to include it.
+  drupal_get_schema(NULL, TRUE);
+  // Update the theme registry to include it.
+  drupal_theme_rebuild();
+
+  // Clear the entity info cache before importing new configuration.
+  entity_info_cache_clear();
+
+  // Install default configuration of the module.
+  config_install_default_config('module', $module);
+
+  // Set the schema version to the number of the last update provided.
+  $versions = drupal_get_schema_versions($module);
+  $version = $versions ? max($versions) : SCHEMA_INSTALLED;
+  $schema_store->set($module, $version);
+
+  // Allow the module to perform install tasks.
+  module_invoke($module, 'install');
+
+  watchdog('system', '%module module installed.', array('%module' => $module), WATCHDOG_INFO);
 
-  config_install_default_config('module', 'system');
+  entity_info_cache_clear();
 
-  module_invoke('system', 'install');
+  // Enable the module.
+  module_invoke($module, 'enable');
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
index 1fb87b0..dac7402 100644
--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -145,7 +145,7 @@ public function encode($data) {
    */
   public function decode($raw) {
     $data = @unserialize($raw);
-    return is_array($data) ? $data : FALSE;
+    return is_array($data) ? $data : array();
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index 1e22fdc..11c6dac 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -173,9 +173,10 @@ public function encode($data) {
    */
   public function decode($raw) {
     $data = $this->getParser()->parse($raw);
-    // A simple string is valid YAML for any reason.
+    // A simple string is valid YAML for any reason. Also, a config file may
+    // exist but can be empty, in which case $data is NULL.
     if (!is_array($data)) {
-      return FALSE;
+      return array();
     }
     return $data;
   }
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
index 064146c..f38c87a 100644
--- a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
@@ -39,14 +39,14 @@ function testCRUD() {
     $data = $this->storage->read($name);
     $this->assertIdentical($data, FALSE);
 
-    // Reading a name containing non-decodeable data returns FALSE.
+    // Reading a name containing non-decodeable data returns an array.
     $this->insert($name, '');
     $data = $this->storage->read($name);
-    $this->assertIdentical($data, FALSE);
+    $this->assertIdentical($data, array());
 
     $this->update($name, 'foo');
     $data = $this->storage->read($name);
-    $this->assertIdentical($data, FALSE);
+    $this->assertIdentical($data, array());
 
     $this->delete($name);
 
diff --git a/core/modules/system/config/system.module.disabled.yml b/core/modules/system/config/system.module.disabled.yml
new file mode 100644
index 0000000..e69de29
diff --git a/core/modules/system/config/system.module.yml b/core/modules/system/config/system.module.yml
index 696bedc..dc71340 100644
--- a/core/modules/system/config/system.module.yml
+++ b/core/modules/system/config/system.module.yml
@@ -1,2 +1,6 @@
 enabled:
+  # @todo config_install_default_config() overwrites existing config.
+  #   In case of System module, the installer needs to write this config file,
+  #   before it can install default config. Therefore, system module MUST be
+  #   contained in here already.
   system: '0'
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/StateSystemUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/StateSystemUpgradePathTest.php
index fbd754f..04dfc1d 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/StateSystemUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/StateSystemUpgradePathTest.php
@@ -35,6 +35,19 @@ public function testSystemVariableUpgrade() {
 
     $expected_state = array();
 
+    $expected_state['system.install_time'] = array(
+      'value' => 1304208000,
+      'variable_name' => 'install_time',
+    );
+    $expected_state['system.install_task'] = array(
+      'value' => 'done',
+      'variable_name' => 'install_task',
+    );
+    $expected_state['system.path_alias_whitelist'] = array(
+      'value' => array(
+      ),
+      'variable_name' => 'path_alias_whitelist',
+    );
     $expected_state['node.node_access_needs_rebuild'] = array(
       'value' => TRUE,
       'variable_name' => 'node_access_needs_rebuild',
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 5a96500..9855e68 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -274,7 +274,7 @@ function system_requirements($phase) {
     // Determine when cron last ran.
     $cron_last = variable_get('cron_last');
     if (!is_numeric($cron_last)) {
-      $cron_last = variable_get('install_time', 0);
+      $cron_last = state()->get('system.install_time') ?: 0;
     }
 
     // Determine severity based on time since cron last ran.
@@ -2120,6 +2120,18 @@ function system_update_8025() {
 }
 
 /**
+ * Migrates install_task and install_time variables to State API.
+ *
+ * @ingroup config_upgrade
+ */
+function system_update_8026() {
+  update_variables_to_state(array(
+    'install_task' => 'system.install_task',
+    'install_time' => 'system.install_time',
+  ));
+}
+
+/**
  * Cleans up javascript_parsed variable.
  *
  * @ingroup system_upgrade
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 4670f88..c24fbdc 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -3701,7 +3701,7 @@ function system_run_automated_cron() {
   // If the site is not fully installed, suppress the automated cron run.
   // Otherwise it could be triggered prematurely by Ajax requests during
   // installation.
-  if (($threshold = config('system.cron')->get('threshold.autorun')) > 0 && variable_get('install_task') == 'done') {
+  if (($threshold = config('system.cron')->get('threshold.autorun')) > 0 && state()->get('system.install_task') == 'done') {
     $cron_last = variable_get('cron_last', NULL);
     if (!isset($cron_last) || (REQUEST_TIME - $cron_last > $threshold)) {
       drupal_cron_run();
diff --git a/core/modules/system/tests/upgrade/drupal-7.state.system.database.php b/core/modules/system/tests/upgrade/drupal-7.state.system.database.php
index 47c86f2..eb47ade 100644
--- a/core/modules/system/tests/upgrade/drupal-7.state.system.database.php
+++ b/core/modules/system/tests/upgrade/drupal-7.state.system.database.php
@@ -12,6 +12,9 @@
 
 // Update system settings to known values.
 db_merge('variable')
+  ->key(array('name' => 'install_time'))->fields(array('value' => serialize(1304208000)))
+  ->execute();
+db_merge('variable')
   ->key(array('name' => 'update_last_check'))
   ->fields(array('value' => serialize(1304208000)))
   ->execute();
