 core/core.services.yml                             |  8 +++-
 .../EventSubscriber/FinishResponseSubscriber.php   |  3 +-
 .../PageCache/ResponsePolicy/DenyNoCacheRoutes.php | 50 ++++++++++++++++++++++
 .../system/src/Tests/System/CronRunTest.php        |  8 +---
 core/modules/system/system.routing.yml             |  4 ++
 .../src/Controller/SystemTestController.php        |  9 ----
 .../modules/system_test/system_test.routing.yml    |  6 +++
 .../modules/user/src/Controller/UserController.php |  9 +---
 core/modules/user/user.routing.yml                 |  1 +
 9 files changed, 74 insertions(+), 24 deletions(-)

diff --git a/core/core.services.yml b/core/core.services.yml
index f3d2ae6..42d66e2 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -210,6 +210,12 @@ services:
     class: Drupal\Core\PageCache\ResponsePolicy\KillSwitch
     tags:
       - { name: page_cache_response_policy }
+  page_cache_no_cache_routes:
+    class: Drupal\Core\PageCache\ResponsePolicy\DenyNoCacheRoutes
+    arguments: ['@current_route_match']
+    public: false
+    tags:
+      - { name: page_cache_response_policy }
   page_cache_no_server_error:
     class: Drupal\Core\PageCache\ResponsePolicy\NoServerError
     public: false
@@ -950,7 +956,7 @@ services:
     class: Drupal\Core\EventSubscriber\FinishResponseSubscriber
     tags:
       - { name: event_subscriber }
-    arguments: ['@language_manager', '@config.factory', '@page_cache_request_policy', '@page_cache_response_policy', '@cache_contexts']
+    arguments: ['@language_manager', '@config.factory', '@page_cache_request_policy', '@page_cache_response_policy', '@cache_contexts', '@current_route_match', '@page_cache_kill_switch']
   redirect_response_subscriber:
     class: Drupal\Core\EventSubscriber\RedirectResponseSubscriber
     arguments: ['@url_generator', '@router.request_context']
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index 0e508a2..48ee4fd 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -79,7 +79,7 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
    * @param \Drupal\Core\PageCache\ResponsePolicyInterface $response_policy
    *   A policy rule determining the cacheability of a response.
    * @param \Drupal\Core\Cache\CacheContexts $cache_contexts
