diff --git a/core/core.services.yml b/core/core.services.yml
index 62a0f3e..7c1e473 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1175,7 +1175,7 @@ services:
     class: Drupal\Core\EventSubscriber\CustomPageExceptionHtmlSubscriber
     tags:
       - { name: event_subscriber }
-    arguments: ['@config.factory', '@http_kernel', '@logger.channel.php', '@redirect.destination', '@router.no_access_checks']
+    arguments: ['@config.factory', '@http_kernel', '@logger.channel.php', '@redirect.destination', '@router.no_access_checks', '@access_manager']
   exception.fast_404_html:
     class: Drupal\Core\EventSubscriber\Fast404ExceptionHtmlSubscriber
     tags:
diff --git a/core/lib/Drupal/Core/EventSubscriber/CustomPageExceptionHtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/CustomPageExceptionHtmlSubscriber.php
index ce56970..e306b38 100644
--- a/core/lib/Drupal/Core/EventSubscriber/CustomPageExceptionHtmlSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/CustomPageExceptionHtmlSubscriber.php
@@ -7,8 +7,13 @@
 
 namespace Drupal\Core\EventSubscriber;
 
+use Drupal\Core\Access\AccessManagerInterface;
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Routing\AccessAwareRouterInterface;
 use Drupal\Core\Routing\RedirectDestinationInterface;
+use Drupal\Core\Url;
 use Psr\Log\LoggerInterface;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
@@ -28,6 +33,13 @@ class CustomPageExceptionHtmlSubscriber extends DefaultExceptionHtmlSubscriber {
   protected $configFactory;
 
   /**
+   * The access manager.
+   *
+   * @var \Drupal\Core\Access\AccessManagerInterface
+   */
+  protected $accessManager;
+
+  /**
    * Constructs a new CustomPageExceptionHtmlSubscriber.
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
@@ -40,10 +52,13 @@ class CustomPageExceptionHtmlSubscriber extends DefaultExceptionHtmlSubscriber {
    *   The redirect destination service.
    * @param \Symfony\Component\Routing\Matcher\UrlMatcherInterface $access_unaware_router
    *   A router implementation which does not check access.
+   * @param \Drupal\Core\Access\AccessManagerInterface
+   *   The access manager.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, HttpKernelInterface $http_kernel, LoggerInterface $logger, RedirectDestinationInterface $redirect_destination, UrlMatcherInterface $access_unaware_router) {
+  public function __construct(ConfigFactoryInterface $config_factory, HttpKernelInterface $http_kernel, LoggerInterface $logger, RedirectDestinationInterface $redirect_destination, UrlMatcherInterface $access_unaware_router, AccessManagerInterface $access_manager) {
     parent::__construct($http_kernel, $logger, $redirect_destination, $access_unaware_router);
     $this->configFactory = $config_factory;
+    $this->accessManager = $access_manager;
   }
 
   /**
@@ -59,7 +74,7 @@ protected static function getPriority() {
   public function on403(GetResponseForExceptionEvent $event) {
     $custom_403_path = $this->configFactory->get('system.site')->get('page.403');
     if (!empty($custom_403_path)) {
-      $this->makeSubrequest($event, $custom_403_path, Response::HTTP_FORBIDDEN);
+      $this->makeSubrequestToCustomPath($event, $custom_403_path, Response::HTTP_FORBIDDEN);
     }
   }
 
@@ -69,8 +84,45 @@ public function on403(GetResponseForExceptionEvent $event) {
   public function on404(GetResponseForExceptionEvent $event) {
     $custom_404_path = $this->configFactory->get('system.site')->get('page.404');
     if (!empty($custom_404_path)) {
-      $this->makeSubrequest($event, $custom_404_path, Response::HTTP_NOT_FOUND);
+      $this->makeSubrequestToCustomPath($event, $custom_404_path, Response::HTTP_NOT_FOUND);
     }
   }
 
+  /**
+   * Makes a subrequest to retrieve the custom error page.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+   *   The event to process
+   * @param string $custom_path
+   *   The custom path to which to make a subrequest for this error message.
+   * @param int $status_code
+   *   The status code for the error being handled.
+   */
+  protected function makeSubrequestToCustomPath(GetResponseForExceptionEvent $event, $custom_path, $status_code) {
+    $url = Url::fromUserInput($custom_path);
+    if ($url->isRouted()) {
+      $access_result = $this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters(), NULL, TRUE);
+      $request = $event->getRequest();
+
+      // Merge the custom path's route's access result's cacheability metadata
+      // with the existing one (from the master request), otherwise create it.
+      if (!$request->attributes->has(AccessAwareRouterInterface::ACCESS_RESULT)) {
+        $request->attributes->set(AccessAwareRouterInterface::ACCESS_RESULT, $access_result);
+      }
+      else {
+        $existing_access_result = $request->attributes->get(AccessAwareRouterInterface::ACCESS_RESULT);
+        if ($existing_access_result instanceof RefinableCacheableDependencyInterface) {
+          $existing_access_result->addCacheableDependency($access_result);
+        }
+      }
+
+      // Only perform the subrequest if the custom path is actually accessible.
+      if (!$access_result->isAllowed()) {
+        return;
+      }
+    }
+
+    $this->makeSubrequest($event, $custom_path, $status_code);
+  }
+
 }
