diff --git a/core/INSTALL.txt b/core/INSTALL.txt
index cde552d9bf..2f763509bf 100644
--- a/core/INSTALL.txt
+++ b/core/INSTALL.txt
@@ -201,36 +201,40 @@ INSTALLATION
 
    b. Missing settings file.
 
-      Drupal will try to automatically create a settings.php configuration file,
-      which is normally in the directory sites/default (to avoid problems when
-      upgrading, Drupal is not packaged with this file). If auto-creation fails,
-      you will need to create this file yourself, using the file
-      sites/default/default.settings.php as a template.
+      Drupal will try to automatically create settings.php and services.yml
+      files, which are normally in the directory settings (to avoid
+      problems when upgrading, Drupal is not packaged with this file). If
+      auto-creation of either file fails, you will need to create the file
+      yourself. Use the template core/default.settings.php or
+      core/default.services.yml respectively.
 
       For example, on a Unix/Linux command line, you can make a copy of the
       default.settings.php file with the command:
 
-        cp sites/default/default.settings.php sites/default/settings.php
+        cp core/default.settings.php settings/settings.php
+        cp core/default.services.yml settings/services.yml
 
       Next, grant write privileges to the file to everyone (including the web
       server) with the command:
 
-        chmod a+w sites/default/settings.php
+        chmod a+w settings/settings.php
+        chmod a+w settings/services.yml
 
       Be sure to set the permissions back after the installation is finished!
       Sample command:
 
-        chmod go-w sites/default/settings.php
+        chmod go-w settings/settings.php
+        chmod go-w settings/services.yml
 
    c. Write permissions after install.
 
-      The install script will attempt to write-protect the settings.php file and
-      the sites/default directory after saving your configuration. If this
-      fails, you will be notified, and you can do it manually. Sample commands
-      from a Unix/Linux command line:
+      The install script will attempt to write-protect the settings.php file
+      after saving your configuration. If this fails, you will be notified, and
+      you can do it manually. Sample commands from a Unix/Linux command line:
 
-        chmod go-w sites/default/settings.php
-        chmod go-w sites/default
+        chmod go-w settings/settings.php
+        chmod go-w settings/services.yml
+        chmod go-w settings
 
 4. Verify that the site is working.
 
@@ -355,9 +359,9 @@ Drupal can be reinstalled without downloading and extracting the Drupal release.
 
 1. Drop all the tables in your database.
 
-2. Remove everything in sites/default/files.
+2. Remove everything in the files directory.
 
-3. Remove sites/default/settings.php.
+3. Remove settings.php.
 
 4. Follow the Installation Instructions above starting from Step 3 (Run the
    install script).
@@ -391,28 +395,28 @@ MULTISITE CONFIGURATION
 A single Drupal installation can host several Drupal-powered sites, each with
 its own individual configuration.
 
-For this to work you need the file sites/sites.php to exist. Make a copy of
-the example.sites.php file:
+For this to work you need to enable the commented out $sites variable in the
+settings.php in Drupal's root directory, like so:
 
-  $ cp sites/example.sites.php sites/sites.php
+  $sites = array();
 
 Additional site configurations are created in subdirectories within the 'sites'
 directory. Each subdirectory must have a 'settings.php' file, which specifies
 the configuration settings. The easiest way to create additional sites is to
