diff --git a/core/lib/Drupal/Core/Render/CacheableStreamedResponseInterface.php b/core/lib/Drupal/Core/Render/CacheableStreamedResponseInterface.php
new file mode 100644
index 0000000..d521d52
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/CacheableStreamedResponseInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Core\Render;
+
+/**
+ * Defines an interface for cacheable streamed responses.
+ *
+ * @internal
+ * @todo Make public once code/modules other than BigPipe start using this.
+ * @todo https://www.drupal.org/node/2577631 will introduce
+ *   StreamedResponseInterface, consider making this interface extend that one.
+ */
+interface CacheableStreamedResponseInterface {
+
+  /**
+   * Returns the entire streamed response for caching, if possible.
+   *
+   * The returned response has had the RESPONSE event dispatched for it.
+   *
+   * @see \Symfony\Component\HttpKernel\KernelEvents::RESPONSE
+   * @see \Drupal\Core\Render\CacheableStreamedResponseEventDispatcherTrait
+   *
+   * @return FALSE|\Drupal\Core\Cache\CacheableResponseInterface
+   */
+  public function getCacheableStreamedResponse();
+
+}
diff --git a/core/modules/page_cache/src/StackMiddleware/PageCache.php b/core/modules/page_cache/src/StackMiddleware/PageCache.php
index a4d9053..c8b1fcf 100644
--- a/core/modules/page_cache/src/StackMiddleware/PageCache.php
+++ b/core/modules/page_cache/src/StackMiddleware/PageCache.php
@@ -7,17 +7,19 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\PageCache\RequestPolicyInterface;
 use Drupal\Core\PageCache\ResponsePolicyInterface;
+use Drupal\Core\Render\CacheableStreamedResponseInterface;
 use Drupal\Core\Site\Settings;
 use Symfony\Component\HttpFoundation\BinaryFileResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\StreamedResponse;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\TerminableInterface;
 
 /**
  * Executes the page caching before the main kernel takes over the request.
  */
-class PageCache implements HttpKernelInterface {
+class PageCache implements HttpKernelInterface, TerminableInterface {
 
   /**
    * The wrapped HTTP kernel.
@@ -81,6 +83,24 @@ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch =
     return $response;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function terminate(Request $request, Response $response) {
+    // Also cache streamed responses, if they support it.
+    if ($response instanceof CacheableStreamedResponseInterface) {
+      $streamed_response = $response->getCacheableStreamedResponse();
+      if ($streamed_response !== FALSE) {
+        $this->storeResponse($request, $streamed_response);
+      }
+    }
+
+    // Delegate, per https://github.com/stackphp/builder/issues/14.
+    if ($this->httpKernel instanceof TerminableInterface) {
+      $this->httpKernel->terminate($request, $response);
+    }
+  }
+
   /**
    * Sidesteps the page cache and directly forwards a request to the backend.
    *
@@ -206,6 +226,39 @@ protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch
     /** @var \Symfony\Component\HttpFoundation\Response $response */
     $response = $this->httpKernel->handle($request, $type, $catch);
 
+    // The storing of cacheable streamed responses is deferred to terminate().
+    // The 'X-Drupal-Cache' header must be set here however, since the response
+    // is already sent to the client before terminate() is called.
+    // @see ::terminate()
+    if ($response instanceof CacheableStreamedResponseInterface) {
+      // Mark streamed response as a cache miss, but only if caching is allowed
+      // for this streamed response.
+      if ($this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY) {
+        $response->headers->set('X-Drupal-Cache', 'MISS');
+      }
+      return $response;
+    }
+
+    // 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');
+    }
+
+    return $response;
+  }
+
+  /**
+   * Stores a response in the page cache.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   A request object.
+   * @param \Symfony\Component\HttpFoundation\Response $response
+   *   A response object that should be stored in the page cache.
+   *
+   * @returns bool
+   */
+  protected function storeResponse(Request $request, Response $response) {
     // Drupal's primary cache invalidation architecture is cache tags: any
     // response that varies by a configuration value or data in a content
     // entity should have cache tags, to allow for instant cache invalidation
@@ -228,7 +281,7 @@ protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch
     //   so by replacing/extending this middleware service or adding another
     //   one.
     if (!$response instanceof CacheableResponseInterface) {
-      return $response;
+      return FALSE;
     }
 
     // Currently it is not possible to cache binary file or streamed responses:
@@ -236,12 +289,12 @@ protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch
     // Therefore exclude them, even for subclasses that implement
     // CacheableResponseInterface.
     if ($response instanceof BinaryFileResponse || $response instanceof StreamedResponse) {
-      return $response;
+      return FALSE;
     }
 
     // Allow policy rules to further restrict which responses to cache.
     if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
-      return $response;
+      return FALSE;
     }
 
     $request_time = $request->server->get('REQUEST_TIME');
