diff --git a/core/core.services.yml b/core/core.services.yml
index 53a2725..ad08aff 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -196,6 +196,10 @@ services:
     factory_method: get
     factory_service: cache_factory
     arguments: [discovery]
+  cache_router_rebuild_subscriber:
+    class: Drupal\Core\EventSubscriber\CacheRouterRebuildSubscriber
+    tags:
+      - { name: event_subscriber }
   page_cache_request_policy:
     class: Drupal\Core\PageCache\DefaultRequestPolicy
     arguments: ['@session_configuration']
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 5a4073f..f268fc3 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -494,6 +494,10 @@ protected function invalidateTagsOnSave($update) {
     // listing's filtering requirements. A newly created entity may start to
     // appear in listings because it did not exist before.)
     $tags = $this->getEntityType()->getListCacheTags();
+    if ($this->hasLinkTemplate('canonical')) {
+      // Creating or updating an entity may change a cached 403 or 404 response.
+      $tags = Cache::mergeTags($tags, ['4xx-response']);
+    }
     if ($update) {
       // An existing entity was updated, also invalidate its unique cache tag.
       $tags = Cache::mergeTags($tags, $this->getCacheTags());
diff --git a/core/lib/Drupal/Core/EventSubscriber/CacheRouterRebuildSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/CacheRouterRebuildSubscriber.php
new file mode 100644
index 0000000..9589348
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/CacheRouterRebuildSubscriber.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\CacheRouterRebuildSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Routing\RoutingEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Clear cache tags when the router is rebuilt.
+ */
+class CacheRouterRebuildSubscriber implements EventSubscriberInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onRouterFinished() {
+    // Requested URLs that formerly gave a 403/404 may now be valid.
+    Cache::invalidateTags(['4xx-response']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events = [];
+    // Act only when the router rebuild is finished.
+    $events[RoutingEvents::FINISHED][] = ['onRouterFinished'];
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index 157454d..abdaede 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -138,6 +138,15 @@ public function onRespond(FilterResponseEvent $event) {
     if ($access_result instanceof CacheableDependencyInterface) {
       $this->updateDrupalCacheHeaders($response, $access_result);
     }
+    // Add a cache tag to any 4xx response.
+    if ($response->isClientError()) {
+      $cache_tags = ['4xx-response'];
+      if ($response->headers->has('X-Drupal-Cache-Tags')) {
+        $existing_cache_tags = explode(' ', $response->headers->get('X-Drupal-Cache-Tags'));
+        $cache_tags = Cache::mergeTags($existing_cache_tags, $cache_tags);
+      }
+      $response->headers->set('X-Drupal-Cache-Tags', implode(' ', $cache_tags));
+    }
 
     $is_cacheable = ($this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW) && ($this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY);
 
diff --git a/core/modules/system/src/Tests/Bootstrap/PageCacheTest.php b/core/modules/system/src/Tests/Bootstrap/PageCacheTest.php
index 4256c16..0b9584b 100644
--- a/core/modules/system/src/Tests/Bootstrap/PageCacheTest.php
+++ b/core/modules/system/src/Tests/Bootstrap/PageCacheTest.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Datetime\DateTimePlus;
 use Drupal\Core\Routing\RequestContext;
 use Drupal\Core\Url;
+use Drupal\entity_test\Entity\EntityTest;
 use Drupal\simpletest\WebTestBase;
 use Drupal\Core\Cache\Cache;
 use Drupal\user\Entity\Role;
@@ -29,7 +30,7 @@ class PageCacheTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = array('test_page_test', 'system_test');
+  public static $modules = array('test_page_test', 'system_test', 'entity_test');
 
   protected function setUp() {
     parent::setUp();
@@ -308,6 +309,57 @@ function testPageCacheAnonymousRolePermissions() {
   }
 
   /**
+   * Tests the 4xx-response cache tag is added and invalidated.
+   */
+  function testPageCacheAnonymous403404() {
+    $config = $this->config('system.performance');
+    $config->set('cache.page.use_internal', 1);
+    $config->set('cache.page.max_age', 300);
+    $config->save();
+    $admin_url = Url::fromRoute('system.admin');
+    $invalid_url = 'foo/' . $this->randomString();
+    $tests = [
+      403 => $admin_url,
+      404 => $invalid_url,
+    ];
+    foreach ($tests as $code => $content_url) {
+      // Anonymous user, without permissions.
+      $this->drupalGet($content_url);
+      $this->assertResponse($code);
+      $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
+      $cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
+      $this->assertTrue(in_array('4xx-response', $cache_tags));
+      $this->drupalGet($content_url);
+      $this->assertResponse($code);
+      $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
+      $entity_values = array(
+        'name' => $this->randomMachineName(),
+        'user_id' => 1,
+        'field_test_text' => array(
+          0 => array(
+            'value' => $this->randomString(),
+            'format' => 'plain_text',
+          )
+        ),
+      );
+      $entity = EntityTest::create($entity_values);
+      $entity->save();
+      // Saving an entity clears 4xx cache tag.
+      $this->drupalGet($content_url);
+      $this->assertResponse($code);
+      $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
+      $this->drupalGet($content_url);
+      $this->assertResponse($code);
+      $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
+      // Rebuilding the router should clear the 4xx cache tag.
+      $this->container->get('router.builder')->rebuild();
+      $this->drupalGet($content_url);
+      $this->assertResponse($code);
+      $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
+    }
+  }
+
+  /**
    * Tests the omit_vary_cookie setting.
    */
   public function testPageCacheWithoutVaryCookie() {