-copy file 'default.settings.php' from the 'sites/default' directory into the
-new site directory with file name 'settings.php' and modify as appropriate.
+copy file 'core/default.settings.php' into the new site directory with file name
+'settings.php' and modify as appropriate.
+
 The new directory name is constructed from the site's URL. The configuration
 for www.example.com could be in 'sites/example.com/settings.php' (note that
 'www.' should be omitted if users can access your site at http://example.com/).
 
-  $ cp sites/default/default.settings.php sites/example.com/settings.php
+  $ cp core/default.settings.php sites/example.com/settings.php
 
 Sites do not have to have a different domain. You can also use subdomains and
 subdirectories for Drupal sites. For example, example.com, sub.example.com, and
 sub.example.com/site3 can all be defined as independent Drupal sites. The setup
 for a configuration such as this would look like the following:
 
-  sites/default/settings.php
   sites/example.com/settings.php
   sites/sub.example.com/settings.php
   sites/sub.example.com.site3/settings.php
@@ -427,7 +431,7 @@ first configuration it finds:
   sites/www.sub.example.com/settings.php
   sites/sub.example.com/settings.php
   sites/example.com/settings.php
-  sites/default/settings.php
+  settings.php
 
 If you are installing on a non-standard port, the port number is treated as the
 deepest subdomain. For example: http://www.example.com:8080/ could be loaded
diff --git a/core/UPGRADE.txt b/core/UPGRADE.txt
new file mode 100644
index 0000000000..a422639a39
--- /dev/null
+++ b/core/UPGRADE.txt
@@ -0,0 +1,130 @@
+INTRODUCTION
+------------
+This document describes how to:
+
+  * Update your Drupal site from one minor 8.x version to another minor 8.x
+    version; for example, from 8.8 to 8.9, or from 8.6 to 8.10.
+
+  * Migrate your Drupal site to version 8.x.
+
+First steps and definitions:
+
+  * If you are upgrading to Drupal version x.y, then x is known as the major
+    version number, and y is known as the minor version number. The download
+    file will be named drupal-x.y.tar.gz (or drupal-x.y.zip).
+
+  * All directories mentioned in this document are relative to the directory of
+    your Drupal installation.
+
+  * Make a full backup of all files, directories, and your database(s) before
+    starting, and save it outside your Drupal installation directory.
+    Instructions may be found at http://drupal.org/upgrade/backing-up-the-db
+
+  * It is wise to try an update or upgrade on a test copy of your site before
+    applying it to your live site. Even minor updates can cause your site's
+    behavior to change.
+
+  * Each new release of Drupal has release notes, which explain the changes made
+    since the previous version and any special instructions needed to update or
+    upgrade to the new version. You can find a link to the release notes for the
+    version you are upgrading or updating to on the Drupal project page
+    (http://drupal.org/project/drupal).
+
+UPGRADE PROBLEMS
+----------------
+If you encounter errors during this process,
+
+  * Note any error messages you see.
+
+  * Restore your site to its previous state, using the file and database backups
+    you created before you started the upgrade process. Do not attempt to do
+    further upgrades on a site that had update problems.
+
+  * Consult one of the support options listed on http://drupal.org/support
+
+More in-depth information on upgrading can be found at http://drupal.org/upgrade
+
+MINOR VERSION UPDATES
+---------------------
+To update from one minor 8.x version of Drupal to any later 8.x version, after
+following the instructions in the INTRODUCTION section at the top of this file:
+
+1. Log in as a user with the permission "Administer software updates".
+
+2. Go to Administration > Configuration > Development > Maintenance mode.
+   Enable the "Put site into maintenance mode" checkbox and save the
+   configuration.
+
+3. Remove all old core files and directories, except for the 'sites' directory
+   and any custom files you added elsewhere.
+
+   If you made modifications to files like .htaccess or robots.txt, you will
+   need to re-apply them from your backup, after the new files are in place.
+
+   Sometimes an update includes changes to core/default.settings.php (this will
+   be noted in the release notes). If that's the case, follow these steps:
+
+   - Make a backup copy of your settings.php file, with a different file name.
+
+   - Make a copy of the new default.settings.php file, and name the copy
+     settings.php (overwriting your previous settings.php file).
+
+   - Copy the custom and site-specific entries from the backup you made into the
+     new settings.php file. You will definitely need the lines giving the
+     database information, and you will also want to copy in any other
+     customizations you have added.
+
+4. Download the latest Drupal 8.x release from http://drupal.org to a
+   directory outside of your web root. Extract the archive and copy the files
+   into your Drupal directory.
+
+   On a typical Unix/Linux command line, use the following commands to download
+   and extract:
+
+     wget http://drupal.org/files/projects/drupal-x.y.tar.gz
+     tar -zxvf drupal-x.y.tar.gz
+
+   This creates a new directory drupal-x.y/ containing all Drupal files and
+   directories. Copy the files into your Drupal installation directory:
+
+     cp -R drupal-x.y/* drupal-x.y/.htaccess /path/to/your/installation
+
+   If you do not have command line access to your server, download the archive
+   from http://drupal.org using your web browser, extract it, and then use an
+   FTP client to upload the files to your web root.
+
+5. Re-apply any modifications to files such as .htaccess or robots.txt.
+
+6. Run update.php by visiting http://www.example.com/core/update.php (replace
+   www.example.com with your domain name). This will update the core database
+   tables.
+
+   If you are unable to access update.php do the following:
+
+   - Open settings.php with a text editor.
+
+   - Find the line that says:
+     $settings['update_free_access'] = FALSE;
+
+   - Change it into:
+     $settings['update_free_access'] = TRUE;
+
+   - Once the upgrade is done, $settings['update_free_access'] must be
+     reverted to FALSE.
+
+7. Go to Administration > Reports > Status report. Verify that everything is
+   working as expected.
+
+8. Ensure that $settings['update_free_access'] is FALSE in settings.php.
+
+9. Go to Administration > Configuration > Development > Maintenance mode.
+   Disable the "Put site into maintenance mode" checkbox and save the
+   configuration.
+
+MAJOR VERSION MIGRATION
+-----------------------
+Upgrading from a prior major version of Drupal to Drupal 8.x is not possible.
+The process now requires a migration to a Drupal 8.x site, utilizing the Migrate
+module in Drupal core.
+
+Note that migration support in Drupal 8 is currently only partially implemented.
diff --git a/sites/default/default.services.yml b/core/default.services.yml
similarity index 100%
rename from sites/default/default.services.yml
rename to core/default.services.yml
diff --git a/sites/default/default.settings.php b/core/default.settings.php
similarity index 100%
rename from sites/default/default.settings.php
rename to core/default.settings.php
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 9563facf28..7ba3e7cfe9 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -13,6 +13,8 @@
 use Drupal\Core\Logger\RfcLogLevel;
 use Drupal\Core\Test\TestDatabase;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\Site\Site;
 use Drupal\Core\Utility\Error;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
@@ -182,6 +184,26 @@
  */
 define('DRUPAL_ROOT', dirname(dirname(__DIR__)));
 
+/**
+ * Returns the appropriate configuration directory.
+ *
+ * Temporary backwards-compatibility layer for Drush.
+ *
+ * @deprecated 8.x-dev
+ *   Use \Drupal\Core\Site\Site::getPath() instead.
+ *
+ * @internal
+ */
+function conf_path() {
+  if (strpos(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)[1]['function'], 'drush') === FALSE) {
+    throw new \Exception('conf_path() is retained for Drush compatibility only.');
+  }
+  // When called from _drush_bootstrap_drupal_site_validate() on testbots, this
+  // function is supposed to return the site path of the parent site, which is
+  // '' (the document root). Ensure that the returned path is relative.
+  // @see https://github.com/drush-ops/drush/blob/master/includes/bootstrap.inc#L782
+  return '.';
+}
 /**
  * Returns the path of a configuration directory.
  *
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 48cc8fe50f..68114a6815 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -25,6 +25,7 @@
 use Drupal\Core\Language\LanguageManager;
 use Drupal\Core\Logger\LoggerChannelFactory;
 use Drupal\Core\Site\Settings;
+use Drupal\Core\Site\Site;
 use Drupal\Core\StringTranslation\Translator\FileTranslation;
 use Drupal\Core\StackMiddleware\ReverseProxyMiddleware;
 use Drupal\Core\StreamWrapper\PublicStream;
@@ -338,6 +339,12 @@ function install_begin_request($class_loader, &$install_state) {
   $site_path = empty($install_state['site_path']) ? DrupalKernel::findSitePath($request, FALSE) : $install_state['site_path'];
   Settings::initialize(dirname(dirname(__DIR__)), $site_path, $class_loader);
 
+  // Initialize the site directory.
+  // This primes the site path to be used during installation, to allow an empty
+  // site folder to be prepared in the /sites directory into which Drupal will
+  // be installed.
+  Site::initInstaller(DRUPAL_ROOT);
+
   // Ensure that procedural dependencies are loaded as early as possible,
   // since the error/exception handlers depend on them.
   require_once __DIR__ . '/../modules/system/system.install';
@@ -442,7 +449,7 @@ function install_begin_request($class_loader, &$install_state) {
     $directory = $GLOBALS['config']['locale.settings']['translation']['path'];
   }
   else {
-    $directory = $site_path . '/files/translations';
+    $directory = Site::getPath('files/translations');
   }
   $container->set('string_translator.file_translation', new FileTranslation($directory));
   $container->get('string_translation')
@@ -1174,8 +1181,7 @@ function install_verify_completed_task() {
 function install_verify_database_settings($site_path) {
   if ($database = Database::getConnectionInfo()) {
     $database = $database['default'];
-    $settings_file = './' . $site_path . '/settings.php';
-    $errors = install_database_errors($database, $settings_file);
+    $errors = install_database_errors($database);
     if (empty($errors)) {
       return TRUE;
     }
@@ -1911,9 +1917,8 @@ function install_check_translations($langcode, $server_pattern) {
   $readable = FALSE;
   $writable = FALSE;
   // @todo: Make this configurable.
-  $site_path = \Drupal::service('site.path');
-  $files_directory = $site_path . '/files';
-  $translations_directory = $site_path . '/files/translations';
+  $files_directory = Site::getPath('files');
+  $translations_directory = Site::getPath('files/translations');
   $translations_directory_exists = FALSE;
   $online = FALSE;
 
@@ -2090,8 +2095,9 @@ function install_check_requirements($install_state) {
     $readable = FALSE;
     $writable = FALSE;
     $site_path = './' . \Drupal::service('site.path');
-    $file = $site_path . "/{$default_file_info['file']}";
-    $default_file = "./sites/default/{$default_file_info['file_default']}";
+    $conf_path = './' . conf_path(FALSE);
+    $file = $conf_path . "/{$default_file_info['file']}";
+    $default_file = "./core/{$default_file_info['file_default']}";
     $exists = FALSE;
     // Verify that the directory exists.
     if (drupal_verify_install_file($site_path, FILE_EXIST, 'dir')) {
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 5ed430342a..62b46be946 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -15,6 +15,7 @@
 use Drupal\Core\Extension\ExtensionDiscovery;
 use Drupal\Core\Extension\ModuleHandler;
 use Drupal\Core\Site\Settings;
+use Drupal\Core\Site\Site;
 
 /**
  * Requirement severity -- Informational message only.
@@ -219,7 +220,7 @@ function drupal_get_database_types() {
  */
 function drupal_rewrite_settings($settings = [], $settings_file = NULL) {
   if (!isset($settings_file)) {
-    $settings_file = \Drupal::service('site.path') . '/settings.php';
+    $settings_file = Site::getPath('settings.php');
   }
   // Build list of setting names and insert the values into the global namespace.
   $variable_names = [];
@@ -678,6 +679,9 @@ function drupal_install_system($install_state) {
  *
  * @return
  *   TRUE on success or FALSE on failure. A message is set for the latter.
+ *
+ * @todo Do not automatically call into drupal_install_fix_file().
+ * @see https://drupal.org/node/1616266
  */
 function drupal_verify_install_file($file, $mask = NULL, $type = 'file', $autofix = TRUE) {
   $return = TRUE;
diff --git a/core/includes/update.inc b/core/includes/update.inc
index 2021a66231..6629ed9b98 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -10,6 +10,11 @@
 
 use Drupal\Component\Graph\Graph;
 use Drupal\Core\Update\UpdateKernel;
+use Drupal\Component\Utility\String;
+use Drupal\Core\Config\FileStorage;
+use Drupal\Core\Config\ConfigException;
+use Drupal\Core\Page\DefaultHtmlPageRenderer;
+use Drupal\Core\Site\Site;
 use Drupal\Core\Utility\Error;
 
 /**
@@ -66,6 +71,34 @@ function update_check_incompatibility($name, $type = 'module') {
   return FALSE;
 }
 
+/**
+ * Returns whether the settings file requirement has been satisfied.
+ *
+ * @return array
+ *  A requirements info array.
+ */
+function update_settings_file_requirements() {
+  $requirements = array();
+
+  // Check whether settings.php needs to be rewritten.
+  $settings_file = Site::getPath('settings.php');
+  $writable = drupal_verify_install_file($settings_file, FILE_EXIST | FILE_READABLE | FILE_WRITABLE);
+  $requirements['settings file']['title'] = 'Settings file';
+  if ($writable) {
+    $requirements['settings file'] += array(
+      'value' => 'settings.php is writable.',
+    );
+  }
+  else {
+    $requirements['settings file'] += array(
+      'value' => 'settings.php is not writable.',
+      'severity' => REQUIREMENT_ERROR,
+      'description' => 'Drupal requires write permissions to <em>' . $settings_file . '</em> during the update process. If you are unsure how to grant file permissions, consult the <a href="http://drupal.org/server-permissions">online handbook</a>.',
+    );
+  }
+  return $requirements;
+}
+
 /**
  * Returns whether the minimum schema requirement has been satisfied.
  *
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php
index dacf5b99fb..ec95bd6593 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Database\Driver\sqlite\Connection;
 use Drupal\Core\Database\DatabaseNotFoundException;
 use Drupal\Core\Database\Install\Tasks as InstallTasks;
+use Drupal\Core\Site\Site;
 
 /**
  * Specifies installation tasks for SQLite databases.
diff --git a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php
index 09d866f288..dcff2a8c30 100644
--- a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php
+++ b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Locale\CountryManagerInterface;
 use Drupal\Core\Site\Settings;
+use Drupal\Core\Site\Site;
 use Drupal\Core\State\StateInterface;
 use Drupal\user\UserStorageInterface;
 use Drupal\user\UserInterface;
@@ -128,6 +129,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     // Warn about settings.php permissions risk
     $settings_dir = $this->sitePath;
     $settings_file = $settings_dir . '/settings.php';
+
     // Check that $_POST is empty so we only show this message when the form is
     // first displayed, not on the next page after it is submitted. (We do not
     // want to repeat it multiple times because it is a general warning that is
diff --git a/core/lib/Drupal/Core/Site/Settings.php b/core/lib/Drupal/Core/Site/Settings.php
index c6fbed659f..5f6f4f9dc4 100644
--- a/core/lib/Drupal/Core/Site/Settings.php
+++ b/core/lib/Drupal/Core/Site/Settings.php
@@ -121,8 +121,24 @@ public static function initialize($app_root, $site_path, &$class_loader) {
     $config = [];
     $databases = [];
 
-    if (is_readable($app_root . '/' . $site_path . '/settings.php')) {
-      require $app_root . '/' . $site_path . '/settings.php';
+    // Read the global /settings/settings.php file.
+    // Allow it to set/override the following variables:
+    $sites = NULL;
+    $conf_path = NULL;
+    // Exclude it for test requests to prevent settings of the test runner from
+    // leaking into the test environment.
+    if (!drupal_valid_test_ua() && is_readable(DRUPAL_ROOT . '/settings/settings.php')) {
+      require DRUPAL_ROOT . '/settings/settings.php';
+    }
+
+    // Discover the site directory.
+    Site::init(DRUPAL_ROOT, $sites, $conf_path);
+
+    // Read settings.php of the actual site, unless it is the root/default site.
+    // Concatenation is safe here, since $conf_path is known to be not empty.
+    $conf_path = Site::getPath();
+    if ($conf_path !== '' && is_readable(DRUPAL_ROOT . '/' . $conf_path . '/settings.php')) {
+      require DRUPAL_ROOT . '/' . $conf_path . '/settings.php';
     }
 
     // Initialize Database.
diff --git a/core/lib/Drupal/Core/Site/Site.php b/core/lib/Drupal/Core/Site/Site.php
new file mode 100644
index 0000000000..916abd9406
--- /dev/null
+++ b/core/lib/Drupal/Core/Site/Site.php
@@ -0,0 +1,378 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Site\Site.
+ */
+
+namespace Drupal\Core\Site;
+
+/**
+ * A utility class for easy access to the site path.
+ */
+class Site {
+
+  /**
+   * The absolute path to the Drupal root directory.
+   *
+   * @var string
+   */
+  private $root;
+
+  /**
+   * The relative path to the site directory.
+   *
+   * May be an empty string, in case the site directory is the root directory.
+   *
+   * @var string
+   */
+  private $path;
+
+  /**
+   * Whether the Site singleton was instantiated by the installer.
+   *
+   * @var bool
+   */
+  private $isInstaller = FALSE;
+
+  /**
+   * The original Site instance of the test runner during test execution.
+   *
+   * @see \Drupal\Core\Site\Site::setUpTest()
+   * @see \Drupal\Core\Site\Site::tearDownTest()
+   *
+   * @var \Drupal\Core\Site\Site
+   */
+  private static $original;
+
+  /**
+   * The Site singleton instance.
+   *
+   * @var \Drupal\Core\Site\Site
+   */
+  private static $instance;
+
+  /**
+   * Initializes the Site singleton.
+   *
+   * @param string $root_directory
+   *   The root directory to use for absolute paths; i.e., DRUPAL_ROOT.
+   * @param array|null $sites
+   *   (optional) A multi-site mapping, as defined in settings.php. An empty
+   *   array is sufficient to enable discovery of a request-specific site
+   *   directory. If NULL and no $custom_path is passed, the root directory will
+   *   be the site directory.
+   * @param string|null $custom_path
+   *   (optional) An explicit site path to set; skipping site negotiation.
+   *   This can be defined as $conf_path in the root /settings.php file.
+   *
+   * @throws \BadMethodCallException
+   *   If the site path is initialized already.
+   *
+   * @see drupal_settings_initialize()
+   */
+  public static function init($root_directory, array $sites = NULL, $custom_path = NULL) {
+    if (isset(self::$instance)) {
+      // Only the installer environment is allowed to instantiate the Site
+      // singleton prior to drupal_settings_initialize().
+      // @see Site::initInstaller()
+      if (!self::$instance->isInstaller()) {
+        throw new \BadMethodCallException('Site path is initialized already.');
+      }
+    }
+    else {
+      new self($root_directory);
+    }
+    self::$instance->initializePath($sites, $custom_path);
+
+    // Prevent this method from being called more than once.
+    if (self::$instance->isInstaller()) {
+      self::$instance->isInstaller = FALSE;
+    }
+  }
+
+  /**
+   * Initializes the Site singleton in the early installer environment.
+   *
+   * Primes the Site singleton with an installer flag, which allows the
+   * application to be installed into a new and empty site directory, which does
+   * not contain a settings.php yet.
+   *
+   * @param string $root_directory
+   *   The root directory to use for absolute paths; i.e., DRUPAL_ROOT.
+   *
+   * @throws \BadMethodCallException
+   *   If the site path is initialized already.
+   *
+   * @see install_begin_request()
+   */
+  public static function initInstaller($root_directory) {
+    if (isset(self::$instance)) {
+      throw new \BadMethodCallException('Site path is initialized already.');
+    }
+    new self($root_directory);
+    // Denote that we are operating in the special installer environment.
+    self::$instance->isInstaller = TRUE;
+  }
+
+  /**
+   * Constructs the Site singleton.
+   *
+   * @throws \BadMethodCallException
+   *   If the site path is initialized already.
+   */
+  private function __construct($root_directory) {
+    if (isset(self::$instance)) {
+      throw new \BadMethodCallException('Site path is initialized already.');
+    }
+    $this->root = $root_directory;
+    self::$instance = $this;
+  }
+
+  /**
+   * Re-initializes (resets) the Site singleton for a test run.
+   *
+   * @throws \RuntimeException
+   *   If the site path of the test runner is not initialized yet.
+   *
+   * @throws \BadMethodCallException
+   *   If no test is executed currently.
+   *
+   * @see \Drupal\simpletest\TestBase::prepareEnvironment()
+   */
+  public static function setUpTest() {
+    if (!isset(self::$instance)) {
+      throw new \RuntimeException('No original Site to backup. Missing invocation of Site::init()?');
+    }
+    if (!drupal_valid_test_ua()) {
+      throw new \BadMethodCallException('Site is not executing a test.');
+    }
+    self::$original = clone self::$instance;
+    self::$instance = NULL;
+  }
+
+  /**
+   * Reverts the Site singleton to the original after a test run.
+   *
+   * @throws \RuntimeException
+   *   If there is no test runner site path to revert to.
+   *
+   * @throws \BadMethodCallException
+   *   If a test is still being executed currently.
+   *
+   * @see \Drupal\simpletest\TestBase::restoreEnvironment()
+   */
+  public static function tearDownTest() {
+    if (!isset(self::$original)) {
+      throw new \RuntimeException('No original Site to revert to. Missing invocation of Site::setUpTest()?');
+    }
+    // Do not allow to restore original Site singleton in a test environment,
+    // unless we are testing the test environment setup and teardown itself.
+    // @see \Drupal\simpletest\Tests\BrokenSetUpTest
+    if (drupal_valid_test_ua() && !DRUPAL_TEST_IN_CHILD_SITE) {
+      throw new \BadMethodCallException('Unable to revert Site: A test is still being executed.');
+    }
+    self::$instance = clone self::$original;
+    self::$original = NULL;
+  }
+
+  /**
+   * Returns whether the Site singleton was instantiated for the installer.
+   *
+   * @return bool
+   */
+  private function isInstaller() {
+    return $this->isInstaller;
+  }
+
+  /**
+   * Initializes the site path.
+   *
+   * @param array|null $sites
+   *   (optional) A multi-site mapping, as defined in settings.php.
+   * @param string|null $custom_path
+   *   (optional) An explicit site path to set; skipping site negotiation.
+   */
+  private function initializePath(array $sites = NULL, $custom_path = NULL) {
+    // Force-override the site directory in tests.
+    if ($test_prefix = drupal_valid_test_ua()) {
+      $this->path = 'sites/simpletest/' . substr($test_prefix, 10);
+    }
+    // An explicitly defined $conf_path in /settings.php takes precedence.
+    elseif (isset($custom_path)) {
+      $this->path = $custom_path;
+    }
+    // If the multi-site functionality was enabled in /settings.php, discover
+    // the path for the current site.
+    // $sites just needs to be defined; an explicit mapping is not required.
+    elseif (isset($sites)) {
+      $this->path = $this->determinePath($sites, !$this->isInstaller());
+    }
+    // Otherwise, the Drupal root directory is the site directory.
+    else {
+      $this->path = '';
+    }
+  }
+
+  /**
+   * Finds the appropriate configuration directory for a given host and path.
+   *
+   * Finds a matching configuration directory file by stripping the server
+   * hostname from left to right and pathname from right to left.
+   *
+   * By default, the site directory must contain a 'settings.php' file. If the
+   * parameter $require_settings is set to FALSE, then a site directory without
+   * a 'settings.php' file will be valid, too. The first valid site directory
+   * found will be used and less specific options will be ignored.
+   *
+   * The $sites variable can define site directory aliases as an associative
+   * array. For example, to create a directory alias for
+   * http://www.drupal.org:8080/mysite/test whose configuration file is in
+   * sites/example.com, the array should be defined as:
+   *
+   * @code
+   * $sites['8080.www.drupal.org.mysite.test'] = 'example.com';
+   * @endcode
+   *
+   * @see default.settings.php
+   *
+   * @param array $sites
+   *   A multi-site alias mapping, as defined in settings.php. May be empty.
+   * @param bool $require_settings
+   *   If TRUE, only site directories containing a settings.php file will be
+   *   recognized. During installation, this is set to FALSE, so that Drupal can
+   *   detect a site directory in which to create a new settings.php file.
+   *
+   * @return string
+   *   The relative path of the site directory. May be an empty string, in case
+   *   no site-specific directory matched, in which case the root directory is
+   *   the site directory.
+   */
+  private function determinePath(array $sites, $require_settings) {
+    // The hostname and optional port number, e.g. "www.example.com" or
+    // "www.example.com:8080".
+    $http_host = $_SERVER['HTTP_HOST'];
+    // The part of the URL following the hostname, including the leading slash.
+    $script_name = $_SERVER['SCRIPT_NAME'] ?: $_SERVER['SCRIPT_FILENAME'];
+
+    $uri = explode('/', $script_name);
+    $server = explode('.', implode('.', array_reverse(explode(':', rtrim($http_host, '.')))));
+    for ($i = count($uri) - 1; $i > 0; $i--) {
+      for ($j = count($server); $j > 0; $j--) {
+        $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
+        // Check for an alias in $sites from settings.php.
+        if (isset($sites[$dir])) {
+          $dir = $sites[$dir];
+        }
+        if ($require_settings) {
+          if (file_exists($this->root . '/sites/' . $dir . '/settings.php')) {
+            return "sites/$dir";
+          }
+        }
+        elseif (file_exists($this->root . '/sites/' . $dir)) {
+          return "sites/$dir";
+        }
+      }
+    }
+    return '';
+  }
+
+  /**
+   * Prefixes a given subpathname with the site directory, if any.
+   *
+   * Site::getPath() and this helper method only exists to ensure that a given
+   * subpathname does not result in an absolute filesystem path in case of a
+   * string concatenation like the following:
+   *
+   * @code
+   * // If the sire directory path is empty (root directory), then the resulting
+   * // filesystem path would become absolute; i.e.: "/some/file"
+   * unlink($site_path . '/some/file');
+   * @endcode
+   *
+   * In case the PHP process has write access to the entire filesystem, such a
+   * file operation could succeed and potentially affect arbitrary other files
+   * and directories that happen to exist. That must not happen.
+   *
+   * @param string $subpathname
+   *   The subpathname to prefix.
+   *
+   * @return string
+   *   The prefixed subpathname.
+   *
+   * @throws \RuntimeException
+   *   If the site path is not initialized yet.
+   */
+  private function resolvePath($subpathname) {
+    // Extra safety protection in case a script somehow manages to bypass all
+    // other protections.
+    if (!isset($this->path)) {
+      throw new \RuntimeException('Site path is not initialized yet.');
+    }
+    // A faulty call to Site::getPath() might include a leading slash (/), in
+    // which case the entire site path resolution of this function would be
+    // pointless, because the resulting path would still be absolute. Therefore,
+    // guarantee that even a bogus argument is resolved correctly.
+    $subpathname = ltrim($subpathname, '/');
+
+    if ($this->path !== '') {
+      if ($subpathname !== '') {
+        return $this->path . '/' . $subpathname;
+      }
+      return $this->path;
+    }
+    return $subpathname;
+  }
+
+  /**
+   * Returns the given path relative to the site directory.
+   *
+   * Use this function instead of appending strings to the site path manually,
+   * because the site directory may be the root directory and thus a
+   * concatenated path would result in an absolute filesystem path.
+   *
+   * @param string $subpathname
+   *   (optional) A relative subpathname to append to the site path.
+   *
+   * @return string
+   *   The given $subpathname, potentially prefixed with the site path.
+   *
+   * @throws \RuntimeException
+   *   If the site path is not initialized yet.
+   *
+   * @see \Drupal\Core\Site\Site::getAbsolutePath()
+   */
+  public static function getPath($subpathname = '') {
+    return self::$instance->resolvePath($subpathname);
+  }
+
+  /**
+   * Returns the given path relative to the site directory, as an absolute path.
+   *
+   * Use this function instead of appending strings to the site path manually,
+   * because the site directory may be the root directory and thus a
+   * concatenated path would result in an absolute filesystem path.
+   *
+   * @param string $subpathname
+   *   (optional) A relative subpathname to append to the site path.
+   *
+   * @return string
+   *   The given $subpathname, potentially prefixed with the site path, as an
+   *   absolute filesystem path.
+   *
+   * @throws \RuntimeException
+   *   If the site path is not initialized yet.
+   *
+   * @see \Drupal\Core\Site\Site::getPath()
+   */
+  public static function getAbsolutePath($subpathname = '') {
+    $subpathname = self::$instance->resolvePath($subpathname);
+    if ($subpathname !== '') {
+      return self::$instance->root . '/' . $subpathname;
+    }
+    else {
+      return self::$instance->root;
+    }
+  }
+
+}
diff --git a/core/modules/file/tests/file_test/src/StreamWrapper/DummyReadOnlyStreamWrapper.php b/core/modules/file/tests/file_test/src/StreamWrapper/DummyReadOnlyStreamWrapper.php
index c1c36af413..91f0e80cf4 100644
--- a/core/modules/file/tests/file_test/src/StreamWrapper/DummyReadOnlyStreamWrapper.php
+++ b/core/modules/file/tests/file_test/src/StreamWrapper/DummyReadOnlyStreamWrapper.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\file_test\StreamWrapper;
 
+use Drupal\Core\Site\Site;
 use Drupal\Core\StreamWrapper\LocalReadOnlyStream;
 
 /**
diff --git a/core/modules/file/tests/file_test/src/StreamWrapper/DummyStreamWrapper.php b/core/modules/file/tests/file_test/src/StreamWrapper/DummyStreamWrapper.php
index 6ce842470f..4ec2612107 100644
--- a/core/modules/file/tests/file_test/src/StreamWrapper/DummyStreamWrapper.php
+++ b/core/modules/file/tests/file_test/src/StreamWrapper/DummyStreamWrapper.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\file_test\StreamWrapper;
 
+use Drupal\Core\Site\Site;
 use Drupal\Core\StreamWrapper\LocalStream;
 
 /**
diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php
index 9af30b70e2..91467b9338 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Site\Settings;
+use Drupal\Core\Site\Site;
 use Drupal\Core\StreamWrapper\PublicStream;
 use Drupal\Core\Test\TestDatabase;
 use Drupal\Core\Test\TestSetupTrait;
@@ -1170,7 +1171,11 @@ private function prepareEnvironment() {
 
     // After preparing the environment and changing the database prefix, we are
     // in a valid test environment.
+    if (!is_dir(DRUPAL_ROOT . '/' . $this->siteDirectory)) {
+      throw new \RuntimeException("Test site directory '$this->siteDirectory' does not exist.");
+    }
     drupal_valid_test_ua($this->databasePrefix);
+    Site::setUpTest();
 
     // Reset settings.
     new Settings([
@@ -1284,6 +1289,11 @@ private function restoreEnvironment() {
       drupal_valid_test_ua(FALSE);
     }
 
+    Site::tearDownTest();
+
+    // Restore stream wrappers of the test runner.
+    file_get_stream_wrappers();
+
     // Restore original shutdown callbacks.
     $callbacks = &drupal_register_shutdown_function();
     $callbacks = $this->originalShutdownCallbacks;
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 54d64e7856..cedc5edd58 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -395,6 +395,248 @@ protected function setUp() {
     $this->rebuildAll();
   }
 
+  /**
+   * Execute the non-interactive installer.
+   *
+   * @see install_drupal()
+   */
+  protected function doInstall() {
+    require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
+    install_drupal($this->classLoader, $this->installParameters());
+  }
+
+  /**
+   * Prepares site settings and services before installation.
+   */
+  protected function prepareSettings() {
+    // Prepare installer settings that are not install_drupal() parameters.
+    // Copy and prepare an actual settings.php, so as to resemble a regular
+    // installation.
+    // Not using File API; a potential error must trigger a PHP warning.
+    $directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
+    copy(DRUPAL_ROOT . '/core/default.settings.php', $directory . '/settings.php');
+    copy(DRUPAL_ROOT . '/core/default.services.yml', $directory . '/services.yml');
+
+    // All file system paths are created by System module during installation.
+    // @see system_requirements()
+    // @see TestBase::prepareEnvironment()
+    $settings['settings']['file_public_path'] = (object) [
+      'value' => $this->publicFilesDirectory,
+      'required' => TRUE,
+    ];
+    $settings['settings']['file_private_path'] = (object) [
+      'value' => $this->privateFilesDirectory,
+      'required' => TRUE,
+    ];
+    // Save the original site directory path, so that extensions in the
+    // site-specific directory can still be discovered in the test site
+    // environment.
+    // @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
+    $settings['settings']['test_parent_site'] = (object) [
+      'value' => $this->originalSite,
+      'required' => TRUE,
+    ];
+    // Add the parent profile's search path to the child site's search paths.
+    // @see \Drupal\Core\Extension\ExtensionDiscovery::getProfileDirectories()
+    $settings['conf']['simpletest.settings']['parent_profile'] = (object) [
+      'value' => $this->originalProfile,
+      'required' => TRUE,
+    ];
+    // Write a new settings.php for the test site environment.
+    // drupal_rewrite_settings() will reload Settings and update
+    // $GLOBALS['config'] accordingly.
+    $this->writeSettings($settings);
+    // Allow for test-specific overrides.
+    $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/settings.testing.php';
+    if (file_exists($settings_testing_file)) {
+      // Copy the testing-specific settings.php overrides in place.
+      copy($settings_testing_file, $directory . '/settings.testing.php');
+      // Add the name of the testing class to settings.php and include the
+      // testing specific overrides
+      file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) . "';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . "\n", FILE_APPEND);
+    }
+    $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSite . '/testing.services.yml';
+    if (!file_exists($settings_services_file)) {
+      // Otherwise, use the default services as a starting point for overrides.
+      $settings_services_file = DRUPAL_ROOT . '/sites/default/default.services.yml';
+    }
+    // Copy the testing-specific service overrides in place.
+    copy($settings_services_file, $directory . '/services.yml');
+    if ($this->strictConfigSchema) {
+      // Add a listener to validate configuration schema on save.
+      $yaml = new SymfonyYaml();
+      $content = file_get_contents($directory . '/services.yml');
+      $services = $yaml->parse($content);
+      $services['services']['simpletest.config_schema_checker'] = [
+        'class' => 'Drupal\Core\Config\Testing\ConfigSchemaChecker',
+        'arguments' => ['@config.typed', $this->getConfigSchemaExclusions()],
+        'tags' => [['name' => 'event_subscriber']]
+      ];
+      file_put_contents($directory . '/services.yml', $yaml->dump($services));
+    }
+    // Since Drupal is bootstrapped already, install_begin_request() will not
+    // bootstrap again. Hence, we have to reload the newly written custom
+    // settings.php manually.
+    Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $this->classLoader);
+  }
+
+  /**
+   * Initialize settings created during install.
+   */
+  protected function initSettings() {
+    Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $this->classLoader);
+    foreach ($GLOBALS['config_directories'] as $type => $path) {
+      $this->configDirectories[$type] = $path;
+    }
+
+    // After writing settings.php, the installer removes write permissions
+    // from the site directory. To allow drupal_generate_test_ua() to write
+    // a file containing the private key for drupal_valid_test_ua(), the site
+    // directory has to be writable.
+    // TestBase::restoreEnvironment() will delete the entire site directory.
+    // Not using File API; a potential error must trigger a PHP warning.
+    chmod(DRUPAL_ROOT . '/' . $this->siteDirectory, 0777);
+    chmod(DRUPAL_ROOT . '/' . $this->siteDirectory . '/settings.php', 0666);
+
+    // During tests, cacheable responses should get the debugging cacheability
+    // headers by default.
+    $this->setContainerParameter('http.response.debug_cacheability_headers', TRUE);
+  }
+
+  /**
+   * Initialize various configurations post-installation.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The container.
+   */
+  protected function initConfig(ContainerInterface $container) {
+    $config = $container->get('config.factory');
+
+    // Manually create and configure private and temporary files directories.
+    // While these could be preset/enforced in settings.php like the public
+    // files directory above, some tests expect them to be configurable in the
+    // UI. If declared in settings.php, they would no longer be configurable.
+    file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
+    file_prepare_directory($this->tempFilesDirectory, FILE_CREATE_DIRECTORY);
+    $config->getEditable('system.file')
+      ->set('path.temporary', $this->tempFilesDirectory)
+      ->save();
+
+    // Manually configure the test mail collector implementation to prevent
+    // tests from sending out emails and collect them in state instead.
+    // While this should be enforced via settings.php prior to installation,
+    // some tests expect to be able to test mail system implementations.
+    $config->getEditable('system.mail')
+      ->set('interface.default', 'test_mail_collector')
+      ->save();
+
+    // By default, verbosely display all errors and disable all production
+    // environment optimizations for all tests to avoid needless overhead and
+    // ensure a sane default experience for test authors.
+    // @see https://www.drupal.org/node/2259167
+    $config->getEditable('system.logging')
+      ->set('error_level', 'verbose')
+      ->save();
+    $config->getEditable('system.performance')
+      ->set('css.preprocess', FALSE)
+      ->set('js.preprocess', FALSE)
+      ->save();
+
+    // Set an explicit time zone to not rely on the system one, which may vary
+    // from setup to setup. The Australia/Sydney time zone is chosen so all
+    // tests are run using an edge case scenario (UTC+10 and DST). This choice
+    // is made to prevent time zone related regressions and reduce the
+    // fragility of the testing system in general.
+    $config->getEditable('system.date')
+      ->set('timezone.default', 'Australia/Sydney')
+      ->save();
+  }
+
+  /**
+   * Reset and rebuild the environment after setup.
+   */
+  protected function rebuildAll() {
+    // Reset/rebuild all data structures after enabling the modules, primarily
+    // to synchronize all data structures and caches between the test runner and
+    // the child site.
+    // @see \Drupal\Core\DrupalKernel::bootCode()
+    // @todo Test-specific setUp() methods may set up further fixtures; find a
+    //   way to execute this after setUp() is done, or to eliminate it entirely.
+    $this->resetAll();
+    $this->kernel->prepareLegacyRequest(\Drupal::request());
+
+    // Explicitly call register() again on the container registered in \Drupal.
+    // @todo This should already be called through
+    //   DrupalKernel::prepareLegacyRequest() -> DrupalKernel::boot() but that
+    //   appears to be calling a different container.
+    $this->container->get('stream_wrapper_manager')->register();
+  }
+
+  /**
+   * Returns the parameters that will be used when Simpletest installs Drupal.
+   *
+   * @see install_drupal()
+   * @see install_state_defaults()
+   *
+   * @return array
+   *   Array of parameters for use in install_drupal().
+   */
+  protected function installParameters() {
+    $connection_info = Database::getConnectionInfo();
+    $driver = $connection_info['default']['driver'];
+    $connection_info['default']['prefix'] = $connection_info['default']['prefix']['default'];
+    unset($connection_info['default']['driver']);
+    unset($connection_info['default']['namespace']);
+    unset($connection_info['default']['pdo']);
+    unset($connection_info['default']['init_commands']);
+    // Remove database connection info that is not used by SQLite.
+    if ($driver == 'sqlite') {
+      unset($connection_info['default']['username']);
+      unset($connection_info['default']['password']);
+      unset($connection_info['default']['host']);
+      unset($connection_info['default']['port']);
+    }
+    $parameters = array(
+      'interactive' => FALSE,
+      'parameters' => array(
+        'profile' => $this->profile,
+        'langcode' => 'en',
+      ),
+      'forms' => array(
+        'install_settings_form' => array(
+          'driver' => $driver,
+          $driver => $connection_info['default'],
+        ),
+        'install_configure_form' => array(
+          'site_name' => 'Drupal',
+          'site_mail' => 'simpletest@example.com',
+          'account' => array(
+            'name' => $this->rootUser->name,
+            'mail' => $this->rootUser->getEmail(),
+            'pass' => array(
+              'pass1' => $this->rootUser->pass_raw,
+              'pass2' => $this->rootUser->pass_raw,
+            ),
+          ),
+          // \Drupal\Core\Render\Element\Checkboxes::valueCallback() requires
+          // NULL instead of FALSE values for programmatic form submissions to
+          // disable a checkbox.
+          'update_status_module' => array(
+            1 => NULL,
+            2 => NULL,
+          ),
+        ),
+      ),
+    );
+
+    // If we only have one db driver available, we cannot set the driver.
+    include_once DRUPAL_ROOT . '/core/includes/install.inc';
+    if (count($this->getDatabaseTypes()) == 1) {
+      unset($parameters['forms']['install_settings_form']['driver']);
+    }
+    return $parameters;
+  }
+
   /**
    * Preserve the original batch, and instantiate the test batch.
    */
diff --git a/core/modules/update/update.manager.inc b/core/modules/update/update.manager.inc
index 2b2aef2ff9..84038e1d8e 100644
--- a/core/modules/update/update.manager.inc
+++ b/core/modules/update/update.manager.inc
@@ -37,6 +37,7 @@
  */
 
 use Drupal\Core\File\Exception\FileException;
+use Drupal\Core\Site\Site;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
diff --git a/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php b/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php
index b5cb96b8cc..4f287586e8 100644
--- a/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php
+++ b/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\DrupalKernel;
 use Drupal\KernelTests\KernelTestBase;
+use Drupal\Core\Site\Site;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -17,6 +18,9 @@ class DrupalKernelTest extends KernelTestBase {
    * {@inheritdoc}
    */
   protected function setUp() {
+    // Initialize the test Site singleton, so that Site::getPath() works.
+    Site::init(DRUPAL_ROOT);
+
     // DrupalKernel relies on global $config_directories and requires those
     // directories to exist. Therefore, create the directories, but do not
     // invoke KernelTestBase::setUp(), since that would set up further
diff --git a/core/tests/Drupal/KernelTests/Core/File/ReadOnlyStreamWrapperTest.php b/core/tests/Drupal/KernelTests/Core/File/ReadOnlyStreamWrapperTest.php
index 8dd9de8965..dd30c7d46f 100644
--- a/core/tests/Drupal/KernelTests/Core/File/ReadOnlyStreamWrapperTest.php
+++ b/core/tests/Drupal/KernelTests/Core/File/ReadOnlyStreamWrapperTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\KernelTests\Core\File;
 
+use Drupal\Core\Site\Site;
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 use Drupal\file_test\StreamWrapper\DummyReadOnlyStreamWrapper;
 
diff --git a/core/tests/Drupal/KernelTests/Core/Site/SettingsRewriteTest.php b/core/tests/Drupal/KernelTests/Core/Site/SettingsRewriteTest.php
index 0507871124..623616ab31 100644
--- a/core/tests/Drupal/KernelTests/Core/Site/SettingsRewriteTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Site/SettingsRewriteTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Site\Settings;
 use Drupal\KernelTests\KernelTestBase;
+use Drupal\Core\Site\Site;
 
 /**
  * Tests the drupal_rewrite_settings() function.
diff --git a/sites/example.sites.php b/sites/example.sites.php
deleted file mode 100644
index daaf68272a..0000000000
--- a/sites/example.sites.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-// @codingStandardsIgnoreFile
-
-/**
- * @file
- * Configuration file for multi-site support and directory aliasing feature.
- *
- * This file is required for multi-site support and also allows you to define a
- * set of aliases that map hostnames, ports, and pathnames to configuration
- * directories in the sites directory. These aliases are loaded prior to
- * scanning for directories, and they are exempt from the normal discovery
- * rules. See default.settings.php to view how Drupal discovers the
- * configuration directory when no alias is found.
- *
- * Aliases are useful on development servers, where the domain name may not be
- * the same as the domain of the live server. Since Drupal stores file paths in
- * the database (files, system table, etc.) this will ensure the paths are
- * correct when the site is deployed to a live server.
- *
- * To activate this feature, copy and rename it such that its path plus
- * filename is 'sites/sites.php'.
- *
- * Aliases are defined in an associative array named $sites. The array is
- * written in the format: '<port>.<domain>.<path>' => 'directory'. As an
- * example, to map https://www.drupal.org:8080/mysite/test to the configuration
- * directory sites/example.com, the array should be defined as:
- * @code
- * $sites = [
- *   '8080.www.drupal.org.mysite.test' => 'example.com',
- * ];
- * @endcode
- * The URL, https://www.drupal.org:8080/mysite/test/, could be a symbolic link
- * or an Apache Alias directive that points to the Drupal root containing
- * index.php. An alias could also be created for a subdomain. See the
- * @link https://www.drupal.org/documentation/install online Drupal installation guide @endlink
- * for more information on setting up domains, subdomains, and subdirectories.
- *
- * The following examples look for a site configuration in sites/example.com:
- * @code
- * URL: http://dev.drupal.org
- * $sites['dev.drupal.org'] = 'example.com';
- *
- * URL: http://localhost/example
- * $sites['localhost.example'] = 'example.com';
- *
- * URL: http://localhost:8080/example
- * $sites['8080.localhost.example'] = 'example.com';
- *
- * URL: https://www.drupal.org:8080/mysite/test/
- * $sites['8080.www.drupal.org.mysite.test'] = 'example.com';
- * @endcode
- *
- * @see default.settings.php
- * @see \Drupal\Core\DrupalKernel::getSitePath()
- * @see https://www.drupal.org/documentation/install/multi-site
- */
