diff --git a/core/core.services.yml b/core/core.services.yml
index d9b1839..5877a38 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -80,6 +80,11 @@ services:
     arguments: ['@request_stack']
     tags:
       - { name: cache.context }
+  cache_context.url.path.parent:
+    class: Drupal\Core\Cache\Context\PathParentCacheContext
+    arguments: ['@request_stack']
+    tags:
+      - { name: cache.context }
   cache_context.url.query_args:
     class: Drupal\Core\Cache\Context\QueryArgsCacheContext
     arguments: ['@request_stack']
diff --git a/core/lib/Drupal/Core/Cache/Context/PathParentCacheContext.php b/core/lib/Drupal/Core/Cache/Context/PathParentCacheContext.php
new file mode 100644
index 0000000..c815077
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/Context/PathParentCacheContext.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\Core\Cache\Context;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\Context\CacheContextInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Defines the path-based breadcrumb cache context service.
+ *
+ * Cache context ID: 'url.path.parent'.
+ *
+ * This allows for caching based on the path, excluding everything after the
+ * last forward slash.
+ */
+class PathParentCacheContext implements CacheContextInterface {
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Constructs a new BookNavigationCacheContext service.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   */
+  public function __construct(RequestStack $request_stack) {
+    $this->requestStack = $request_stack;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('Path-based breadcrumb');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    $request = $this->requestStack->getCurrentRequest();
+    $path_elements = explode('/', trim($request->getPathInfo(), '/'));
+    array_pop($path_elements);
+    return implode('/', $path_elements);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return new CacheableMetadata();
+  }
+
+}
diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php
index b4871b7..b4dce56 100644
--- a/core/modules/node/src/Tests/NodeTranslationUITest.php
+++ b/core/modules/node/src/Tests/NodeTranslationUITest.php
@@ -25,7 +25,7 @@ class NodeTranslationUITest extends ContentTranslationUITestBase {
     'theme',
     'route',
     'timezone',
-    'url.path',
+    'url.path.parent',
     'url.query_args:_wrapper_format',
     'user'
   ];
diff --git a/core/modules/system/src/PathBasedBreadcrumbBuilder.php b/core/modules/system/src/PathBasedBreadcrumbBuilder.php
index 060f1b1..dcfc073 100644
--- a/core/modules/system/src/PathBasedBreadcrumbBuilder.php
+++ b/core/modules/system/src/PathBasedBreadcrumbBuilder.php
@@ -136,9 +136,9 @@ public function build(RouteMatchInterface $route_match) {
     // /user is just a redirect, so skip it.
     // @todo Find a better way to deal with /user.
     $exclude['/user'] = TRUE;
-    // Because this breadcrumb builder is entirely path-based, vary by the
-    // 'url.path' cache context.
-    $breadcrumb->addCacheContexts(['url.path']);
+    // Add the url.path.parent cache context. This code ignores the last path
+    // part so the result only depends on the path parents.
+    $breadcrumb->addCacheContexts(['url.path.parent']);
     while (count($path_elements) > 1) {
       array_pop($path_elements);
       // Copy the path elements for up-casting.
diff --git a/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php b/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php
index 29f0cea..eb095a7 100644
--- a/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php
+++ b/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php
@@ -142,7 +142,7 @@ public function testBuildOnFrontpage() {
 
     $breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
     $this->assertEquals([], $breadcrumb->getLinks());
-    $this->assertEquals(['url.path'], $breadcrumb->getCacheContexts());
+    $this->assertEquals(['url.path.parent'], $breadcrumb->getCacheContexts());
     $this->assertEquals([], $breadcrumb->getCacheTags());
     $this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
   }
@@ -159,7 +159,7 @@ public function testBuildWithOnePathElement() {
 
     $breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
     $this->assertEquals([0 => new Link('Home', new Url('<front>'))], $breadcrumb->getLinks());
-    $this->assertEquals(['url.path'], $breadcrumb->getCacheContexts());
+    $this->assertEquals(['url.path.parent'], $breadcrumb->getCacheContexts());
     $this->assertEquals([], $breadcrumb->getCacheTags());
     $this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
   }
@@ -194,7 +194,7 @@ public function testBuildWithTwoPathElements() {
 
     $breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
     $this->assertEquals([0 => new Link('Home', new Url('<front>')), 1 => new Link('Example', new Url('example'))], $breadcrumb->getLinks());
-    $this->assertEquals(['url.path', 'user.permissions'], $breadcrumb->getCacheContexts());
+    $this->assertEquals(['url.path.parent', 'user.permissions'], $breadcrumb->getCacheContexts());
     $this->assertEquals([], $breadcrumb->getCacheTags());
     $this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
   }
@@ -245,7 +245,7 @@ public function testBuildWithThreePathElements() {
       new Link('Example', new Url('example')),
       new Link('Bar', new Url('example_bar')),
     ], $breadcrumb->getLinks());
-    $this->assertEquals(['bar', 'url.path', 'user.permissions'], $breadcrumb->getCacheContexts());
+    $this->assertEquals(['bar', 'url.path.parent', 'user.permissions'], $breadcrumb->getCacheContexts());
     $this->assertEquals(['example'], $breadcrumb->getCacheTags());
     $this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
   }
