 .../tests/src/Functional/BasicAuthTest.php         |  4 ++--
 .../EventSubscriber/DynamicPageCacheSubscriber.php | 10 +++++++--
 .../Functional/DynamicPageCacheIntegrationTest.php | 18 +++++++--------
 .../tests/src/Functional/LayoutSectionTest.php     | 11 ++++-----
 .../page_cache/src/StackMiddleware/PageCache.php   | 12 ++++++++--
 .../tests/src/Functional/PageCacheTest.php         | 26 ++++++++++++++++++----
 .../EntityResource/EntityResourceTestBase.php      |  8 +++----
 .../rest/tests/src/Functional/ResourceTestBase.php |  6 +++--
 .../standard/tests/src/Functional/StandardTest.php |  2 +-
 9 files changed, 66 insertions(+), 31 deletions(-)

diff --git a/core/modules/basic_auth/tests/src/Functional/BasicAuthTest.php b/core/modules/basic_auth/tests/src/Functional/BasicAuthTest.php
index f844cb9..de176db 100644
--- a/core/modules/basic_auth/tests/src/Functional/BasicAuthTest.php
+++ b/core/modules/basic_auth/tests/src/Functional/BasicAuthTest.php
@@ -41,7 +41,7 @@ public function testBasicAuth() {
     $this->assertText($account->getUsername(), 'Account name is displayed.');
     $this->assertResponse('200', 'HTTP response is OK');
     $this->mink->resetSessions();
-    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
+    $this->assertSame('UNCACHEABLE (request policy)', $this->drupalGetHeader('X-Drupal-Cache'));
     $this->assertIdentical(strpos($this->drupalGetHeader('Cache-Control'), 'public'), FALSE, 'Cache-Control is not set to public');
 
     $this->basicAuthGet($url, $account->getUsername(), $this->randomMachineName());
@@ -69,7 +69,7 @@ public function testBasicAuth() {
     $this->drupalGet($url);
     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
     $this->basicAuthGet($url, $account->getUsername(), $account->pass_raw);
-    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
+    $this->assertSame('UNCACHEABLE (request policy)', $this->drupalGetHeader('X-Drupal-Cache'));
     $this->assertIdentical(strpos($this->drupalGetHeader('Cache-Control'), 'public'), FALSE, 'No page cache response when requesting a cached page with basic auth credentials.');
   }
 
diff --git a/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php b/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php
index 8316470..60b9e0d 100644
--- a/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php
+++ b/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php
@@ -156,6 +156,7 @@ public function onResponse(FilterResponseEvent $event) {
     // access and modify the cacheability metadata associated with the
     // response.)
     if (!$response instanceof CacheableResponseInterface) {
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (no cacheability)');
       return;
     }
 
@@ -167,7 +168,7 @@ public function onResponse(FilterResponseEvent $event) {
     // There's no work left to be done if this is an uncacheable response.
     if (!$this->shouldCacheResponse($response)) {
       // The response is uncacheable, mark it as such.
-      $response->headers->set(self::HEADER, 'UNCACHEABLE');
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (poor cacheability)');
       return;
     }
 
@@ -190,7 +191,12 @@ public function onResponse(FilterResponseEvent $event) {
     // Don't cache the response if the Dynamic Page Cache request & response
     // policies are not met.
     // @see onRouteMatch()
-    if ($this->requestPolicyResults[$request] === RequestPolicyInterface::DENY || $this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
+    if ($this->requestPolicyResults[$request] === RequestPolicyInterface::DENY) {
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (request policy)');
+      return;
+    }
+    if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (response policy)');
       return;
     }
 
diff --git a/core/modules/dynamic_page_cache/tests/src/Functional/DynamicPageCacheIntegrationTest.php b/core/modules/dynamic_page_cache/tests/src/Functional/DynamicPageCacheIntegrationTest.php
index ffa8f43..232b5b5 100644
--- a/core/modules/dynamic_page_cache/tests/src/Functional/DynamicPageCacheIntegrationTest.php
+++ b/core/modules/dynamic_page_cache/tests/src/Functional/DynamicPageCacheIntegrationTest.php
@@ -51,7 +51,7 @@ public function testDynamicPageCache() {
     // Cache.
     $url = Url::fromUri('route:dynamic_page_cache_test.response');
     $this->drupalGet($url);
-    $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response object returned: Dynamic Page Cache is ignoring.');
+    $this->assertSame('UNCACHEABLE (no cacheability)', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response object returned: Dynamic Page Cache is ignoring.');
 
     // Controllers returning CacheableResponseInterface (cacheable response)
     // objects are handled by Dynamic Page Cache.
@@ -95,27 +95,27 @@ public function testDynamicPageCache() {
     // response, are ignored by Dynamic Page Cache (but only because those
     // wrapper formats' responses do not implement CacheableResponseInterface).
     $this->drupalGet('dynamic-page-cache-test/html', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']]);
-    $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as AJAX response: Dynamic Page Cache is ignoring.');
+    $this->assertSame('UNCACHEABLE (no cacheability)', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as AJAX response: Dynamic Page Cache is ignoring.');
     $this->drupalGet('dynamic-page-cache-test/html', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog']]);
-    $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as dialog response: Dynamic Page Cache is ignoring.');
+    $this->assertSame('UNCACHEABLE (no cacheability)', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as dialog response: Dynamic Page Cache is ignoring.');
     $this->drupalGet('dynamic-page-cache-test/html', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal']]);
-    $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as modal response: Dynamic Page Cache is ignoring.');
+    $this->assertSame('UNCACHEABLE (no cacheability)', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as modal response: Dynamic Page Cache is ignoring.');
 
     // Admin routes are ignored by Dynamic Page Cache.
     $this->drupalGet('dynamic-page-cache-test/html/admin');
-    $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, rendered as HTML response, admin route: Dynamic Page Cache is ignoring');
+    $this->assertSame('UNCACHEABLE (response policy)', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, rendered as HTML response, admin route: Dynamic Page Cache is ignoring');
     $this->drupalGet('dynamic-page-cache-test/response/admin');
-    $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, plain response, admin route: Dynamic Page Cache is ignoring');
+    $this->assertSame('UNCACHEABLE (no cacheability)', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, plain response, admin route: Dynamic Page Cache is ignoring');
     $this->drupalGet('dynamic-page-cache-test/cacheable-response/admin');
-    $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, cacheable response, admin route: Dynamic Page Cache is ignoring');
+    $this->assertSame('UNCACHEABLE (response policy)', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, cacheable response, admin route: Dynamic Page Cache is ignoring');
 
     // Max-age = 0 responses are ignored by Dynamic Page Cache.
     $this->drupalGet('dynamic-page-cache-test/html/uncacheable/max-age');
-    $this->assertEqual('UNCACHEABLE', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response, but uncacheable: Dynamic Page Cache is running, but not caching.');
+    $this->assertSame('UNCACHEABLE (poor cacheability)', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response, but uncacheable: Dynamic Page Cache is running, but not caching.');
 
     // 'user' cache context responses are ignored by Dynamic Page Cache.
     $this->drupalGet('dynamic-page-cache-test/html/uncacheable/contexts');
-    $this->assertEqual('UNCACHEABLE', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response, but uncacheable: Dynamic Page Cache is running, but not caching.');
+    $this->assertSame('UNCACHEABLE (poor cacheability)', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response, but uncacheable: Dynamic Page Cache is running, but not caching.');
 
     // 'current-temperature' cache tag responses are ignored by Dynamic Page
     // Cache.
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
index d5e0c75..7f9ce7c 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
@@ -79,7 +79,7 @@ public function providerTestLayoutSectionFormatter() {
       ],
       'user',
       'user:2',
-      'UNCACHEABLE',
+      'UNCACHEABLE (poor cacheability)',
     ];
     $data['block_with_entity_context'] = [
       [
@@ -173,7 +173,7 @@ public function testLayoutSectionFormatter($layout_data, $expected_selector, $ex
     $this->assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, $expected_dynamic_cache);
 
     $this->drupalGet($canonical_url->toString() . '/layout');
-    $this->assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, 'UNCACHEABLE');
+    $this->assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, 'UNCACHEABLE (poor cacheability)');
   }
 
   /**
@@ -194,14 +194,14 @@ public function testLayoutSectionFormatterAccess() {
     $this->container->get('state')->set('test_block_access', FALSE);
 
     $this->drupalGet($node->toUrl('canonical'));
-    $this->assertLayoutSection('.layout--onecol', NULL, '', '', 'UNCACHEABLE');
+    $this->assertLayoutSection('.layout--onecol', NULL, '', '', 'UNCACHEABLE (poor cacheability)');
     // Ensure the block was not rendered.
     $this->assertSession()->pageTextNotContains('Hello test world');
 
     // Grant access to the block, and ensure it was rendered.
     $this->container->get('state')->set('test_block_access', TRUE);
     $this->drupalGet($node->toUrl('canonical'));
-    $this->assertLayoutSection('.layout--onecol', 'Hello test world', '', '', 'UNCACHEABLE');
+    $this->assertLayoutSection('.layout--onecol', 'Hello test world', '', '', 'UNCACHEABLE (poor cacheability)');
   }
 
   /**
@@ -331,7 +331,8 @@ public function testLayoutDeletingBundle() {
    * @param string $expected_cache_tags
    *   A string of cache tags to be found in the header.
    * @param string $expected_dynamic_cache
-   *   The expected dynamic cache header. Either 'HIT', 'MISS' or 'UNCACHEABLE'.
+   *   The expected dynamic cache header. Either 'HIT', 'MISS' or
+   *   'UNCACHEABLE (poor cacheability)'.
    */
   protected function assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts = '', $expected_cache_tags = '', $expected_dynamic_cache = 'MISS') {
     $assert_session = $this->assertSession();
diff --git a/core/modules/page_cache/src/StackMiddleware/PageCache.php b/core/modules/page_cache/src/StackMiddleware/PageCache.php
index 82456b1..86a0210 100644
--- a/core/modules/page_cache/src/StackMiddleware/PageCache.php
+++ b/core/modules/page_cache/src/StackMiddleware/PageCache.php
@@ -20,6 +20,11 @@
 class PageCache implements HttpKernelInterface {
 
   /**
+   * Name of Page Cache's response header.
+   */
+  const HEADER = 'X-Drupal-Cache';
+
+  /**
    * The wrapped HTTP kernel.
    *
    * @var \Symfony\Component\HttpKernel\HttpKernelInterface
@@ -76,6 +81,7 @@ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch =
     }
     else {
       $response = $this->pass($request, $type, $catch);
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (request policy)');
     }
 
     return $response;
@@ -115,7 +121,7 @@ protected function pass(Request $request, $type = self::MASTER_REQUEST, $catch =
    */
   protected function lookup(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
     if ($response = $this->get($request)) {
-      $response->headers->set('X-Drupal-Cache', 'HIT');
+      $response->headers->set(static::HEADER, 'HIT');
     }
     else {
       $response = $this->fetch($request, $type, $catch);
@@ -186,7 +192,7 @@ protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch
     // Only set the 'X-Drupal-Cache' header if caching is allowed for this
     // response.
     if ($this->storeResponse($request, $response)) {
-      $response->headers->set('X-Drupal-Cache', 'MISS');
+      $response->headers->set(static::HEADER, 'MISS');
     }
 
     return $response;
@@ -225,6 +231,7 @@ protected function storeResponse(Request $request, Response $response) {
     //   so by replacing/extending this middleware service or adding another
     //   one.
     if (!$response instanceof CacheableResponseInterface) {
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (no cacheability)');
       return FALSE;
     }
 
@@ -238,6 +245,7 @@ protected function storeResponse(Request $request, Response $response) {
 
     // Allow policy rules to further restrict which responses to cache.
     if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (response policy)');
       return FALSE;
     }
 
diff --git a/core/modules/page_cache/tests/src/Functional/PageCacheTest.php b/core/modules/page_cache/tests/src/Functional/PageCacheTest.php
index bf4951d..37cad54 100644
--- a/core/modules/page_cache/tests/src/Functional/PageCacheTest.php
+++ b/core/modules/page_cache/tests/src/Functional/PageCacheTest.php
@@ -488,22 +488,22 @@ public function testCacheableResponseResponses() {
 
     // GET a URL, which would be marked as a cache miss if it were cacheable.
     $this->drupalGet('/system-test/respond-reponse');
-    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.');
+    $this->assertSame('UNCACHEABLE (no cacheability)', $this->drupalGetHeader('X-Drupal-Cache'));
     $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, private', 'Cache-Control header was sent');
 
     // GET it again, verify it's still not cached.
     $this->drupalGet('/system-test/respond-reponse');
-    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.');
+    $this->assertSame('UNCACHEABLE (no cacheability)', $this->drupalGetHeader('X-Drupal-Cache'));
     $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, private', 'Cache-Control header was sent');
 
     // GET a URL, which would be marked as a cache miss if it were cacheable.
     $this->drupalGet('/system-test/respond-public-response');
-    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.');
+    $this->assertSame('UNCACHEABLE (no cacheability)', $this->drupalGetHeader('X-Drupal-Cache'));
     $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=60, public', 'Cache-Control header was sent');
 
     // GET it again, verify it's still not cached.
     $this->drupalGet('/system-test/respond-public-response');
-    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.');
+    $this->assertSame('UNCACHEABLE (no cacheability)', $this->drupalGetHeader('X-Drupal-Cache'));
     $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=60, public', 'Cache-Control header was sent');
 
     // GET a URL, which should be marked as a cache miss.
@@ -527,6 +527,24 @@ public function testCacheableResponseResponses() {
   }
 
   /**
+   * Tests uncacheable responses' X-Drupal-Cache response headers.
+   */
+  public function testUncacheableResponseHeader() {
+    // A controller that responds with a cacheable response, but the policy
+    // \Drupal\Core\PageCache\ResponsePolicy\DenyNoCacheRoutes prevents caching.
+    $this->drupalGet(Url::fromRoute('system_test.lock_acquire'));
+    $this->assertSame('UNCACHEABLE (response policy)', $this->drupalGetHeader('X-Drupal-Cache'));
+
+    // A controller that responds with an uncacheable response.
+    $this->drupalGet(Url::fromRoute('system_test.date'));
+    $this->assertSame('UNCACHEABLE (no cacheability)', $this->drupalGetHeader('X-Drupal-Cache'));
+
+    $this->drupalLogin($this->drupalCreateUser());
+    $this->drupalGet(Url::fromRoute('system_test.date'));
+    $this->assertSame('UNCACHEABLE (request policy)', $this->drupalGetHeader('X-Drupal-Cache'));
+  }
+
+  /**
    * Tests that HEAD requests are treated the same as GET requests.
    */
   public function testHead() {
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
index d1ac74d..291c83e 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
@@ -929,7 +929,7 @@ public function testPost() {
     else {
       $this->assertSame([], $response->getHeader('Location'));
     }
-    $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
+    $this->assertSame(['UNCACHEABLE (request policy)'], $response->getHeader('X-Drupal-Cache'));
     // If the entity is stored, perform extra checks.
     if (get_class($this->entityStorage) !== ContentEntityNullStorage::class) {
       // Assert that the entity was indeed created, and that the response body
@@ -980,7 +980,7 @@ public function testPost() {
     else {
       $this->assertSame([], $response->getHeader('Location'));
     }
-    $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
+    $this->assertSame(['UNCACHEABLE (request policy)'], $response->getHeader('X-Drupal-Cache'));
 
     if ($this->entity->getEntityType()->getStorageClass() !== ContentEntityNullStorage::class && $this->entity->getEntityType()->hasKey('uuid')) {
       // 500 when creating an entity with a duplicate UUID.
@@ -1196,7 +1196,7 @@ public function testPatch() {
     // 200 for well-formed request.
     $response = $this->request('PATCH', $url, $request_options);
     $this->assertResourceResponse(200, FALSE, $response);
-    $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
+    $this->assertSame(['UNCACHEABLE (request policy)'], $response->getHeader('X-Drupal-Cache'));
     // Assert that the entity was indeed updated, and that the response body
     // contains the serialized updated entity.
     $updated_entity = $this->entityStorage->loadUnchanged($this->entity->id());
@@ -1249,7 +1249,7 @@ public function testPatch() {
     // 200 for well-formed request.
     $response = $this->request('PATCH', $url, $request_options);
     $this->assertResourceResponse(200, FALSE, $response);
-    $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
+    $this->assertSame(['UNCACHEABLE (request policy)'], $response->getHeader('X-Drupal-Cache'));
   }
 
   /**
diff --git a/core/modules/rest/tests/src/Functional/ResourceTestBase.php b/core/modules/rest/tests/src/Functional/ResourceTestBase.php
index 8283bef..66f67f4 100644
--- a/core/modules/rest/tests/src/Functional/ResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/ResourceTestBase.php
@@ -412,7 +412,8 @@ protected function assertResourceResponse($expected_status_code, $expected_body,
       $this->assertSame($expected_page_cache_header_value, $response->getHeader('X-Drupal-Cache')[0]);
     }
     else {
-      $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
+      $this->assertTrue($response->hasHeader('X-Drupal-Cache'));
+      $this->stringStartsWith('UNCACHEABLE (', $response->getHeader('X-Drupal-Cache')[0]);
     }
 
     // Expected Dynamic Page Cache header value: X-Drupal-Dynamic-Cache header.
@@ -421,7 +422,8 @@ protected function assertResourceResponse($expected_status_code, $expected_body,
       $this->assertSame($expected_dynamic_page_cache_header_value, $response->getHeader('X-Drupal-Dynamic-Cache')[0]);
     }
     else {
-      $this->assertFalse($response->hasHeader('X-Drupal-Dynamic-Cache'));
+      $this->assertTrue($response->hasHeader('X-Drupal-Dynamic-Cache'));
+      $this->stringStartsWith('UNCACHEABLE (', $response->getHeader('X-Drupal-Dynamic-Cache')[0]);
     }
   }
 
diff --git a/core/profiles/standard/tests/src/Functional/StandardTest.php b/core/profiles/standard/tests/src/Functional/StandardTest.php
index f449c10..a2c9329 100644
--- a/core/profiles/standard/tests/src/Functional/StandardTest.php
+++ b/core/profiles/standard/tests/src/Functional/StandardTest.php
@@ -181,7 +181,7 @@ public function testStandard() {
     $this->drupalLogin($this->adminUser);
     $url = Url::fromRoute('contact.site_page');
     $this->drupalGet($url);
-    $this->assertEqual('UNCACHEABLE', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Site-wide contact page cannot be cached by Dynamic Page Cache.');
+    $this->assertEqual('UNCACHEABLE (poor cacheability)', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Site-wide contact page cannot be cached by Dynamic Page Cache.');
 
     $url = Url::fromRoute('<front>');
     $this->drupalGet($url);
