diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
index 3238ce6..e90cd0c 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -96,9 +96,10 @@ class ExtensionDiscovery {
    * @param string $root
    *   The app root.
    */
-  public function __construct($root) {
+  public function __construct($root, $use_file_cache = TRUE, $profile_directories = NULL) {
     $this->root = $root;
-    $this->fileCache = FileCacheFactory::get('extension_discovery');
+    $this->fileCache = $use_file_cache ? FileCacheFactory::get('extension_discovery') : NULL;
+    $this->profileDirectories = $profile_directories;
   }
 
   /**
@@ -427,7 +428,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 +468,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/modules/system/src/Tests/Update/DedicatedFrontControllerTest.php b/core/modules/system/src/Tests/Update/DedicatedFrontControllerTest.php
new file mode 100644
index 0000000..79051fc
--- /dev/null
+++ b/core/modules/system/src/Tests/Update/DedicatedFrontControllerTest.php
@@ -0,0 +1,142 @@
+<?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');
+
+    // 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/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/update.php b/core/update.php
new file mode 100644
index 0000000..bcf5334
--- /dev/null
+++ b/core/update.php
@@ -0,0 +1,158 @@
+<?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 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;
+
+/**
+ * Loads the update_early.inc file for a given extension.
+ *
+ * @param \Drupal\Core\Extension\Extension $module
+ */
+function _update_load_update_early_file(Extension $module) {
+  $filename = $module->getPath() . '/' . $module->getName() . '.update_early.inc';
+  if (file_exists($filename)) {
+    include_once $filename;
+  }
+}
+
+/**
+ * Gets all available update_early functions.
+ *
+ * @return string[]
+ */
+function _update_get_available_update_early_functions() {
+  $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;
+}
+
+function _update_get_missing_update_early_functions(DrupalKernelInterface $kernel) {
+  // 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($kernel->getAppRoot(), FALSE, []);
+  $module_extensions = $extension_discovery->scan('module');
+
+  $config = BootstrapConfigStorageFactory::get();
+
+  // Load all the update_early.inc files.
+  foreach (array_keys($config->read('core.extension')['module']) as $module) {
+    if (isset($module_extensions[$module])) {
+      _update_load_update_early_file($module_extensions[$module]);
+    }
+  }
+
+  // First figure out which hook_update_early got executed already.
+  $filename = PublicStream::basePath() . '/.ht.update_early';
+  $existing_update_early_N = [];
+  if (file_exists($filename)) {
+    $existing_update_early_N = file_get_contents($filename);
+    $existing_update_early_N = explode("\n", $existing_update_early_N);
+  }
+
+  $available_update_early_N = _update_get_available_update_early_functions();
+  $not_executed_update_early_N = array_diff($available_update_early_N, $existing_update_early_N);
+
+  return $not_executed_update_early_N;
+}
+
+// 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';
+DrupalKernel::bootEnvironment();
+
+try {
+  Settings::initialize(dirname(__DIR__), DrupalKernel::findSitePath($request), $autoloader);
+  $kernel = new DrupalKernel('prod', $autoloader);
+  $kernel->setSitePath(DrupalKernel::findSitePath($request));
+
+  // 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.
+  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 (\Exception $e) {
+    if (!Settings::get('update_free_access')) {
+      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.');
+    }
+  }
+
+  $response = new Response();
+  $log_filename = PublicStream::basePath() . '/.ht.update_early';
+
+  // Execute all of the remaining ones.
+  $missing_update_early_N = _update_get_missing_update_early_functions($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;
+}