@@ -273,10 +326,7 @@ protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch
       $this->set($request, $response, $expire, $tags);
     }
 
-    // Mark response as a cache miss.
-    $response->headers->set('X-Drupal-Cache', 'MISS');
-
-    return $response;
+    return TRUE;
   }
 
   /**
diff --git a/core/modules/page_cache/src/Tests/PageCacheTest.php b/core/modules/page_cache/src/Tests/PageCacheTest.php
index 22b8b51..aae6c61 100644
--- a/core/modules/page_cache/src/Tests/PageCacheTest.php
+++ b/core/modules/page_cache/src/Tests/PageCacheTest.php
@@ -24,7 +24,7 @@ class PageCacheTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = array('test_page_test', 'system_test', 'entity_test');
+  public static $modules = array('test_page_test', 'system_test', 'entity_test', 'page_cache_test');
 
   /**
    * {@inheritdoc}
@@ -510,4 +510,36 @@ public function testCacheableResponseResponses() {
     $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.');
   }
 
+  /**
+   * Tests Page Cache's support for streamed responses.
+   *
+   * @see \Drupal\page_cache\StackMiddleware\PageCache::terminate()
+   */
+  public function testStreamedResponses() {
+    // Symfony streamed response: not even considered.
+    $this->drupalGet(Url::fromRoute('page_cache_test.symfony_streamed_response'));
+    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
+    $this->drupalGet(Url::fromRoute('page_cache_test.symfony_streamed_response'));
+    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
+
+    // StreamedResponseInterface Response, with untracked stream: always cache
+    // miss.
+    $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.untracked'));
+    $this->assertIdentical('MISS', $this->drupalGetHeader('X-Drupal-Cache'));
+    $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.untracked'));
+    $this->assertIdentical('MISS', $this->drupalGetHeader('X-Drupal-Cache'));
+
+    // StreamedResponseInterface Response, with tracked stream: cacheable.
+    $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.tracked'));
+    $this->assertIdentical('MISS', $this->drupalGetHeader('X-Drupal-Cache'));
+    $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.tracked'));
+    $this->assertIdentical('HIT', $this->drupalGetHeader('X-Drupal-Cache'));
+    // Except when a response policy says it cannot be cached by PageCache, such
+    // as \Drupal\Core\PageCache\ResponsePolicy\DenyNoCacheRoutes.
+    $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.tracked.no_cache'));
+    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.');
+    $this->drupalGet(Url::fromRoute('page_cache_test.streamed_response_interface.tracked.no_cache'));
+    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.');
+  }
+
 }