diff --git a/core/modules/system/src/Tests/System/AccessDeniedTest.php b/core/modules/system/src/Tests/System/AccessDeniedTest.php
index 5c6c3bf..c013525 100644
--- a/core/modules/system/src/Tests/System/AccessDeniedTest.php
+++ b/core/modules/system/src/Tests/System/AccessDeniedTest.php
@@ -23,7 +23,7 @@ class AccessDeniedTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = ['block'];
+  public static $modules = ['block', 'node', 'system_test'];
 
   protected $adminUser;
 
@@ -34,12 +34,14 @@ protected function setUp() {
 
     // Create an administrative user.
     $this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer site configuration', 'link to any page', 'administer blocks']);
+    $this->adminUser->roles[] = 'administrator';
+    $this->adminUser->save();
 
     user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('access user profiles'));
     user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, array('access user profiles'));
   }
 
-  function testAccessDenied() {
+  function atestAccessDenied() {
     $this->drupalGet('admin');
     $this->assertText(t('Access denied'), 'Found the default 403 page');
     $this->assertResponse(403);
@@ -109,4 +111,24 @@ function testAccessDenied() {
     // Check that we're still on the same page.
     $this->assertText(t('Basic site settings'));
   }
+
+  public function testAccessDeniedCustomPageWithAccessDenied() {
+    // Sets up a 403 page not accessible by the anonymous user.
+    $this->config('system.site')->set('page.403', '/system-test/custom-4xx')->save();
+
+    $this->drupalGet('/system-test/always-denied');
+    $this->assertNoText('Admin-only 4xx response');
+    $this->assertText('You are not authorized to access this page.');
+    $this->assertResponse(403);
+    // Verify the access cacheability metadata for custom 403 is bubbled.
+    $this->assertCacheContext('user.roles');
+
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('/system-test/always-denied');
+    $this->assertText('Admin-only 4xx response');
+    $this->assertResponse(403);
+    // Verify the access cacheability metadata for custom 403 is bubbled.
+    $this->assertCacheContext('user.roles');
+  }
+
 }
diff --git a/core/modules/system/src/Tests/System/PageNotFoundTest.php b/core/modules/system/src/Tests/System/PageNotFoundTest.php
index b7c0609..17ad8d7 100644
--- a/core/modules/system/src/Tests/System/PageNotFoundTest.php
+++ b/core/modules/system/src/Tests/System/PageNotFoundTest.php
@@ -17,6 +17,14 @@
  * @group system
  */
 class PageNotFoundTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['system_test'];
+
   protected $adminUser;
 
   protected function setUp() {
@@ -24,6 +32,8 @@ protected function setUp() {
 
     // Create an administrative user.
     $this->adminUser = $this->drupalCreateUser(array('administer site configuration', 'link to any page'));
+    $this->adminUser->roles[] = 'administrator';
+    $this->adminUser->save();
 
     user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('access user profiles'));
     user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, array('access user profiles'));
@@ -50,4 +60,25 @@ function testPageNotFound() {
     $this->drupalGet($this->randomMachineName(10));
     $this->assertText($this->adminUser->getUsername(), 'Found the custom 404 page');
   }
+
+  public function testPageNotFoundCustomPageWithAccessDenied() {
+    // Sets up a 404 page not accessible by the anonymous user.
+    $this->config('system.site')->set('page.404', '/system-test/custom-4xx')->save();
+
+    $this->drupalGet('/this-path-does-not-exist');
+    $this->assertNoText('Admin-only 4xx response');
+    $this->assertText('The requested page could not be found.');
+    $this->assertResponse(404);
+    // Verify the access cacheability metadata for custom 404 is bubbled.
+    $this->assertCacheContext('user.roles');
+
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('/this-path-does-not-exist');
+    $this->assertText('Admin-only 4xx response');
+    $this->assertNoText('The requested page could not be found.');
+    $this->assertResponse(404);
+    // Verify the access cacheability metadata for custom 404 is bubbled.
+    $this->assertCacheContext('user.roles');
+  }
+
 }
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 f171efd..467426a 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
@@ -19,6 +19,7 @@
 use Symfony\Component\HttpFoundation\Response;
 use Drupal\Core\Lock\LockBackendInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 
 /**
  * Controller routines for system_test routes.
@@ -353,4 +354,13 @@ public function getCurrentDate() {
     return $response;
   }
 
+  /**
+   * Always denies access.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   */
+  public function alwaysDenied() {
+    throw new AccessDeniedHttpException();
+  }
+
 }
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 a933048..3ce845f 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
@@ -159,3 +159,19 @@ system_test.date:
     no_cache: 'TRUE'
   requirements:
     _access: 'TRUE'
