diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 9dd3f56..1f98e87 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -17,6 +17,7 @@
 use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\DependencyInjection\Exception\RuntimeException as DependencyInjectionRuntimeException;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Lock\DatabaseLockBackend;
 use Drupal\Core\Lock\LockBackendInterface;
@@ -1068,6 +1069,8 @@ function drupal_load($type, $name) {
  *   reason phrase, e.g. "404 Not Found".
  * @param $append
  *   Whether to append the value to an existing header or to replace it.
+ *
+ * @deprecated Header handling is being shifted to a Symfony response object.
  */
 function drupal_add_http_header($name, $value, $append = FALSE) {
   // The headers as name/value pairs.
@@ -1087,7 +1090,6 @@ function drupal_add_http_header($name, $value, $append = FALSE) {
   else {
     $headers[$name_lower] = $value;
   }
-  drupal_send_headers(array($name => $headers[$name_lower]), TRUE);
 }
 
 /**
@@ -1100,6 +1102,8 @@ function drupal_add_http_header($name, $value, $append = FALSE) {
  * @return
  *   A string containing the header value, or FALSE if the header has been set,
  *   or NULL if the header has not been set.
+ *
+ * @deprecated Header handling is being shifted to a Symfony response object.
  */
 function drupal_get_http_header($name = NULL) {
   $headers = &drupal_static('drupal_http_headers', array());
@@ -1116,7 +1120,9 @@ function drupal_get_http_header($name = NULL) {
  * Sets the preferred name for the HTTP header.
  *
  * Header names are case-insensitive, but for maximum compatibility they should
- * follow "common form" (see RFC 2617, section 4.2).
+ * follow "common form" (see RFC 2616, section 4.2).
+ *
+ * @deprecated Header handling is being shifted to a Symfony response object.
  */
 function _drupal_set_preferred_header_name($name = NULL) {
   static $header_names = array();
@@ -1138,6 +1144,8 @@ function _drupal_set_preferred_header_name($name = NULL) {
  * @param bool $only_default
  *   (optional) If TRUE and headers have already been sent, send only the
  *   specified headers.
+ *
+ * @deprecated Header handling is being shifted to a Symfony response object.
  */
 function drupal_send_headers($default_headers = array(), $only_default = FALSE) {
   $headers_sent = &drupal_static(__FUNCTION__, FALSE);
@@ -1192,6 +1200,8 @@ function drupal_send_headers($default_headers = array(), $only_default = FALSE)
  * identical.
  *
  * @see drupal_page_set_cache()
+ *
+ * @deprecated Header handling is being shifted to a Symfony response object.
  */
 function drupal_page_header() {
   $headers_sent = &drupal_static(__FUNCTION__, FALSE);
@@ -1220,26 +1230,24 @@ function drupal_page_header() {
  * and the conditions match those currently in the cache, a 304 Not Modified
  * response is sent.
  */
-function drupal_serve_page_from_cache(stdClass $cache) {
+function drupal_serve_page_from_cache(stdClass $cache, Response $response, Request $request) {
   $config = config('system.performance');
 
+  // First half: we must determine if we should be returning a 304.
+
   // Negotiate whether to use compression.
   $page_compression = $config->get('response.gzip') && extension_loaded('zlib');
-  $return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE;
+  $return_compressed = $page_compression && $request->server->has('HTTP_ACCEPT_ENCODING') && strpos($request->server->get('HTTP_ACCEPT_ENCODING'), 'gzip') !== FALSE;
 
   // Get headers. Keys are lower-case.
   $boot_headers = drupal_get_http_header();
 
-  // Headers generated in this function, that may be replaced or unset using
-  // drupal_add_http_headers(). Keys are mixed-case.
-  $default_headers = array();
-
   foreach ($cache->data['headers'] as $name => $value) {
     // In the case of a 304 response, certain headers must be sent, and the
     // remaining may not (see RFC 2616, section 10.3.5).
     $name_lower = strtolower($name);
     if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !isset($boot_headers[$name_lower])) {
-      drupal_add_http_header($name, $value);
+      $response->headers->set($name, $value);
       unset($cache->data['headers'][$name]);
     }
   }
@@ -1248,39 +1256,40 @@ function drupal_serve_page_from_cache(stdClass $cache) {
   // to that one particular client due to Vary: Cookie. Thus, do not set
   // max-age > 0, allowing the page to be cached by external proxies, when a
   // session cookie is present unless the Vary header has been replaced.
-  $max_age = !isset($_COOKIE[session_name()]) || isset($boot_headers['vary']) ? $config->get('cache.page.max_age') : 0;
-  $default_headers['Cache-Control'] = 'public, max-age=' . $max_age;
+  $max_age = !$request->cookies->has(session_name()) || isset($boot_headers['vary']) ? $config->get('cache.page.max_age') : 0;
+  $response->headers->set('Cache-Control', 'public, max-age=' . $max_age);
 
   // Entity tag should change if the output changes.
-  $etag = '"' . $cache->created . '-' . intval($return_compressed) . '"';
-  header('Etag: ' . $etag);
+  $response->setEtag($cache->created);
 
   // See if the client has provided the required HTTP headers.
-  $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE;
-  $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE;
+  $if_modified_since = $request->server->has('HTTP_IF_MODIFIED_SINCE') ? strtotime($request->server->get('HTTP_IF_MODIFIED_SINCE')) : FALSE;
+  $if_none_match = $request->server->has('HTTP_IF_NONE_MATCH') ? stripslashes($request->server->get('HTTP_IF_NONE_MATCH')) : FALSE;
 
   if ($if_modified_since && $if_none_match
-      && $if_none_match == $etag // etag must match
+      && $if_none_match == $response->headers->get('etag') // etag must match
       && $if_modified_since == $cache->created) {  // if-modified-since must match
-    header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
-    drupal_send_headers($default_headers);
+    $response->setStatusCode(304);
     return;
   }
 
+  // Second half: we're not returning a 304, so put in other headers.
+
   // Send the remaining headers.
   foreach ($cache->data['headers'] as $name => $value) {
+    $response->headers->set($name, $value);
     drupal_add_http_header($name, $value);
   }
 
-  $default_headers['Last-Modified'] = gmdate(DATE_RFC1123, $cache->created);
+  $response->setLastModified(\DateTime::createFromFormat('U', $cache->created));
 
   // HTTP/1.0 proxies does not support the Vary header, so prevent any caching
   // by sending an Expires date in the past. HTTP/1.1 clients ignores the
   // Expires header if a Cache-Control: max-age= directive is specified (see RFC
   // 2616, section 14.9.3).
-  $default_headers['Expires'] = 'Sun, 19 Nov 1978 05:00:00 GMT';
-
-  drupal_send_headers($default_headers);
+  if (!$response->getExpires()) {
+    $response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 GMT'));
+  }
 
   // Allow HTTP proxies to cache pages for anonymous users without a session
   // cookie. The Vary header is used to indicates the set of request-header
@@ -1288,17 +1297,17 @@ function drupal_serve_page_from_cache(stdClass $cache) {
   // response to reply to a subsequent request for a given URL without
   // revalidation.
   if (!isset($boot_headers['vary']) && !settings()->get('omit_vary_cookie')) {
-    header('Vary: Cookie');
+    $response->setVary('Cookie', FALSE);
   }
 
   if ($page_compression) {
-    header('Vary: Accept-Encoding', FALSE);
+    $response->setVary('accept-encoding', FALSE);
     // If page_compression is enabled, the cache contains gzipped data.
     if ($return_compressed) {
       // $cache->data['body'] is already gzip'ed, so make sure
       // zlib.output_compression does not compress it once more.
       ini_set('zlib.output_compression', '0');
-      header('Content-Encoding: gzip');
+      $response->headers->set('content-encoding', 'gzip');
     }
     else {
       // The client does not support compression, so unzip the data in the
@@ -1307,8 +1316,7 @@ function drupal_serve_page_from_cache(stdClass $cache) {
     }
   }
 
-  // Print the page.
-  print $cache->data['body'];
+  $response->setContent($cache->data['body']);
 }
 
 /**
@@ -2066,27 +2074,34 @@ function _drupal_bootstrap_page_cache() {
     $config = config('system.performance');
     $cache_enabled = $config->get('cache.page.use_internal');
   }
+
+  // @todo this is *criminal*. but, necessary, until we fix bootstrap ordering.
+  $request = Request::createFromGlobals();
   // If there is no session cookie and cache is enabled (or forced), try
   // to serve a cached page.
-  if (!isset($_COOKIE[session_name()]) && $cache_enabled) {
+  if (!$request->cookies->has(session_name()) && $cache_enabled) {
     // Make sure there is a user object because its timestamp will be checked.
     $user = drupal_anonymous_user();
     // Get the page from the cache.
     $cache = drupal_page_get_cache();
     // If there is a cached page, display it.
     if (is_object($cache)) {
-      header('X-Drupal-Cache: HIT');
+      $response = new Response();
+      $response->headers->set('X-Drupal-Cache', 'HIT');
       // Restore the metadata cached with the page.
       _current_path($cache->data['path']);
       drupal_set_title($cache->data['title'], PASS_THROUGH);
       date_default_timezone_set(drupal_get_user_timezone());
 
-      drupal_serve_page_from_cache($cache);
+      drupal_serve_page_from_cache($cache, $response, $request);
+
       // We are done.
+      $response->prepare($request);
+      $response->send();
       exit;
     }
     else {
-      header('X-Drupal-Cache: MISS');
+      drupal_add_http_header('X-Drupal-Cache', 'MISS');
     }
   }
 }
diff --git a/core/includes/common.inc b/core/includes/common.inc
index c4bf7e2..0d0a8f7 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -8,6 +8,8 @@
 use Drupal\Core\Language\Language;
 use Symfony\Component\DependencyInjection\Container;
 use Symfony\Component\Yaml\Parser;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Request;
 use Drupal\Component\PhpStorage\PhpStorageFactory;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\CacheBackendInterface;
@@ -4396,15 +4398,15 @@ function _drupal_bootstrap_full($skip = FALSE) {
  *
  * @see drupal_page_header()
  */
-function drupal_page_set_cache($body) {
+function drupal_page_set_cache(Response $response, Request $request) {
   global $base_root;
 
   if (drupal_page_is_cacheable()) {
     $cache = (object) array(
-      'cid' => $base_root . request_uri(),
+      'cid' => $base_root . $request->getRequestUri(),
       'data' => array(
-        'path' => current_path(),
-        'body' => $body,
+        'path' => $request->attributes->get('system_path'),
+        'body' => $response->getContent(),
         'title' => drupal_get_title(),
         'headers' => array(),
       ),
@@ -4413,16 +4415,18 @@ function drupal_page_set_cache($body) {
       'created' => REQUEST_TIME,
     );
 
-    // Restore preferred header names based on the lower-case names returned
-    // by drupal_get_http_header().
-    $header_names = _drupal_set_preferred_header_name();
-    foreach (drupal_get_http_header() as $name_lower => $value) {
-      $cache->data['headers'][$header_names[$name_lower]] = $value;
-      if ($name_lower == 'expires') {
-        // Use the actual timestamp from an Expires header if available.
-        $date = new DrupalDateTime($value);
-        $cache->expire = $date->getTimestamp();
-      }
+    $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 = new DrupalDateTime($date);
+      $cache->expire = $date->getTimestamp();
     }
 
     if ($cache->data['body']) {
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index 163d5c8..0b9bbad 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -47,6 +47,7 @@ public function onRespond(FilterResponseEvent $event) {
       return;
     }
 
+    $request = $event->getRequest();
     $response = $event->getResponse();
 
     // Set the X-UA-Compatible HTTP header to force IE to use the most recent
@@ -60,7 +61,7 @@ public function onRespond(FilterResponseEvent $event) {
     // since the page is in fact being regenerated right now.
     // @todo Remove this and use a more intelligent default so that HTTP
     // caching can function properly.
-    $response->headers->set('Last-Modified', gmdate(DATE_RFC1123, REQUEST_TIME));
+    $response->setLastModified(new \DateTime(gmdate(DATE_RFC1123, REQUEST_TIME)));
 
     // Also give each page a unique ETag. This will force clients to include
     // both an If-Modified-Since header and an If-None-Match header when doing
@@ -82,25 +83,31 @@ public function onRespond(FilterResponseEvent $event) {
     // identical.
     // @todo Remove this line as no longer necessary per
     //   http://drupal.org/node/1573064
-    $response->headers->set('ETag', '"' . REQUEST_TIME . '"');
+    $response->setEtag(REQUEST_TIME);
 
     // Authenticated users are always given a 'no-cache' header, and will fetch
     // a fresh page on every request. This prevents authenticated users from
     // seeing locally cached pages.
     // @todo Revisit whether or not this is still appropriate now that the
-    //   Response object does its own cache control procesisng and we intend to
+    //   Response object does its own cache control processing and we intend to
     //   use partial page caching more extensively.
     // Commit the user session, if needed.
     drupal_session_commit();
+
+    // Attach globally-declared headers to the response object so that Symfony
+    // can send them for us correctly.
+    // @todo remove this once we have removed all drupal_add_http_header() calls
+    $headers = drupal_get_http_header();
+    foreach ($headers as $name => $value) {
+      $response->headers->set($name, $value, FALSE);
+    }
+
     $max_age = config('system.performance')->get('cache.page.max_age');
-    if ($max_age > 0 && ($cache = drupal_page_set_cache($response->getContent()))) {
-      drupal_serve_page_from_cache($cache);
-      // drupal_serve_page_from_cache() already printed the response.
-      $response->setContent('');
-      $response->headers->remove('cache-control');
+    if ($max_age > 0 && ($cache = drupal_page_set_cache($response, $request))) {
+      drupal_serve_page_from_cache($cache, $response, $request);
     }
     else {
-      $response->headers->set('Expires', 'Sun, 19 Nov 1978 05:00:00 GMT');
+      $response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 GMT'));
       $response->headers->set('Cache-Control', 'no-cache, must-revalidate, post-check=0, pre-check=0');
     }
   }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index acdec6d..a2bb55a 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -54,16 +54,6 @@
   protected $headers;
 
   /**
-   * Indicates that headers should be dumped if verbose output is enabled.
-   *
-   * Headers are dumped to verbose by drupalGet(), drupalHead(), and
-   * drupalPost().
-   *
-   * @var bool
-   */
-  protected $dumpHeaders = FALSE;
-
-  /**
    * The content of the page currently loaded in the internal browser.
    *
    * @var string
@@ -1201,15 +1191,9 @@ protected function drupalGet($path, array $options = array(), array $headers = a
     if ($new = $this->checkForMetaRefresh()) {
       $out = $new;
     }
-
-    $verbose = 'GET request to: ' . $path .
-               '<hr />Ending URL: ' . $this->getUrl();
-    if ($this->dumpHeaders) {
-      $verbose .= '<hr />Headers: <pre>' . check_plain(var_export(array_map('trim', $this->headers), TRUE)) . '</pre>';
-    }
-    $verbose .= '<hr />' . $out;
-
-    $this->verbose($verbose);
+    $this->verbose('GET request to: ' . $path .
+                   '<hr />Ending URL: ' . $this->getUrl() .
+                   '<hr />' . $out);
     return $out;
   }
 
@@ -1389,16 +1373,10 @@ protected function drupalPost($path, $edit, $submit, array $options = array(), a
           if ($new = $this->checkForMetaRefresh()) {
             $out = $new;
           }
-
-          $verbose = 'POST request to: ' . $path;
-          $verbose .= '<hr />Ending URL: ' . $this->getUrl();
-          if ($this->dumpHeaders) {
-            $verbose .= '<hr />Headers: <pre>' . check_plain(var_export(array_map('trim', $this->headers), TRUE)) . '</pre>';
-          }
-          $verbose .= '<hr />Fields: ' . highlight_string('<?php ' . var_export($post_array, TRUE), TRUE);
-          $verbose .= '<hr />' . $out;
-
-          $this->verbose($verbose);
+          $this->verbose('POST request to: ' . $path .
+                         '<hr />Ending URL: ' . $this->getUrl() .
+                         '<hr />Fields: ' . highlight_string('<?php ' . var_export($post_array, TRUE), TRUE) .
+                         '<hr />' . $out);
           return $out;
         }
       }
@@ -1655,13 +1633,6 @@ protected function drupalHead($path, array $options = array(), array $headers =
     $options['absolute'] = TRUE;
     $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_HTTPHEADER => $headers));
     $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up.
-
-    if ($this->dumpHeaders) {
-      $this->verbose('GET request to: ' . $path .
-                     '<hr />Ending URL: ' . $this->getUrl() .
-                     '<hr />Headers: <pre>' . check_plain(var_export(array_map('trim', $this->headers), TRUE)) . '</pre>');
-    }
-
     return $out;
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php
index 253c463..ca2d7b3 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php
@@ -92,21 +92,25 @@ function testPageCache() {
     $config = config('system.performance');
     $config->set('cache.page.use_internal', 1);
     $config->set('cache.page.max_age', 300);
+    $config->set('response.gzip', 1);
     $config->save();
 
     // Fill the cache.
     $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
-    $this->assertEqual($this->drupalGetHeader('Vary'), 'Cookie,Accept-Encoding', 'Vary header was sent.');
-    $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'public, max-age=300', 'Cache-Control header was sent.');
+    $this->assertEqual(strtolower($this->drupalGetHeader('Vary')), 'cookie,accept-encoding', 'Vary header was sent.');
+    // Symfony's Response logic determines a specific order for the subvalues
+    // of the Cache-Control header, even if they are explicitly passed in to
+    // the response header bag in a different order.
+    $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.');
     $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
     $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.');
 
     // Check cache.
     $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
-    $this->assertEqual($this->drupalGetHeader('Vary'), 'Cookie,Accept-Encoding', 'Vary: Cookie header was sent.');
-    $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'public, max-age=300', 'Cache-Control header was sent.');
+    $this->assertEqual(strtolower($this->drupalGetHeader('Vary')), 'cookie,accept-encoding', 'Vary: Cookie header was sent.');
+    $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.');
     $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
     $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.');
 
@@ -114,14 +118,14 @@ function testPageCache() {
     $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Expires', 'value' => 'Fri, 19 Nov 2008 05:00:00 GMT')));
     $this->assertEqual($this->drupalGetHeader('Expires'), 'Fri, 19 Nov 2008 05:00:00 GMT', 'Default header was replaced.');
     $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Vary', 'value' => 'User-Agent')));
-    $this->assertEqual($this->drupalGetHeader('Vary'), 'User-Agent,Accept-Encoding', 'Default header was replaced.');
+    $this->assertEqual(strtolower($this->drupalGetHeader('Vary')), 'user-agent,accept-encoding', 'Default header was replaced.');
 
     // Check that authenticated users bypass the cache.
     $user = $this->drupalCreateUser();
     $this->drupalLogin($user);
     $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
     $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.');
-    $this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.');
+    $this->assertTrue(strpos(strtolower($this->drupalGetHeader('Vary')), 'cookie') === FALSE, 'Vary: Cookie header was not sent.');
     $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, post-check=0, pre-check=0, private', 'Cache-Control header was sent.');
     $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
     $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.');
@@ -148,6 +152,7 @@ function testPageCompression() {
     $config = config('system.performance');
     $config->set('cache.page.use_internal', 1);
     $config->set('cache.page.max_age', 300);
+    $config->set('response.gzip', 1);
     $config->save();
 
     // Fill the cache and verify that output is compressed.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php b/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php
index d1a601a..f96b13e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php
@@ -18,6 +18,8 @@ class SessionTest extends WebTestBase {
    */
   public static $modules = array('session_test');
 
+  protected $dumpHeaders = TRUE;
+
   public static function getInfo() {
     return array(
       'name' => 'Session tests',
diff --git a/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php b/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php
index 2bb96b9..b0e255e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php
@@ -21,6 +21,8 @@ class UpdateScriptTest extends WebTestBase {
    */
   public static $modules = array('update_script_test', 'dblog');
 
+  protected $dumpHeaders = TRUE;
+
   private $update_url;
   private $update_user;
 
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index c9f0c22..e1d55f3 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -9,6 +9,7 @@
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Drupal\Core\Template\Attribute;
 use Drupal\Component\Utility\Crypt;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Implements hook_help().
@@ -129,19 +130,23 @@ function _toolbar_initialize_page_cache() {
   // @see _drupal_bootstrap_page_cache()
   $cache = drupal_page_get_cache();
   if (is_object($cache)) {
-    header('X-Drupal-Cache: HIT');
+    $response = new Response();
+    $request = \Drupal::request();
+    $response->headers->set('X-Drupal-Cache', 'HIT');
     // Restore the metadata cached with the page.
     $_GET['q'] = $cache->data['path'];
     date_default_timezone_set(drupal_get_user_timezone());
 
-    drupal_serve_page_from_cache($cache);
+    drupal_serve_page_from_cache($cache, $response, $request);
 
+    $response->prepare($request);
+    $response->send();
     // We are done.
     exit;
   }
 
   // Otherwise, create a new page response (that will be cached).
-  header('X-Drupal-Cache: MISS');
+  drupal_add_http_header('X-Drupal-Cache', 'MISS');
 
   // The Expires HTTP header is the heart of the client-side HTTP caching. The
   // additional server-side page cache only takes effect when the client
diff --git a/core/update.php b/core/update.php
index cee23c4..6e85b0f 100644
--- a/core/update.php
+++ b/core/update.php
@@ -302,6 +302,7 @@ function update_info_page() {
  */
 function update_access_denied_page() {
   drupal_add_http_header('Status', '403 Forbidden');
+  header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
   watchdog('access denied', 'update.php', NULL, WATCHDOG_WARNING);
   drupal_set_title('Access denied');
   return '<p>Access denied. You are not authorized to access this page. Log in using either an account with the <em>administer software updates</em> permission or the site maintenance account (the account you created during installation). If you cannot log in, you will have to edit <code>settings.php</code> to bypass this access check. To do this:</p>
