diff --git a/core/core.update_early_services.yml b/core/core.update_early_services.yml
new file mode 100644
index 0000000..0d2399d
--- /dev/null
+++ b/core/core.update_early_services.yml
@@ -0,0 +1,17 @@
+parameters:
+  session.storage.options: {}
+services:
+  database:
+    class: Drupal\Core\Database\Connection
+    factory: Drupal\Core\Database\Database::getConnection
+    arguments: [default]
+  session_configuration:
+    class: Drupal\Core\Session\SessionConfiguration
+    arguments: ['%session.storage.options%']
+  authentication:
+    class: Drupal\Core\Authentication\AuthenticationManager
+    arguments: ['@authentication_collector']
+  authentication_collector:
+    class: Drupal\Core\Authentication\AuthenticationCollector
+    tags:
+      - { name: service_collector, tag: authentication_provider, call: addProvider }
diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
index 3238ce6..1f61d15 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -91,14 +91,23 @@ class ExtensionDiscovery {
   protected $fileCache;
 
   /**
+   * The site path.
+   *
+   * @var string
+   */
+  protected $sitePath;
+
+  /**
    * Constructs a new ExtensionDiscovery object.
    *
    * @param string $root
    *   The app root.
    */
-  public function __construct($root) {
+  public function __construct($root, $use_file_cache = TRUE, $profile_directories = NULL, $site_path = NULL) {
     $this->root = $root;
-    $this->fileCache = FileCacheFactory::get('extension_discovery');
+    $this->fileCache = $use_file_cache ? FileCacheFactory::get('extension_discovery') : NULL;
+    $this->profileDirectories = $profile_directories;
+    $this->sitePath = $site_path;
   }
 
   /**
@@ -172,7 +181,7 @@ public function scan($type, $include_tests = NULL) {
       $searchdirs[static::ORIGIN_SITE] = \Drupal::service('site.path');
     }
     else {
-      $searchdirs[static::ORIGIN_SITE] = DrupalKernel::findSitePath(Request::createFromGlobals());
+      $searchdirs[static::ORIGIN_SITE] = isset($this->sitePath) ? $this->sitePath : DrupalKernel::findSitePath(Request::createFromGlobals());
     }
 
     // Unless an explicit value has been passed, manually check whether we are
@@ -427,7 +436,7 @@ protected function scanDirectory($dir, $include_tests) {
         continue;
       }
 
-      if ($cached_extension = $this->fileCache->get($fileinfo->getPathName())) {
+      if ($this->fileCache && $cached_extension = $this->fileCache->get($fileinfo->getPathName())) {
         $files[$cached_extension->getType()][$key] = $cached_extension;
         continue;
       }
@@ -467,7 +476,10 @@ protected function scanDirectory($dir, $include_tests) {
       $extension->origin = $dir;
 
       $files[$type][$key] = $extension;
-      $this->fileCache->set($fileinfo->getPathName(), $extension);
+
+      if ($this->fileCache) {
+        $this->fileCache->set($fileinfo->getPathName(), $extension);
+      }
     }
     return $files;
   }
diff --git a/core/lib/Drupal/Core/Update/UpdateEarlyKernel.php b/core/lib/Drupal/Core/Update/UpdateEarlyKernel.php
new file mode 100644
index 0000000..482cc61
--- /dev/null
+++ b/core/lib/Drupal/Core/Update/UpdateEarlyKernel.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Update\UpdateEarlyKernel.
+ */
+
+namespace Drupal\Core\Update;
+
+use Drupal\Core\DrupalKernel;
+
+/**
+ * Provides a kernel with a container with just the services needed for early updated.
+ *
+ * The early update is needed in order to fix for example the schema so that Drupal can
+ * bootstrap properly and execute the steps done in /admin/update, which requires a
+ * working Drupal.
+ *
+ * The list of services are limited by not using .services.yml files but rather
+ * .update_early_services.yml. Core by default provides at least the database service
+ * but maybe other contrib modules
+ *
+ *
+ * @see /core/update.php
+ */
+class UpdateEarlyKernel extends DrupalKernel {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function discoverServiceProviders() {
+    $this->serviceYamls = [
+      'app' => [],
+      'site' => [],
+    ];
+    $this->serviceProviderClasses = [
+      'app' => [],
+      'site' => [],
+    ];
+    $this->serviceYamls['app']['core'] = 'core/core.update_early_services.yml';
+
+    // Retrieve enabled modules and register their namespaces.
+    if (!isset($this->moduleList)) {
+      $extensions = $this->getConfigStorage()->read('core.extension');
+      $this->moduleList = isset($extensions['module']) ? $extensions['module'] : array();
+    }
+    $module_filenames = $this->getModuleFileNames();
+    $this->classLoaderAddMultiplePsr4($this->getModuleNamespacesPsr4($module_filenames));
+
+    // Load each module's .update_early_services files.
+    foreach ($module_filenames as $module => $filename) {
+      $filename = dirname($filename) . "/$module.update_early_services.yml";
+      if (file_exists($filename)) {
+        $this->serviceYamls['app'][$module] = $filename;
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function initializeContainer() {
+    // Always force a container rebuild.
+    $this->containerNeedsRebuild = TRUE;
+    $container = parent::initializeContainer();
+    return $container;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Update/UpdateEarlyRegistry.php b/core/lib/Drupal/Core/Update/UpdateEarlyRegistry.php
new file mode 100644
index 0000000..abf8433
--- /dev/null
+++ b/core/lib/Drupal/Core/Update/UpdateEarlyRegistry.php
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Update\UpdateEarlyRegistry.
+ */
+
+namespace Drupal\Core\Update;
+
+use Drupal\Core\Config\BootstrapConfigStorageFactory;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ExtensionDiscovery;
+
+class UpdateEarlyRegistry {
+
+  /**
+   * The app root.
+   *
+   * @var string
+   */
+  protected $root;
+
+  /**
+   * The filename of the log file.
+   *
+   * @var string
+   */
+  protected $logFilename;
+
+  /**
+   * @var string[]
+   */
+  protected $enabledModules;
+
+  public function __construct($root, $site_path, array $enabled_modules, $log_filename) {
+    $this->root = $root;
+    $this->sitePath = $site_path;
+    $this->enabledModules = $enabled_modules;
+    $this->logFilename = $log_filename;
+  }
+
+  /**
+   * Gets all available update_early functions.
+   *
+   * @return callable[]
+   */
+  protected function getAvailableEarlyUpdateFunctions() {
+    $regexp = '/^(?<module>.+)_update_early_(?<version>\d+)$/';
+    $functions = get_defined_functions();
+
+    $updates = [];
+    foreach (preg_grep('/_\d+$/', $functions['user']) as $function) {
+      // If this function is a module update function, add it to the list of
+      // module updates.
+      if (preg_match($regexp, $function, $matches)) {
+        $updates[] = $matches['module'] . '_update_early_' . $matches['version'];
+      }
+    }
+
+    // Ensure that updates are applied in right order.
+    // @todo Does that mean we need to take into account module weights?
+    sort($updates);
+
+    return $updates;
+  }
+
+  /**
+   * Gets the non already executed update_early functions.
+   *
+   * @return callable[]
+   */
+  public function getMissingUpdateEarlyFunctions() {
+    // We need a) the list of active modules (we get that from the config
+    // bootstrap factory) and b) the path to the modules, we use the extension
+    // discovery for that.
+
+    // Scan the module list.
+    // We don't support install profiles at that point?
+    $extension_discovery = new ExtensionDiscovery($this->root, FALSE, [], $this->sitePath);
+    $module_extensions = $extension_discovery->scan('module', FALSE);
+
+    $this->loadUpdateEarlyFiles($module_extensions);
+
+    // First figure out which hook_update_early got executed already.
+    $existing_update_early_N = [];
+    if (file_exists($this->logFilename)) {
+      $existing_update_early_N = file_get_contents($this->logFilename);
+      $existing_update_early_N = explode("\n", $existing_update_early_N);
+    }
+
+    $available_update_early_N = $this->getAvailableEarlyUpdateFunctions();
+    $not_executed_update_early_N = array_diff($available_update_early_N, $existing_update_early_N);
+
+    return $not_executed_update_early_N;
+  }
+
+  protected function loadUpdateEarlyFiles(array $module_extensions) {
+    // Load all the update_early.inc files.
+    foreach ($this->enabledModules as $module) {
+      if (isset($module_extensions[$module])) {
+        $this->loadUpdateEarlyFile($module_extensions[$module]);
+      }
+    }
+  }
+
+  /**
+   * Loads the update_early.inc file for a given extension.
+   *
+   * @param \Drupal\Core\Extension\Extension $module
+   */
+  protected function loadUpdateEarlyFile(Extension $module) {
+    $filename = $this->root . '/' . $module->getPath() . '/' . $module->getName() . '.update_early.inc';
+    if (file_exists($filename)) {
+      include_once $filename;
+    }
+  }
+
+
+
+}
diff --git a/core/modules/system/src/Tests/Update/DedicatedFrontControllerTest.php b/core/modules/system/src/Tests/Update/DedicatedFrontControllerTest.php
new file mode 100644
index 0000000..d7f3861
--- /dev/null
+++ b/core/modules/system/src/Tests/Update/DedicatedFrontControllerTest.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Update\DedicatedFrontControllerTest.
+ */
+
+namespace Drupal\system\Tests\Update;
+use Drupal\Core\StreamWrapper\PublicStream;
+
+/**
+ * Tests the dedicated front controller /core/update.php.
+ *
+ * @group Update
+ */
+class DedicatedFrontControllerTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['error_service_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../tests/fixtures/update/drupal-8.bare.standard.php.gz',
+      __DIR__ . '/../../../tests/fixtures/update/drupal-8.update_early.php',
+    ];
+    parent::setUp();
+  }
+
+  protected function setupBrokenContainer() {
+    \Drupal::state()->set('error_service_test_break_authentication', TRUE);
+    $this->kernel->rebuildContainer();
+  }
+
+  protected function setupNonBrokenContainer() {
+    \Drupal::state()->set('error_service_test_break_authentication', FALSE);
+    $this->kernel->rebuildContainer();
+  }
+
+  protected function setupFreeAccess() {
+    include_once DRUPAL_ROOT . '/core/includes/install.inc';
+    $filename = $this->siteDirectory . '/settings.php';
+    chmod($filename, 0666);
+
+    $settings['settings']['update_free_access'] = (object) [
+      'value' => TRUE,
+      'required' => TRUE,
+    ];
+    drupal_rewrite_settings($settings);
+  }
+
+  protected function setupNoFreeAccess() {
+    include_once DRUPAL_ROOT . '/core/includes/install.inc';
+    $filename = $this->siteDirectory . '/settings.php';
+    chmod($filename, 0666);
+
+    $settings['settings']['update_free_access'] = (object) [
+      'value' => FALSE,
+      'required' => TRUE,
+    ];
+    drupal_rewrite_settings($settings);
+  }
+
+  /**
+   * Tests the various scenarios of access to /core/update.php.
+   */
+  public function testUpdatePhpAccess() {
+    $this->setupBrokenContainer();
+    $this->dotestAccessDeniedWithBrokenBootstrapAndWithoutFreeAccess();
+    $this->dotestAccessAllowedWithBrokenBootstrapAndFreeAccess();
+
+    $this->setupNonBrokenContainer();
+    $this->dotestAccessDeniedAsUserWithoutPermission();
+  }
+
+  /**
+   * Tests a broken authentication without $update_free_access.
+   */
+  public function dotestAccessDeniedWithBrokenBootstrapAndWithoutFreeAccess() {
+    $this->drupalGet('core/update.php');
+    $this->assertResponse(403);
+  }
+
+  /**
+   * Tests a broken authentication with $update_free_access.
+   */
+  public function dotestAccessAllowedWithBrokenBootstrapAndFreeAccess() {
+    $this->setupFreeAccess();
+
+    $this->drupalGet('core/update.php');
+    $this->assertResponse(200);
+
+    $this->setupNoFreeAccess();
+  }
+
+  /**
+   * Tests a non broken authentication without access.
+   */
+  protected function dotestAccessDeniedAsUserWithoutPermission() {
+    $account = $this->drupalCreateUser();
+    $this->drupalLogin($account);
+    $this->setupNoFreeAccess();
+
+    $this->drupalGet('core/update.php');
+    $this->assertResponse(403);
+  }
+
+  /**
+   * Tests a non broken authentication without access but $update_free_access.
+   */
+  protected function dotestAccessDeniedAsUserWithoutPermissionAndFreeAccess() {
+    $account = $this->drupalCreateUser();
+    $this->drupalLogin($account);
+    $this->setupFreeAccess();
+
+    $this->drupalGet('core/update.php');
+
+    $this->assertResponse(200);
+  }
+
+  public function testUpdatePhpRepair() {
+    $this->setupFreeAccess();
+    $this->setupBrokenContainer();
+
+    $this->drupalGet('core/update.php');
+
+    // Ensure that the user got redirected.
+    $this->assertUrl('admin/update');
+    $this->assertResponse(200);
+
+    // Ensure that the update_early_N ran.
+    $this->assertFalse(\Drupal::state()->get('error_service_test_break_authentication'));
+    // Ensure that the function was logged.
+
+    $log_filename = PublicStream::basePath() . '/.ht.update_early';
+    $this->assertEqual("error_service_test_update_early_8000\n", file_get_contents($log_filename));
+  }
+
+}
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index a656ab3..3ec57c9 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -449,7 +449,7 @@ system.batch_page.json:
     _admin_route: TRUE
 
 system.db_update:
-  path: '/update.php/{op}'
+  path: '/admin/update/{op}'
   defaults:
     _title: 'Drupal database update'
     _controller: '\Drupal\system\Controller\DbUpdateController::handle'
diff --git a/core/modules/system/system.update_early.inc b/core/modules/system/system.update_early.inc
new file mode 100644
index 0000000..a187b0b
--- /dev/null
+++ b/core/modules/system/system.update_early.inc
@@ -0,0 +1,22 @@
+<?php
+
+use Drupal\Core\Database\Database;
+
+/**
+ * Registers the /admin/update route on the {router}.
+ */
+function system_update_early_8001() {
+  // Insert the new /admin/update route into the router. We can't rebuild the
+  // router, because it depends on too much of a working Drupal.
+  $connection = Database::getConnection();
+  $connection->insert('router')
+    ->fields([
+      'name' => 'system.db_update',
+      'path' => '/admin/update/{op}',
+      'pattern_outline' => '/admin/update',
+      'fit' => 3,
+      'route' => 'C:31:"Symfony\Component\Routing\Route":1279:{a:9:{s:4:"path";s:18:"/admin/update/{op}";s:4:"host";s:0:"";s:8:"defaults";a:3:{s:6:"_title";s:22:"Drupal database update";s:11:"_controller";s:52:"\Drupal\system\Controller\DbUpdateController::handle";s:2:"op";s:4:"info";}s:12:"requirements";a:2:{s:21:"_access_system_update";s:4:"TRUE";s:7:"_method";s:8:"GET|POST";}s:7:"options";a:6:{s:14:"compiler_class";s:34:"\Drupal\Core\Routing\RouteCompiler";s:19:"_maintenance_access";b:1;s:12:"_admin_route";b:1;s:14:"_route_filters";a:1:{i:0;s:27:"content_type_header_matcher";}s:16:"_route_enhancers";a:1:{i:0;s:31:"route_enhancer.param_conversion";}s:14:"_access_checks";a:1:{i:0;s:22:"access_check.db_update";}}s:7:"schemes";a:0:{}s:7:"methods";a:2:{i:0;s:3:"GET";i:1;s:4:"POST";}s:9:"condition";s:0:"";s:8:"compiled";C:33:"Drupal\Core\Routing\CompiledRoute":465:{a:11:{s:4:"vars";a:1:{i:0;s:2:"op";}s:11:"path_prefix";s:13:"/admin/update";s:10:"path_regex";s:38:"#^/admin/update(?:/(?P<op>[^/]++))?$#s";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:6:"[^/]++";i:3;s:2:"op";}i:1;a:2:{i:0;s:4:"text";i:1;s:13:"/admin/update";}}s:9:"path_vars";a:1:{i:0;s:2:"op";}s:10:"host_regex";N;s:11:"host_tokens";a:0:{}s:9:"host_vars";a:0:{}s:3:"fit";i:3;s:14:"patternOutline";s:13:"/admin/update";s:8:"numParts";i:2;}}}}',
+      'number_parts' => 2,
+    ])
+    ->execute();
+}
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.update_early.php b/core/modules/system/tests/fixtures/update/drupal-8.update_early.php
new file mode 100644
index 0000000..575eac2
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.update_early.php
@@ -0,0 +1,17 @@
+<?php
+
+use Drupal\Core\Database\Database;
+
+$database = Database::getConnection();
+
+$extensions = unserialize($database->query("SELECT data FROM {config} WHERE name = 'core.extension'")->fetchField());
+$extensions['module']['error_service_test'] = 0;
+$database->update('config')
+  ->fields(['data' => serialize($extensions)])
+  ->condition('name', 'core.extension')
+  ->execute();
+
+$database->insert('key_value')
+  ->fields(['collection' => 'system.schema', 'name' => 'error_service_test', 'value' => serialize(8000)])
+  ->execute();
+
diff --git a/core/modules/system/tests/modules/error_service_test/error_service_test.update_early.inc b/core/modules/system/tests/modules/error_service_test/error_service_test.update_early.inc
new file mode 100644
index 0000000..f5624e0
--- /dev/null
+++ b/core/modules/system/tests/modules/error_service_test/error_service_test.update_early.inc
@@ -0,0 +1,14 @@
+<?php
+use Drupal\Core\Database\Database;
+
+/**
+ * Implements hook_update_early_N().
+ */
+function error_service_test_update_early_8000() {
+  $database = Database::getConnection();
+  $database->update('key_value')
+    ->condition('collection', 'state')
+    ->condition('name', 'error_service_test_break_authentication')
+    ->fields(['value' => serialize(FALSE)])
+    ->execute();
+}
diff --git a/core/modules/system/tests/modules/error_service_test/src/BrokenAuthenticationManager.php b/core/modules/system/tests/modules/error_service_test/src/BrokenAuthenticationManager.php
new file mode 100644
index 0000000..fa798a5
--- /dev/null
+++ b/core/modules/system/tests/modules/error_service_test/src/BrokenAuthenticationManager.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\error_service_test\BrokenAuthenticationManager.
+ */
+
+namespace Drupal\error_service_test;
+
+use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
+use Drupal\Core\Authentication\AuthenticationProviderFilterInterface;
+use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+class BrokenAuthenticationManager implements AuthenticationProviderInterface, AuthenticationProviderFilterInterface, AuthenticationProviderChallengeInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Request $request) {
+    throw new \RuntimeException('Broken authentication');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function authenticate(Request $request) {
+    throw new \RuntimeException('Broken authentication');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesToRoutedRequest(Request $request, $authenticated) {
+    throw new \RuntimeException('Broken authentication');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function challengeException(Request $request, \Exception $previous) {
+    throw new \RuntimeException('Broken authentication');
+  }
+
+}
diff --git a/core/modules/system/tests/modules/error_service_test/src/ErrorServiceTestServiceProvider.php b/core/modules/system/tests/modules/error_service_test/src/ErrorServiceTestServiceProvider.php
index 4a4aaaf..f5d41aa 100644
--- a/core/modules/system/tests/modules/error_service_test/src/ErrorServiceTestServiceProvider.php
+++ b/core/modules/system/tests/modules/error_service_test/src/ErrorServiceTestServiceProvider.php
@@ -24,5 +24,10 @@ class ErrorServiceTestServiceProvider implements ServiceModifierInterface {
    */
   public function alter(ContainerBuilder $container) {
     static::$containerBuilder = $container;
+
+    if (\Drupal::state()->get('error_service_test_break_authentication')) {
+      $container->register('authentication_manager', 'Drupal\error_service_test\BrokenAuthenticationManager');
+    }
   }
+
 }
diff --git a/core/modules/user/user.update_early_services.yml b/core/modules/user/user.update_early_services.yml
new file mode 100644
index 0000000..4fe3282
--- /dev/null
+++ b/core/modules/user/user.update_early_services.yml
@@ -0,0 +1,6 @@
+services:
+  user.authentication.cookie:
+    class: Drupal\user\Authentication\Provider\Cookie
+    arguments: ['@session_configuration', '@database']
+    tags:
+      - { name: authentication_provider, provider_id: 'cookie', priority: 0, global: TRUE }
diff --git a/core/tests/Drupal/Tests/Core/Update/UpdateEarlyRegistryTest.php b/core/tests/Drupal/Tests/Core/Update/UpdateEarlyRegistryTest.php
new file mode 100644
index 0000000..7faae35
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Update/UpdateEarlyRegistryTest.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Update\UpdateEarlyRegistryTest.
+ */
+
+namespace Drupal\Tests\Core\Update;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Update\UpdateEarlyRegistry;
+use Drupal\Tests\UnitTestCase;
+use org\bovigo\vfs\vfsStream;
+use org\bovigo\vfs\vfsStreamWrapper;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Update\UpdateEarlyRegistry
+ * @group Update
+ *
+ * @runTestsInSeparateProcesses
+ */
+class UpdateEarlyRegistryTest extends UnitTestCase {
+
+  protected function setupBasicModules($directories) {
+    $info_a = <<<'EOS'
+type: module
+name: Module A
+core: 8.x
+EOS;
+
+    $info_b = <<<'EOS'
+type: module
+name: Module B
+core: 8.x
+EOS;
+
+    $module_a = <<<'EOS'
+<?php
+
+function module_a_update_early_8001() {
+}
+
+function module_a_update_early_8002() {
+}
+
+EOS;
+    $module_b = <<<'EOS'
+<?php
+
+function module_b_update_early_8001() {
+}
+
+EOS;
+    vfsStream::setup('drupal');
+    vfsStream::create(NestedArray::mergeDeep($directories, [
+      'sites' => ['default' => ['modules' => [
+        'module_a' => ['module_a.update_early.inc' => $module_a, 'module_a.info.yml' => $info_a],
+        'module_b' => ['module_b.update_early.inc' => $module_b, 'module_b.info.yml' => $info_b],
+      ]]],
+    ]));
+  }
+
+  /**
+   * @covers ::getMissingUpdateEarlyFunctions
+   */
+  public function ptestGetMissingUpdateEarlyFunctionsWithNoExistingUpdates() {
+    $this->setupBasicModules();
+
+    $update_registry = new UpdateEarlyRegistry('vfs://drupal', 'sites/default', ['module_a', 'module_b'], 'vfs://drupal/sites/default/files/.ht.update_early');
+
+    $this->assertEquals(['module_a_update_early_8001', 'module_a_update_early_8002', 'module_b_update_early_8001'], $update_registry->getMissingUpdateEarlyFunctions());
+  }
+
+  /**
+   * @covers ::getMissingUpdateEarlyFunctions
+   */
+  public function testGetMissingUpdateEarlyFunctionsWithExistingUpdates() {
+    $this->setupBasicModules([
+      'sites' => ['default' => ['files' => ['.ht.update_early' => 'module_a_update_early_8001']]],
+    ]);
+
+    $update_registry = new UpdateEarlyRegistry('vfs://drupal', 'sites/default', ['module_a', 'module_b'], 'vfs://drupal/sites/default/files/.ht.update_early');
+
+    $this->assertEquals(array_values(['module_a_update_early_8002', 'module_b_update_early_8001']), array_values($update_registry->getMissingUpdateEarlyFunctions()));
+  }
+
+}
diff --git a/core/update.php b/core/update.php
new file mode 100644
index 0000000..8211dd2
--- /dev/null
+++ b/core/update.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * @file
+ * Provides a front controller dedicated to update which relies on as few as
+ * possible dependencies, so that it can change for example some of the really
+ * low level tables.
+ *
+ * In order to have access to this front controller you either needed to be logged
+ * in before (this might or might not work), or have $update_free_access set in
+ * your
+ *
+ *
+ * @see \Drupal\system\Controller\DbUpdateController
+ */
+
+use Drupal\Core\Config\BootstrapConfigStorageFactory;
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\DrupalKernelInterface;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\Core\Update\UpdateEarlyKernel;
+use Drupal\Core\Update\UpdateEarlyRegistry;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
+
+// Change the directory to the Drupal root.
+chdir('..');
+
+$autoloader = require_once __DIR__ . '/vendor/autoload.php';
+require_once __DIR__ . '/includes/utility.inc';
+
+$request = Request::createFromGlobals();
+// Manually resemble early bootstrap of DrupalKernel::boot().
+require_once __DIR__ . '/includes/bootstrap.inc';
+UpdateEarlyKernel::bootEnvironment();
+
+try {
+  Settings::initialize(dirname(__DIR__), UpdateEarlyKernel::findSitePath($request), $autoloader);
+  $kernel = new UpdateEarlyKernel('prod', $autoloader);
+  $kernel->setSitePath(UpdateEarlyKernel::findSitePath($request));
+
+  $log_filename = $kernel->getSitePath() . '/.ht.update_early';
+  $update_early_registry = new UpdateEarlyRegistry($kernel->getAppRoot(), $log_filename);
+
+  // Try to boot up Drupal and authenicate the user. In case it works, we know
+  // that the user has access. Otherwise we check for $update_free_access, if
+  // this also doesn't work we show at least some help message.
+  if (!Settings::get('update_free_access')) {
+    try {
+      $kernel->boot();
+      $container = $kernel->getContainer();
+      /** @var \Drupal\Core\Authentication\AuthenticationManager $authentication_manager */
+      $authentication_manager = $container->get('authentication');
+      $account = $authentication_manager->authenticate($request);
+
+      // Ensure that the user is allowed to access update.php.
+      if (!$account || !$container->get('access_check.db_update')->access($account)) {
+        throw new AccessDeniedHttpException();
+      }
+    }
+    catch (AccessDeniedHttpException $e) {
+      throw new AccessDeniedHttpException('In order to run update.php you need to either be logged in as admin or have set $update_free_access in your settings.php.');
+    }
+    catch (\Exception $e) {
+      throw new AccessDeniedHttpException('Authentication fataled for running update.php, so you need to set $update_free_access in settings.php.');
+    }
+  }
+
+  $response = new Response();
+
+  // Execute all of the remaining ones.
+  $missing_update_early_N = $update_early_registry->getMissingUpdateEarlyFunctions($kernel);
+  foreach ($missing_update_early_N as $function) {
+    $output = $function();
+    $response->setContent($output)->prepare($request)->send();
+
+    file_put_contents($log_filename, $function . "\n", FILE_APPEND);
+  }
+
+  // Redirect to the actual update controller which can deal with a bootstrapped
+  // Drupal.
+  $response = new RedirectResponse(str_replace('core/update.php', '', $request->getUriForPath('/admin/update')));
+  $response->prepare($request)->send();
+}
+catch (HttpExceptionInterface $e) {
+  $response = new Response('', $e->getStatusCode());
+  $response->prepare($request)->send();
+  exit;
+}