diff --git a/core/modules/page_cache/tests/modules/page_cache_form_test.info.yml b/core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.info.yml
similarity index 100%
rename from core/modules/page_cache/tests/modules/page_cache_form_test.info.yml
rename to core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.info.yml
diff --git a/core/modules/page_cache/tests/modules/page_cache_form_test.install b/core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.install
similarity index 100%
rename from core/modules/page_cache/tests/modules/page_cache_form_test.install
rename to core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.install
diff --git a/core/modules/page_cache/tests/modules/page_cache_form_test.module b/core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.module
similarity index 100%
rename from core/modules/page_cache/tests/modules/page_cache_form_test.module
rename to core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.module
diff --git a/core/modules/page_cache/tests/modules/page_cache_form_test.routing.yml b/core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.routing.yml
similarity index 100%
rename from core/modules/page_cache/tests/modules/page_cache_form_test.routing.yml
rename to core/modules/page_cache/tests/modules/page_cache_form_test/page_cache_form_test.routing.yml
diff --git a/core/modules/page_cache/tests/modules/src/Form/TestForm.php b/core/modules/page_cache/tests/modules/page_cache_form_test/src/Form/TestForm.php
similarity index 100%
rename from core/modules/page_cache/tests/modules/src/Form/TestForm.php
rename to core/modules/page_cache/tests/modules/page_cache_form_test/src/Form/TestForm.php
diff --git a/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.info.yml b/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.info.yml
new file mode 100644
index 0000000..bef43eb
--- /dev/null
+++ b/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.info.yml
@@ -0,0 +1,6 @@
+name: 'Page Cache Test'
+type: module
+description: 'Support module for the Page Cache module tests.'
+core: 8.x
+package: Testing
+version: VERSION
diff --git a/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.routing.yml b/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.routing.yml
new file mode 100644
index 0000000..d1e4baf
--- /dev/null
+++ b/core/modules/page_cache/tests/modules/page_cache_test/page_cache_test.routing.yml
@@ -0,0 +1,27 @@
+page_cache_test.symfony_streamed_response:
+  path: '/page_cache_test/symfony_streamedresponse'
+  defaults:
+    _controller: '\Drupal\page_cache_test\PageCacheTestController::symfonyStreamedResponse'
+  requirements:
+    _access: 'TRUE'
+
+page_cache_test.streamed_response_interface.untracked:
+  path: '/page_cache_test/streamed_response_interface/untracked'
+  defaults:
+    _controller: '\Drupal\page_cache_test\PageCacheTestController::streamedResponseInterfaceUntracked'
+  requirements:
+    _access: 'TRUE'
+page_cache_test.streamed_response_interface.tracked:
+  path: '/page_cache_test/streamed_response_interface/tracked'
+  defaults:
+    _controller: '\Drupal\page_cache_test\PageCacheTestController::streamedResponseInterfaceTracked'
+  requirements:
+    _access: 'TRUE'
+page_cache_test.streamed_response_interface.tracked.no_cache:
+  path: '/page_cache_test/streamed_response_interface/tracked/nocache'
+  defaults:
+    _controller: '\Drupal\page_cache_test\PageCacheTestController::streamedResponseInterfaceTracked'
+  requirements:
+    _access: 'TRUE'
+  options:
+    no_cache: TRUE
diff --git a/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestCacheableStreamedResponse.php b/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestCacheableStreamedResponse.php
new file mode 100644
index 0000000..3042d6a
--- /dev/null
+++ b/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestCacheableStreamedResponse.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\page_cache_test;
+
+
+use Drupal\Core\Cache\CacheableResponseInterface;
+use Drupal\Core\Cache\CacheableResponseTrait;
+use Drupal\Core\Render\CacheableStreamedResponseInterface;
+use Symfony\Component\HttpFoundation\Response;
+
+class PageCacheTestCacheableStreamedResponse extends Response implements CacheableStreamedResponseInterface, CacheableResponseInterface {
+
+  use CacheableResponseTrait;
+
+  protected $streamedResponse;
+
+  public function __construct($streamed_response) {
+    $this->streamedResponse = $streamed_response;
+    parent::__construct('', 200, []);
+  }
+
+  public function getCacheableStreamedResponse() {
+    return $this->streamedResponse;
+  }
+}
diff --git a/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestController.php b/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestController.php
new file mode 100644
index 0000000..0809cfe
--- /dev/null
+++ b/core/modules/page_cache/tests/modules/page_cache_test/src/PageCacheTestController.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\page_cache_test;
+
+use Drupal\Core\Render\HtmlResponse;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
+class PageCacheTestController {
+
+  /**
+   * Returns a Symfony streamed response.
+   */
+  public function symfonyStreamedResponse() {
+    return new StreamedResponse(function () { echo 'Symfony streamed response'; });
+  }
+
+  /**
+   * Returns a StreamedResponseInterface Response, with untracked stream.
+   */
+  public function streamedResponseInterfaceUntracked() {
+    return new PageCacheTestCacheableStreamedResponse(FALSE);
+  }
+
+  /**
+   * Returns a StreamedResponseInterface Response, with tracked stream.
+   */
+  public function streamedResponseInterfaceTracked() {
+    return new PageCacheTestCacheableStreamedResponse((new HtmlResponse('Hello world!'))->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 UTC')));
+  }
+
+}