-   *   The cache contexts service.
+   *   The cache contexts
    */
   public function __construct(LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory, RequestPolicyInterface $request_policy, ResponsePolicyInterface $response_policy, CacheContexts $cache_contexts) {
     $this->languageManager = $language_manager;
@@ -87,6 +87,7 @@ public function __construct(LanguageManagerInterface $language_manager, ConfigFa
     $this->requestPolicy = $request_policy;
     $this->responsePolicy = $response_policy;
     $this->cacheContexts = $cache_contexts;
+    $this->routeMatch = $route_match;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/PageCache/ResponsePolicy/DenyNoCacheRoutes.php b/core/lib/Drupal/Core/PageCache/ResponsePolicy/DenyNoCacheRoutes.php
new file mode 100644
index 0000000..cbe4dc7
--- /dev/null
+++ b/core/lib/Drupal/Core/PageCache/ResponsePolicy/DenyNoCacheRoutes.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\PageCache\DenyNoCacheRoutes.
+ */
+
+namespace Drupal\Core\PageCache\ResponsePolicy;
+
+use Drupal\Core\PageCache\ResponsePolicyInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Cache policy for routes with the 'no_cache' option set.
+ *
+ * This policy rule denies caching of responses generated for routes that have
+ * the 'no_cache' option set to TRUE.
+ */
+class DenyNoCacheRoutes implements ResponsePolicyInterface {
+
+  /**
+   * The current route match.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * Constructs a deny node preview page cache policy.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The current route match.
+   */
+  public function __construct(RouteMatchInterface $route_match) {
+    $this->routeMatch = $route_match;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function check(Response $response, Request $request) {
+    if (($route = $this->routeMatch->getRouteObject()) && $route->getOption('no_cache')) {
+      return static::DENY;
+    }
+  }
+
+}
+
diff --git a/core/modules/system/src/Tests/System/CronRunTest.php b/core/modules/system/src/Tests/System/CronRunTest.php
index e17bbf3..de8cdcc 100644
--- a/core/modules/system/src/Tests/System/CronRunTest.php
+++ b/core/modules/system/src/Tests/System/CronRunTest.php
@@ -49,12 +49,6 @@ function testCronRun() {
    * need the exact time when cron is triggered.
    */
   function testAutomaticCron() {
-    // Test with a logged in user; anonymous users likely don't cause Drupal to
-    // fully bootstrap, because of the internal page cache or an external
-    // reverse proxy. Reuse this user for disabling cron later in the test.
-    $admin_user = $this->drupalCreateUser(array('administer site configuration'));
-    $this->drupalLogin($admin_user);
-
     // Ensure cron does not run when the cron threshold is enabled and was
     // not passed.
     $cron_last = time();
@@ -74,6 +68,8 @@ function testAutomaticCron() {
     $this->assertTrue($cron_last < \Drupal::state()->get('system.cron_last'), 'Cron runs when the cron threshold is passed.');
 
     // Disable the cron threshold through the interface.
+    $admin_user = $this->drupalCreateUser(array('administer site configuration'));
+    $this->drupalLogin($admin_user);
     $this->drupalPostForm('admin/config/system/cron', array('cron_safe_threshold' => 0), t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'));
     $this->drupalLogout();
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 544c005..39755b6 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -123,6 +123,8 @@ system.cron:
   path: '/cron/{key}'
   defaults:
     _controller: '\Drupal\system\CronController::run'
+  options:
+    no_cache: TRUE
   requirements:
     _access_system_cron: 'TRUE'
 
@@ -217,6 +219,8 @@ system.run_cron:
   path: '/admin/reports/status/run-cron'
   defaults:
     _controller: '\Drupal\system\CronController::runManually'
+  options:
+    no_cache: TRUE
   requirements:
     _permission: 'administer site configuration'
     _csrf_token: 'TRUE'
diff --git a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
index 204e7af..6b6b063 100644
--- a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
+++ b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
@@ -119,9 +119,6 @@ public function requestDestination(Request $request) {
    * Try to acquire a named lock and report the outcome.
    */
   public function lockAcquire() {
-    // Don't cache lock-testing pages.
-    \Drupal::service('page_cache_kill_switch')->trigger();
-
     if ($this->lock->acquire('system_test_lock_acquire')) {
       $this->lock->release('system_test_lock_acquire');
       return ['#markup' => 'TRUE: Lock successfully acquired in \Drupal\system_test\Controller\SystemTestController::lockAcquire()'];
@@ -135,9 +132,6 @@ public function lockAcquire() {
    * Try to acquire a specific lock, and then exit.
    */
   public function lockExit() {
-    // Don't cache lock-testing pages.
-    \Drupal::service('page_cache_kill_switch')->trigger();
-
     if ($this->lock->acquire('system_test_lock_exit', 900)) {
       echo 'TRUE: Lock successfully acquired in \Drupal\system_test\Controller\SystemTestController::lockExit()';
       // The shut-down function should release the lock.
@@ -158,9 +152,6 @@ public function lockExit() {
    *   The text to display.
    */
   public function lockPersist($lock_name) {
-    // Don't cache lock-testing pages.
-    \Drupal::service('page_cache_kill_switch')->trigger();
-
     if ($this->persistentLock->acquire($lock_name)) {
       return ['#markup' => 'TRUE: Lock successfully acquired in SystemTestController::lockPersist()'];
     }
diff --git a/core/modules/system/tests/modules/system_test/system_test.routing.yml b/core/modules/system/tests/modules/system_test/system_test.routing.yml
index 86d0a31..9df9dc0 100644
--- a/core/modules/system/tests/modules/system_test/system_test.routing.yml
+++ b/core/modules/system/tests/modules/system_test/system_test.routing.yml
@@ -34,6 +34,8 @@ system_test.lock_acquire:
   defaults:
     _title: 'Lock acquire'
     _controller: '\Drupal\system_test\Controller\SystemTestController::lockAcquire'
+  options:
+    no_cache: TRUE
   requirements:
     _access: 'TRUE'
 
@@ -42,6 +44,8 @@ system_test.lock_exit:
   defaults:
     _title: 'Lock acquire then exit'
     _controller: '\Drupal\system_test\Controller\SystemTestController::lockExit'
+  options:
+    no_cache: TRUE
   requirements:
     _access: 'TRUE'
 
@@ -50,6 +54,8 @@ system_test.lock_persist:
   defaults:
     _title: 'Persistent lock acquire'
     _controller: '\Drupal\system_test\Controller\SystemTestController::lockPersist'
+  options:
+    no_cache: TRUE
   requirements:
     _access: 'TRUE'
 
diff --git a/core/modules/user/src/Controller/UserController.php b/core/modules/user/src/Controller/UserController.php
index 14e0a09..0d9fa53 100644
--- a/core/modules/user/src/Controller/UserController.php
+++ b/core/modules/user/src/Controller/UserController.php
@@ -62,11 +62,10 @@ class UserController extends ControllerBase {
    * @param \Drupal\Core\PageCache\ResponsePolicy\KillSwitch $page_cache_kill_switch
    *   The page cache killswitch.
    */
-  public function __construct(DateFormatter $date_formatter, UserStorageInterface $user_storage, UserDataInterface $user_data, KillSwitch $page_cache_kill_switch) {
+  public function __construct(DateFormatter $date_formatter, UserStorageInterface $user_storage, UserDataInterface $user_data) {
     $this->dateFormatter = $date_formatter;
     $this->userStorage = $user_storage;
     $this->userData = $user_data;
-    $this->pageCacheKillSwitch = $page_cache_kill_switch;
   }
 
   /**
@@ -76,8 +75,7 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('date.formatter'),
       $container->get('entity.manager')->getStorage('user'),
-      $container->get('user.data'),
-      $container->get('page_cache_kill_switch')
+      $container->get('user.data')
     );
   }
 
@@ -98,9 +96,6 @@ public static function create(ContainerInterface $container) {
    *   If the login link is for a blocked user or invalid user ID.
    */
   public function resetPass($uid, $timestamp, $hash) {
-    // Don't cache the password reset page.
-    $this->pageCacheKillSwitch->trigger();
-
     $account = $this->currentUser();
     $config = $this->config('user.settings');
     // When processing the one-time login link, we have to make sure that a user
diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml
index d30770f..6eb709f 100644
--- a/core/modules/user/user.routing.yml
+++ b/core/modules/user/user.routing.yml
@@ -148,3 +148,4 @@ user.reset:
     _access: 'TRUE'
   options:
     _maintenance_access: TRUE
+    no_cache: TRUE
