diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 83aa307..627b59d 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -1660,9 +1660,6 @@ function _drupal_bootstrap_page_cache() {
       $response->send();
       exit;
     }
-    else {
-      drupal_add_http_header('X-Drupal-Cache', 'MISS');
-    }
   }
 }
 
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 107e515..e41040b 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3010,12 +3010,6 @@ function drupal_page_set_cache(Response $response, Request $request) {
 
     $cache->data['headers'] = $response->headers->all();
 
-    // Hack: exclude the x-drupal-cache header; it may make it in here because
-    // of awkwardness in how we defer sending it over in _drupal_page_get_cache.
-    if (isset($cache->data['headers']['x-drupal-cache'])) {
-      unset($cache->data['headers']['x-drupal-cache']);
-    }
-
      // Use the actual timestamp from an Expires header, if available.
     if ($date = $response->getExpires()) {
       $date = DrupalDateTime::createFromDateTime($date);
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index 8e4596f..c12b252 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -9,6 +9,8 @@
 
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManager;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\StreamedResponse;
 use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
 use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
@@ -101,7 +103,9 @@ public function onRespond(FilterResponseEvent $event) {
     }
 
     $max_age = \Drupal::config('system.performance')->get('cache.page.max_age');
-    if ($max_age > 0 && ($cache = drupal_page_set_cache($response, $request))) {
+    $is_uncacheable_file_response = ($response instanceof BinaryFileResponse || $response instanceof StreamedResponse);
+    if ($max_age > 0 && !$is_uncacheable_file_response && ($cache = drupal_page_set_cache($response, $request))) {
+      $response->headers->set('X-Drupal-Cache', 'MISS');
       drupal_serve_page_from_cache($cache, $response, $request);
     }
     else {
diff --git a/core/modules/file/lib/Drupal/file/Tests/DownloadTest.php b/core/modules/file/lib/Drupal/file/Tests/DownloadTest.php
index e39b39c..8baa3f2 100644
--- a/core/modules/file/lib/Drupal/file/Tests/DownloadTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/DownloadTest.php
@@ -53,7 +53,27 @@ function testPublicFileTransfer() {
   /**
    * Test the private file transfer system.
    */
-  function testPrivateFileTransfer() {
+  public function testPrivateFileTransfer() {
+    $this->doPrivateFileTransferTest();
+  }
+
+  /**
+   * Test the private file transfer system with page cache.
+   */
+  public function testPrivateFileTransferWithPageCache() {
+    // Turn on page caching and rerun the test.
+    $config = \Drupal::config('system.performance');
+    $config->set('cache.page.use_internal', 1);
+    $config->set('cache.page.max_age', 300);
+    $config->save();
+
+    $this->doPrivateFileTransferTest();
+  }
+
+  /**
+   * Test the private file transfer system.
+   */
+  protected function doPrivateFileTransferTest() {
     // Set file downloads to private so handler functions get called.
 
     // Create a file.
@@ -64,8 +84,8 @@ function testPrivateFileTransfer() {
     // Set file_test access header to allow the download.
     file_test_set_return('download', array('x-foo' => 'Bar'));
     $this->drupalGet($url);
-    $headers = $this->drupalGetHeaders();
-    $this->assertEqual($headers['x-foo'], 'Bar', 'Found header set by file_test module on private download.');
+    $this->assertEqual($this->drupalGetHeader('x-foo'), 'Bar', 'Found header set by file_test module on private download.');
+    $this->assertFalse($this->drupalGetHeader('x-drupal-cache'), 'Page cache is disabled on private file download.');
     $this->assertResponse(200, 'Correctly allowed access to a file when file_test provides headers.');
 
     // Test that the file transferred correctly.
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
index 5b0c2d6..4b16530 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
@@ -85,6 +85,22 @@ function testImageStyleUrlAndPathPrivateUnclean() {
   }
 
   /**
+   * Tests an image style URL using the "public://" scheme and page cache.
+   */
+  public function testImageStyleUrlAndPathPublicWithPageCache() {
+    $this->enablePageCache();
+    $this->doImageStyleUrlAndPathTests('public');
+  }
+
+  /**
+   * Tests an image style URL using the "private://" scheme and page cache.
+   */
+  public function testImageStyleUrlAndPathPrivateWithPageCache() {
+    $this->enablePageCache();
+    $this->doImageStyleUrlAndPathTests('private');
+  }
+
+  /**
    * Tests an image style URL with a file URL that has an extra slash in it.
    */
   function testImageStyleUrlExtraSlash() {
@@ -232,4 +248,15 @@ function doImageStyleUrlAndPathTests($scheme, $clean_url = TRUE, $extra_slash =
     $this->assertResponse(200, 'Image was accessible at the URL with a missing token.');
   }
 
+  /**
+   * Turn on page caching.
+   */
+  protected function enablePageCache() {
+    // Turn on page caching and rerun the test.
+    $config = \Drupal::config('system.performance');
+    $config->set('cache.page.use_internal', 1);
+    $config->set('cache.page.max_age', 300);
+    $config->save();
+  }
+
 }