+
+system_test.custom_4xx_with_limited_access:
+  path: '/system-test/custom-4xx'
+  defaults:
+    _title: 'Admin-only 4xx response'
+    _controller: '\Drupal\system_test\Controller\SystemTestController::mainContentFallback'
+  requirements:
+    _role: 'administrator'
+
+system_test.always_denied:
+  path: '/system-test/always-denied'
+  defaults:
+    _title: 'Always denied'
+    _controller: '\Drupal\system_test\Controller\SystemTestController::alwaysDenied'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/CustomPageExceptionHtmlSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/CustomPageExceptionHtmlSubscriberTest.php
index cdcf5b4..e931a0c 100644
--- a/core/tests/Drupal/Tests/Core/EventSubscriber/CustomPageExceptionHtmlSubscriberTest.php
+++ b/core/tests/Drupal/Tests/Core/EventSubscriber/CustomPageExceptionHtmlSubscriberTest.php
@@ -8,7 +8,12 @@
 namespace Drupal\Tests\Core\EventSubscriber;
 
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\EventSubscriber\CustomPageExceptionHtmlSubscriber;
+use Drupal\Core\Render\HtmlResponse;
+use Drupal\Core\Routing\AccessAwareRouterInterface;
+use Drupal\Core\Url;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
@@ -70,6 +75,13 @@ class CustomPageExceptionHtmlSubscriberTest extends UnitTestCase {
   protected $accessUnawareRouter;
 
   /**
+   * The access manager.
+   *
+   * @var \Drupal\Core\Access\AccessManagerInterface
+   */
+  protected $accessManager;
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
@@ -87,8 +99,20 @@ protected function setUp() {
       ->willReturn([
         '_controller' => 'mocked',
       ]);
+    $this->accessManager = $this->getMock('Drupal\Core\Access\AccessManagerInterface');
+    $this->accessManager->expects($this->any())
+      ->method('checkNamedRoute')
+      ->willReturn(AccessResult::allowed()->addCacheTags(['foo', 'bar']));
+
+    $this->customPageSubscriber = new CustomPageExceptionHtmlSubscriber($this->configFactory, $this->kernel, $this->logger, $this->redirectDestination, $this->accessUnawareRouter, $this->accessManager);
 
-    $this->customPageSubscriber = new CustomPageExceptionHtmlSubscriber($this->configFactory, $this->kernel, $this->logger, $this->redirectDestination, $this->accessUnawareRouter);
+    $path_validator = $this->getMock('Drupal\Core\Path\PathValidatorInterface');
+    $path_validator->expects($this->any())
+      ->method('getUrlIfValidWithoutAccessCheck')
+      ->willReturn(Url::fromRoute('foo', ['foo' => 'bar']));
+    $container = new ContainerBuilder();
+    $container->set('path.validator', $path_validator);
+    \Drupal::setContainer($container);
 
     // You can't create an exception in PHP without throwing it. Store the
     // current error_log, and disable it temporarily.
@@ -109,7 +133,7 @@ public function testHandleWithPostRequest() {
     $request = Request::create('/test', 'POST', array('name' => 'druplicon', 'pass' => '12345'));
 
     $this->kernel->expects($this->once())->method('handle')->will($this->returnCallback(function (Request $request) {
-      return new Response($request->getMethod());
+      return new HtmlResponse($request->getMethod());
     }));
 
     $event = new GetResponseForExceptionEvent($this->kernel, $request, 'foo', new NotFoundHttpException('foo'));
@@ -119,6 +143,7 @@ public function testHandleWithPostRequest() {
     $response = $event->getResponse();
     $result = $response->getContent() . " " . UrlHelper::buildQuery($request->request->all());
     $this->assertEquals('POST name=druplicon&pass=12345', $result);
+    $this->assertEquals(AccessResult::allowed()->addCacheTags(['foo', 'bar']), $request->attributes->get(AccessAwareRouterInterface::ACCESS_RESULT));
   }
 
   /**
@@ -126,6 +151,7 @@ public function testHandleWithPostRequest() {
    */
   public function testHandleWithGetRequest() {
     $request = Request::create('/test', 'GET', array('name' => 'druplicon', 'pass' => '12345'));
+    $request->attributes->set(AccessAwareRouterInterface::ACCESS_RESULT, AccessResult::forbidden()->addCacheTags(['druplicon']));
 
     $this->kernel->expects($this->once())->method('handle')->will($this->returnCallback(function (Request $request) {
       return new Response($request->getMethod() . ' ' . UrlHelper::buildQuery($request->query->all()));
@@ -137,6 +163,7 @@ public function testHandleWithGetRequest() {
     $response = $event->getResponse();
     $result = $response->getContent() . " " . UrlHelper::buildQuery($request->request->all());
     $this->assertEquals('GET name=druplicon&pass=12345&destination=test&_exception_statuscode=404 ', $result);
+    $this->assertEquals(AccessResult::forbidden()->addCacheTags(['druplicon', 'foo', 'bar']), $request->attributes->get(AccessAwareRouterInterface::ACCESS_RESULT));
   }
 
 }
