diff --git a/core/core.services.yml b/core/core.services.yml
index f3d2ae618fb50b2b8d333d4fe6a98fc276c3f088..14c665c0ea81b953d491c3f85238759745923d30 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
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 0000000000000000000000000000000000000000..cbe4dc7052502c594aeb60e39739a09fda41a0d1
--- /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 e17bbf311293e0f13f51675c3c4d73c84fc76ff4..de8cdcccbd73a174e324fc35511a4fa17550d78e 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 544c005f111fccfbee5711113d70df000a136890..39755b61af5c62df009df895fcf1bf151d4e08af 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 204e7af95db714a281dfd5af0ffa7b35d8dd9f28..6b6b06333f6b2e93671d20172d95996ee68c878a 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 86d0a31ec92a12f1f8193b6c99ba9589f5005598..9df9dc0a7508df553f9e0bb3a3bc30afce6be703 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 14e0a0997c7a833749a4639950f97ea8637dcd40..0243c33690ab111373ceaf5d629d3cd9bd03a20f 100644
--- a/core/modules/user/src/Controller/UserController.php
+++ b/core/modules/user/src/Controller/UserController.php
@@ -10,7 +10,6 @@
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Datetime\DateFormatter;
-use Drupal\Core\PageCache\ResponsePolicy\KillSwitch;
 use Drupal\user\UserDataInterface;
 use Drupal\user\UserInterface;
 use Drupal\user\UserStorageInterface;
@@ -43,13 +42,6 @@ class UserController extends ControllerBase {
    */
   protected $userData;
 
-  /**
-   * The page cache killswitch.
-   *
-   * @var \Drupal\Core\PageCache\ResponsePolicy\KillSwitch
-   */
-  protected $pageCacheKillSwitch;
-
   /**
    * Constructs a UserController object.
    *
@@ -59,14 +51,11 @@ class UserController extends ControllerBase {
    *   The user storage.
    * @param \Drupal\user\UserDataInterface $user_data
    *   The user data service.
-   * @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 +65,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 +86,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 d30770f4f15553348e8d8103b932b69edbad5cba..6eb709fe50fe5b444542211c1d92151b3a8f1478 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