@@ -272,7 +272,7 @@ public function testBuildWithException($exception_class, $exception_argument) {
 
     // No path matched, though at least the frontpage is displayed.
     $this->assertEquals([0 => new Link('Home', new Url('<front>'))], $breadcrumb->getLinks());
-    $this->assertEquals(['url.path'], $breadcrumb->getCacheContexts());
+    $this->assertEquals(['url.path.parent'], $breadcrumb->getCacheContexts());
     $this->assertEquals([], $breadcrumb->getCacheTags());
     $this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
   }
@@ -316,7 +316,7 @@ public function testBuildWithNonProcessedPath() {
 
     // No path matched, though at least the frontpage is displayed.
     $this->assertEquals([0 => new Link('Home', new Url('<front>'))], $breadcrumb->getLinks());
-    $this->assertEquals(['url.path'], $breadcrumb->getCacheContexts());
+    $this->assertEquals(['url.path.parent'], $breadcrumb->getCacheContexts());
     $this->assertEquals([], $breadcrumb->getCacheTags());
     $this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
   }
@@ -364,7 +364,7 @@ public function testBuildWithUserPath() {
 
     $breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
     $this->assertEquals([0 => new Link('Home', new Url('<front>')), 1 => new Link('Admin', new Url('user_page'))], $breadcrumb->getLinks());
-    $this->assertEquals(['url.path', 'user.permissions'], $breadcrumb->getCacheContexts());
+    $this->assertEquals(['url.path.parent', 'user.permissions'], $breadcrumb->getCacheContexts());
     $this->assertEquals([], $breadcrumb->getCacheTags());
     $this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
   }
diff --git a/core/tests/Drupal/FunctionalTests/Breadcrumb/Breadcrumb404Test.php b/core/tests/Drupal/FunctionalTests/Breadcrumb/Breadcrumb404Test.php
new file mode 100644
index 0000000..a0a3701
--- /dev/null
+++ b/core/tests/Drupal/FunctionalTests/Breadcrumb/Breadcrumb404Test.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\FunctionalTests\Breadcrumb;
+
+use Drupal\simpletest\BlockCreationTrait;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests the breadcrumb of 404 pages.
+ *
+ * @group breadcrumb
+ */
+class Breadcrumb404Test extends BrowserTestBase {
+
+  use BlockCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['system', 'block'];
+
+  public function testBreadcrumbOn404Pages() {
+    $this->placeBlock('system_breadcrumb_block', ['id' => 'breadcrumb']);
+
+    // Prone the cache first.
+    $this->drupalGet('/not-found-1');
+    $base_count = count($this->getBreadcrumbCacheEntries());
+
+    $this->drupalGet('/not-found-2');
+    $next_count = count($this->getBreadcrumbCacheEntries());
+    $this->assertEquals($base_count, $next_count);
+
+    $this->drupalGet('/not-found-3');
+    $next_count = count($this->getBreadcrumbCacheEntries());
+    $this->assertEquals($base_count, $next_count);
+  }
+
+  protected function getBreadcrumbCacheEntries() {
+    $database = \Drupal::database();
+    $cache_entries = $database->select('cache_render')
+      ->fields('cache_render')
+      ->condition('cid', $database->escapeLike('entity_view:block:breadcrumb') . '%', 'LIKE')
+      ->execute()
+      ->fetchAllAssoc('cid');
+    return $cache_entries;
+  }
+
+}
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 7f28c29..1155b15 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -421,6 +421,16 @@
 # $settings['omit_vary_cookie'] = TRUE;
 
 /**
+ * Cache TTL for 404 responses.
+ *
+ * Items cached per-URL tend to result in a large number of cache items, and
+ * this can be problematic on 404 pages which by their nature are unbounded. A
+ * fixed TTL can be set for these items, defaulting to 24 hours, so that cache
+ * backends which do not support LRU can purge older entries.
+ */
+# $settings['404_cache_ttl'] = 86400;
+
+/**
  * Class Loader.
  *
  * If the APC extension is detected, the Symfony APC class loader is used for
