diff --git a/core/lib/Drupal/Core/Update/UpdateKernel.php b/core/lib/Drupal/Core/Update/UpdateKernel.php
new file mode 100755
index 0000000000000000000000000000000000000000..6c5de739fcc638f80b7217f420782cfe82890474
--- /dev/null
+++ b/core/lib/Drupal/Core/Update/UpdateKernel.php
@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Update\UpdateKernel.
+ */
+
+namespace Drupal\Core\Update;
+
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Session\AnonymousUserSession;
+use Drupal\Core\Site\Settings;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\ParameterBag;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * Defines a kernel which is used primarily to run the update of Drupal.
+ *
+ * We use a dedicated kernel + front controller (update.php) in order to be able
+ * to repair Drupal if it is in a broken state.
+ *
+ * @see update.php
+ * @see \Drupal\system\Controller\DbUpdateController
+ */
+class UpdateKernel extends DrupalKernel {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
+    try {
+      static::bootEnvironment();
+
+      // First boot up basic things, like loading the include files.
+      $this->initializeSettings($request);
+      $this->boot();
+      $container = $this->getContainer();
+      /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
+      $request_stack = $container->get('request_stack');
+      $request_stack->push($request);
+      $this->preHandle($request);
+
+      // Handle the actual request. We need the session both for authentication
+      // as well as the DB update, like
+      // \Drupal\system\Controller\DbUpdateController::batchFinished.
+      $this->bootSession($request, $type);
+      $result = $this->handleRaw($request);
+      $this->shutdownSession($request);
+
+      return $result;
+    }
+    catch (\Exception $e) {
+      return $this->handleException($e, $request, $type);
+    }
+  }
+
+  /**
+   * Generates the actual result of update.php.
+   *
+   * The actual logic of the update is done in the db update controller.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The incoming request.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   A response object.
+   *
+   * @see \Drupal\system\Controller\DbUpdateController
+   */
+  protected function handleRaw(Request $request) {
+    $container = $this->getContainer();
+
+    $this->handleAccess($request, $container);
+
+    /** @var \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver */
+    $controller_resolver = $container->get('controller_resolver');
+
+    /** @var callable $db_update_controller */
+    $db_update_controller = $controller_resolver->getControllerFromDefinition('\Drupal\system\Controller\DbUpdateController::handle');
+
+    $this->setupRequestMatch($request);
+
+    $arguments = $controller_resolver->getArguments($request, $db_update_controller);
+    return call_user_func_array($db_update_controller, $arguments);
+  }
+
+  /**
+   * Boots up the session.
+   *
+   * bootSession() + shutdownSession() basically simulates what
+   * \Drupal\Core\StackMiddleware\Session does.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The incoming request.
+   */
+  protected function bootSession(Request $request) {
+    $container = $this->getContainer();
+    /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */
+    $session = $container->get('session');
+    $session->start();
+    $request->setSession($session);
+  }
+
+  /**
+   * Ensures that the session is saved.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The incoming request.
+   */
+  protected function shutdownSession(Request $request) {
+    if ($request->hasSession()) {
+      $request->getSession()->save();
+    }
+  }
+
+  /**
+   * Set up the request with fake routing data for update.php.
+   *
+   * This fake routing data is needed in order to make batch API work properly.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The incoming request.
+   */
+  protected function setupRequestMatch(Request $request) {
+    $path = $request->getPathInfo();
+    $args = explode('/', ltrim($path, '/'));
+
+    $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'system.db_update');
+    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $this->getContainer()->get('router.route_provider')->getRouteByName('system.db_update'));
+    $op = $args[0] ?: 'info';
+    $request->attributes->set('op', $op);
+    $request->attributes->set('_raw_variables', new ParameterBag(['op' => $op]));
+  }
+
+  /**
+   * Checks if the current user has rights to access updates page.
+   *
+   * If the current user does not have the rights, an exception is thrown.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The incoming request.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   Thrown when update.php should not be accessible.
+   */
+  protected function handleAccess(Request $request) {
+    /** @var \Drupal\Core\Authentication\AuthenticationManager $authentication_manager */
+    $authentication_manager = $this->getContainer()->get('authentication');
+    $account = $authentication_manager->authenticate($request) ?: new AnonymousUserSession();
+
+    /** @var \Drupal\Core\Session\AccountProxyInterface $current_user */
+    $current_user = $this->getContainer()->get('current_user');
+    $current_user->setAccount($account);
+
+    /** @var \Drupal\system\Access\DbUpdateAccessCheck $db_update_access */
+    $db_update_access = $this->getContainer()->get('access_check.db_update');
+
+    if (!Settings::get('update_free_access', FALSE) && !$db_update_access->access($account)->isAllowed()) {
+      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.');
+    }
+  }
+
+}
diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
index 6fe6225f47887cd5e07dc602cf6d6ce71166072c..402d471b46f4ad1b04fce95cdaf161ed34b07392 100644
--- a/core/modules/simpletest/src/AssertContentTrait.php
+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -286,7 +286,7 @@ protected function getAllOptions(\SimpleXMLElement $element) {
    *   Link position counting from zero.
    * @param string $message
    *   (optional) A message to display with the assertion. Do not translate
-   *   messages: use format_string() to embed variables in the message text, not
+   *   messages: use strtr() to embed variables in the message text, not
    *   t(). If left blank, a default message will be displayed.
    * @param string $group
    *   (optional) The group this message is in, which is displayed in a column
@@ -299,7 +299,7 @@ protected function getAllOptions(\SimpleXMLElement $element) {
    */
   protected function assertLink($label, $index = 0, $message = '', $group = 'Other') {
     $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
-    $message = ($message ? $message : SafeMarkup::format('Link with label %label found.', array('%label' => $label)));
+    $message = ($message ? $message : strtr('Link with label %label found.', array('%label' => $label)));
     return $this->assert(isset($links[$index]), $message, $group);
   }
 
@@ -377,6 +377,30 @@ protected function assertNoLinkByHref($href, $message = '', $group = 'Other') {
     return $this->assert(empty($links), $message, $group);
   }
 
+  /**
+   * Passes if a link containing a given href is not found in the main region.
+   *
+   * @param string $href
+   *   The full or partial value of the 'href' attribute of the anchor tag.
+   * @param string $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param string $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Other'; most tests do not override
+   *   this default.
+   *
+   * @return bool
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertNoLinkByHrefInMainRegion($href, $message = '', $group = 'Other') {
+    $links = $this->xpath('//main//a[contains(@href, :href)]', array(':href' => $href));
+    $message = ($message ? $message : SafeMarkup::format('No link containing href %href found.', array('%href' => $href)));
+    return $this->assert(empty($links), $message, $group);
+  }
+
   /**
    * Passes if the raw text IS found on the loaded page, fail otherwise.
    *
diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php
index c2e195e9454578bb61382015d0d622d6ef421f3b..aa46654847925b59dc2a1d79ed10af1cb069e7ba 100644
--- a/core/modules/system/src/Controller/DbUpdateController.php
+++ b/core/modules/system/src/Controller/DbUpdateController.php
@@ -160,13 +160,13 @@ public function handle($op, Request $request) {
     $severity = drupal_requirements_severity($requirements);
     if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($_SESSION['update_ignore_warnings']))) {
       $regions['sidebar_first'] = $this->updateTasksList('requirements');
-      $output = $this->requirements($severity, $requirements);
+      $output = $this->requirements($severity, $requirements, $request);
     }
     else {
       switch ($op) {
         case 'selection':
           $regions['sidebar_first'] = $this->updateTasksList('selection');
-          $output = $this->selection();
+          $output = $this->selection($request);
           break;
 
         case 'run':
@@ -176,12 +176,12 @@ public function handle($op, Request $request) {
 
         case 'info':
           $regions['sidebar_first'] = $this->updateTasksList('info');
-          $output = $this->info();
+          $output = $this->info($request);
           break;
 
         case 'results':
           $regions['sidebar_first'] = $this->updateTasksList('results');
-          $output = $this->results();
+          $output = $this->results($request);
           break;
 
         // Regular batch ops : defer to batch processing API.
@@ -204,10 +204,13 @@ public function handle($op, Request $request) {
   /**
    * Returns the info database update page.
    *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
    * @return array
    *   A render array.
    */
-  protected function info() {
+  protected function info(Request $request) {
     // Change query-strings on css/js files to enforce reload for all users.
     _drupal_flush_css_js();
     // Flush the cache of all data for the update status module.
@@ -233,12 +236,11 @@ protected function info() {
       '#markup' => '<p>' . $this->t('When you have performed the steps above, you may proceed.') . '</p>',
     );
 
-    $url = new Url('system.db_update', array('op' => 'selection'));
     $build['link'] = array(
       '#type' => 'link',
       '#title' => $this->t('Continue'),
       '#attributes' => array('class' => array('button', 'button--primary')),
-      '#url' => $url,
+      '#url' => Url::fromUri($request->getUriForPath('/selection')),
     );
     return $build;
   }
@@ -246,10 +248,13 @@ protected function info() {
   /**
    * Renders a list of available database updates.
    *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
    * @return array
    *   A render array.
    */
-  protected function selection() {
+  protected function selection(Request $request) {
     // Make sure there is no stale theme registry.
     $this->cache->deleteAll();
 
@@ -342,7 +347,7 @@ protected function selection() {
       unset($build);
       $build['links'] = array(
         '#theme' => 'links',
-        '#links' => $this->helpfulLinks(),
+        '#links' => $this->helpfulLinks($request),
       );
 
       // No updates to run, so caches won't get flushed later.  Clear them now.
@@ -364,7 +369,9 @@ protected function selection() {
       else {
         $build['start']['#title'] = $this->formatPlural($count, '1 pending update', '@count pending updates');
       }
-      $url = new Url('system.db_update', array('op' => 'run'));
+      // @todo Simplify with https://www.drupal.org/node/2548095
+      $base_url = str_replace('/update.php', '', $request->getBaseUrl());
+      $url = (new Url('system.db_update', array('op' => 'run')))->setOption('base_url', $base_url);
       $build['link'] = array(
         '#type' => 'link',
         '#title' => $this->t('Apply pending updates'),
@@ -380,15 +387,21 @@ protected function selection() {
   /**
    * Displays results of the update script with any accompanying errors.
    *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
    * @return array
    *   A render array.
    */
-  protected function results() {
+  protected function results(Request $request) {
+    // @todo Simplify with https://www.drupal.org/node/2548095
+    $base_url = str_replace('/update.php', '', $request->getBaseUrl());
+
     // Report end result.
     $dblog_exists = $this->moduleHandler->moduleExists('dblog');
     if ($dblog_exists && $this->account->hasPermission('access site reports')) {
       $log_message = $this->t('All errors have been <a href="@url">logged</a>.', array(
-        '@url' => Url::fromRoute('dblog.overview')->toString(TRUE)->getGeneratedUrl(),
+        '@url' => Url::fromRoute('dblog.overview')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl(),
       ));
     }
     else {
@@ -396,7 +409,7 @@ protected function results() {
     }
 
     if (!empty($_SESSION['update_success'])) {
-      $message = '<p>' . $this->t('Updates were attempted. If you see no failures below, you may proceed happily back to your <a href="@url">site</a>. Otherwise, you may need to update your database manually.', array('@url' => Url::fromRoute('<front>')->toString(TRUE)->getGeneratedUrl())) . ' ' . $log_message . '</p>';
+      $message = '<p>' . $this->t('Updates were attempted. If you see no failures below, you may proceed happily back to your <a href="@url">site</a>. Otherwise, you may need to update your database manually.', array('@url' => Url::fromRoute('<front>')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl())) . ' ' . $log_message . '</p>';
     }
     else {
       $last = reset($_SESSION['updates_remaining']);
@@ -420,7 +433,7 @@ protected function results() {
     );
     $build['links'] = array(
       '#theme' => 'links',
-      '#links' => $this->helpfulLinks(),
+      '#links' => $this->helpfulLinks($request),
     );
 
     // Output a list of info messages.
@@ -492,12 +505,15 @@ protected function results() {
   /**
    * Renders a list of requirement errors or warnings.
    *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
    * @return array
    *   A render array.
    */
-  public function requirements($severity, array $requirements) {
+  public function requirements($severity, array $requirements, Request $request) {
     $options = $severity == REQUIREMENT_WARNING ? array('continue' => 1) : array();
-    $try_again_url = Url::fromRoute('system.db_update', $options)->toString(TRUE)->getGeneratedUrl();
+    $try_again_url = Url::fromUri($request->getUriForPath(''))->setOptions(['query' => $options])->toString(TRUE)->getGeneratedUrl();
 
     $build['status_report'] = array(
       '#theme' => 'status_report',
@@ -603,7 +619,7 @@ protected function triggerBatch(Request $request) {
     );
     batch_set($batch);
 
-    return batch_process('update.php/results', Url::fromRoute('system.db_update', array('op' => 'start')));
+    return batch_process(Url::fromUri($request->getUriForPath('/results')), Url::fromUri($request->getUriForPath('/start')));
   }
 
   /**
@@ -640,18 +656,23 @@ public static function batchFinished($success, $results, $operations) {
   /**
    * Provides links to the homepage and administration pages.
    *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
    * @return array
    *   An array of links.
    */
-  protected function helpfulLinks() {
+  protected function helpfulLinks(Request $request) {
+    // @todo Simplify with https://www.drupal.org/node/2548095
+    $base_url = str_replace('/update.php', '', $request->getBaseUrl());
     $links['front'] = array(
       'title' => $this->t('Front page'),
-      'url' => Url::fromRoute('<front>'),
+      'url' => Url::fromRoute('<front>')->setOption('base_url', $base_url),
     );
     if ($this->account->hasPermission('access administration pages')) {
       $links['admin-pages'] = array(
         'title' => $this->t('Administration pages'),
-        'url' => Url::fromRoute('system.admin'),
+        'url' => Url::fromRoute('system.admin')->setOption('base_url', $base_url),
       );
     }
     return $links;
diff --git a/core/modules/system/src/Tests/Update/UpdatePathWithBrokenRoutingTest.php b/core/modules/system/src/Tests/Update/UpdatePathWithBrokenRoutingTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..551feab0f7e096f289f17ceb9afa6d13aa2088d2
--- /dev/null
+++ b/core/modules/system/src/Tests/Update/UpdatePathWithBrokenRoutingTest.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Update\UpdatePathWithBrokenRoutingTest.
+ */
+
+namespace Drupal\system\Tests\Update;
+
+/**
+ * Tests the update path with a broken router.
+ *
+ * @group Update
+ */
+class UpdatePathWithBrokenRoutingTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../tests/fixtures/update/drupal-8.bare.standard.php.gz',
+      __DIR__ . '/../../../tests/fixtures/update/drupal-8.broken_routing.php',
+    ];
+    parent::setUp();
+  }
+
+  /**
+   * Tests running update.php with some form of broken routing.
+   */
+  public function testWithBrokenRouting() {
+    // Make sure we can get to the front page.
+    $this->drupalGet('<front>');
+    $this->assertResponse(200);
+
+    // Simulate a broken router, and make sure the front page is
+    // inaccessible.
+    \Drupal::state()->set('update_script_test_broken_inbound', TRUE);
+    \Drupal::service('cache_tags.invalidator')->invalidateTags(['route_match', 'rendered']);
+    $this->drupalGet('<front>');
+    $this->assertResponse(500);
+
+    // The exceptions are expected. Do not interpret them as a test failure.
+    // Not using File API; a potential error must trigger a PHP warning.
+    unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
+    foreach ($this->assertions as $key => $assertion) {
+      if (strpos($assertion['message'], 'core/modules/system/tests/modules/update_script_test/src/PathProcessor/BrokenInboundPathProcessor.php') !== FALSE) {
+        unset($this->assertions[$key]);
+        $this->deleteAssert($assertion['message_id']);
+      }
+    }
+
+    $this->runUpdates();
+
+    // Remove the simulation of the broken router, and make sure we can get to
+    // the front page again.
+    \Drupal::state()->set('update_script_test_broken_inbound', FALSE);
+    $this->drupalGet('<front>');
+    $this->assertResponse(200);
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Update/UpdateScriptTest.php b/core/modules/system/src/Tests/Update/UpdateScriptTest.php
index 7aed8613b93a5f9cb0c680e6564fabd62a1bcd63..cba80ca5fb4e77eaa01674c02204d6dfb13ebab5 100644
--- a/core/modules/system/src/Tests/Update/UpdateScriptTest.php
+++ b/core/modules/system/src/Tests/Update/UpdateScriptTest.php
@@ -153,7 +153,7 @@ function testNoUpdateFunctionality() {
     $this->clickLink(t('Continue'));
     $this->assertText(t('No pending updates.'));
     $this->assertNoLink('Administration pages');
-    $this->assertNoLinkByHref('update.php', 0);
+    $this->assertNoLinkByHrefInMainRegion('update.php', 0);
     $this->clickLink('Front page');
     $this->assertResponse(200);
 
@@ -164,7 +164,7 @@ function testNoUpdateFunctionality() {
     $this->clickLink(t('Continue'));
     $this->assertText(t('No pending updates.'));
     $this->assertLink('Administration pages');
-    $this->assertNoLinkByHref('update.php', 1);
+    $this->assertNoLinkByHrefInMainRegion('update.php', 1);
     $this->clickLink('Administration pages');
     $this->assertResponse(200);
   }
@@ -198,7 +198,7 @@ function testSuccessfulUpdateFunctionality() {
     $this->assertText('Updates were attempted.');
     $this->assertLink('logged');
     $this->assertLink('Administration pages');
-    $this->assertNoLinkByHref('update.php', 1);
+    $this->assertNoLinkByHrefInMainRegion('update.php', 1);
     $this->clickLink('Administration pages');
     $this->assertResponse(200);
   }
@@ -253,7 +253,7 @@ protected function updateScriptTest($maintenance_mode) {
 
     // Verify that there are no links to different parts of the workflow.
     $this->assertNoLink('Administration pages');
-    $this->assertNoLinkByHref('update.php', 0);
+    $this->assertNoLinkByHrefInMainRegion('update.php', 0);
     $this->assertNoLink('logged');
 
     // Verify the front page can be visited following the upgrade.
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index a656ab3aaa2d8993b4d6b7ca0543ec649c59c5c7..2da51261b1f375d7788e20fa5752c2e4e63bf3f6 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -448,16 +448,14 @@ system.batch_page.json:
   options:
     _admin_route: TRUE
 
+# Note: This route just exists for generating URLs, the dedicated
+# frontcontroller is used if the URL is accessed.
 system.db_update:
   path: '/update.php/{op}'
   defaults:
-    _title: 'Drupal database update'
-    _controller: '\Drupal\system\Controller\DbUpdateController::handle'
     op: 'info'
-  options:
-    _maintenance_access: TRUE
   requirements:
-    _access_system_update: 'TRUE'
+    _access: 'TRUE'
 
 system.admin_content:
   path: '/admin/content'
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.broken_routing.php b/core/modules/system/tests/fixtures/update/drupal-8.broken_routing.php
new file mode 100644
index 0000000000000000000000000000000000000000..3c5e9afa5cb8efc28fdc8dccef802b9beeb41eb1
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.broken_routing.php
@@ -0,0 +1,18 @@
+<?php
+
+use Drupal\Core\Database\Database;
+
+
+$connection = Database::getConnection();
+
+$config = unserialize($connection->query("SELECT data FROM {config} where name = :name", [':name' => 'core.extension'])->fetchField());
+$config['module']['update_script_test'] = 0;
+$connection->update('config')
+  ->fields(['data' => serialize($config)])
+  ->condition('name', 'core.extension')
+  ->execute();
+
+$connection->insert('key_value')
+  ->fields(['collection' => 'system.schema', 'name' => 'update_script_test', 'value' => serialize(8000)])
+  ->execute();
+
diff --git a/core/modules/system/tests/modules/update_script_test/src/PathProcessor/BrokenInboundPathProcessor.php b/core/modules/system/tests/modules/update_script_test/src/PathProcessor/BrokenInboundPathProcessor.php
new file mode 100644
index 0000000000000000000000000000000000000000..24f15d05a169b6e085247afd732c660af3610bfa
--- /dev/null
+++ b/core/modules/system/tests/modules/update_script_test/src/PathProcessor/BrokenInboundPathProcessor.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\update_script_test\PathProcessor\BrokenInboundPathProcessor.
+ */
+
+namespace Drupal\update_script_test\PathProcessor;
+
+use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
+use Drupal\Core\State\StateInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Example path processor which breaks on inbound.
+ */
+class BrokenInboundPathProcessor implements InboundPathProcessorInterface {
+
+  /**
+   * The state.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * Constructs a new BrokenInboundPathProcessor instance.
+   *
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state.
+   */
+  public function __construct(StateInterface $state) {
+    $this->state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processInbound($path, Request $request) {
+    if ($this->state->get('update_script_test_broken_inbound', FALSE)) {
+      throw new \RuntimeException();
+    }
+    else {
+      return $path;
+    }
+  }
+
+}
diff --git a/core/modules/system/tests/modules/update_script_test/update_script_test.services.yml b/core/modules/system/tests/modules/update_script_test/update_script_test.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..82446eda3c19dd0226fb1fac55cd4ed871ec382b
--- /dev/null
+++ b/core/modules/system/tests/modules/update_script_test/update_script_test.services.yml
@@ -0,0 +1,7 @@
+services:
+  update_script_test.broken_path_processor:
+    class: Drupal\update_script_test\PathProcessor\BrokenInboundPathProcessor
+    arguments: ['@state']
+    tags:
+      - { name: path_processor_inbound, priority: 1000 }
+
diff --git a/update.php b/update.php
new file mode 100755
index 0000000000000000000000000000000000000000..5222b967acbcf1e6a1701ea25699ffbdcb04fada
--- /dev/null
+++ b/update.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * The PHP page that handles updating the Drupal installation.
+ *
+ * All Drupal code is released under the GNU General Public License.
+ * See COPYRIGHT.txt and LICENSE.txt files in the "core" directory.
+ */
+
+use Drupal\Core\Update\UpdateKernel;
+use Symfony\Component\HttpFoundation\Request;
+
+$autoloader = require_once 'autoload.php';
+
+$kernel = new UpdateKernel('prod', $autoloader);
+$request = Request::createFromGlobals();
+
+$response = $kernel->handle($request);
+$response->send();
+
+$kernel->terminate($request, $response);