diff --git a/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php b/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php
index 0a9ac0a86e79fca836af85f2ec6cd8adba458e3c..71db8e64c78c64271839236f69fc95556e858687 100644
--- a/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php
@@ -25,11 +25,28 @@ public static function getLabel() {
    */
   public function getContext($header = NULL) {
     if ($header === NULL) {
-      return $this->requestStack->getCurrentRequest()->headers->all();
+      $headers = $this->requestStack->getCurrentRequest()->headers->all();
+      // Order headers by name to have less cache variations.
+      ksort($headers);
+      $result = '';
+      foreach ($headers as $name => $value) {
+        if ($result) {
+          $result .= '&';
+        }
+        // Sort values to minimize cache variations.
+        sort($value);
+        $result .= $name . '=' . implode(',', $value);
+      }
+      return $result;
     }
-    else {
-      return $this->requestStack->getCurrentRequest()->headers->get($header);
+    elseif ($this->requestStack->getCurrentRequest()->headers->has($header)) {
+      $value = $this->requestStack->getCurrentRequest()->headers->get($header);
+      if ($value !== '') {
+        return $value;
+      }
+      return '?valueless?';
     }
+    return '';
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Cache/Context/HeadersCacheContextTest.php b/core/tests/Drupal/Tests/Core/Cache/Context/HeadersCacheContextTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8f69e21740c9022e54ad49bf7bed7c4dc910ca05
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Cache/Context/HeadersCacheContextTest.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\Tests\Core\Cache\Context;
+
+use Drupal\Core\Cache\Context\HeadersCacheContext;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Cache\Context\HeadersCacheContext
+ * @group Cache
+ */
+class HeadersCacheContextTest extends UnitTestCase {
+
+  /**
+   * @covers ::getContext
+   *
+   * @dataProvider providerTestGetContext
+   */
+  public function testGetContext($headers, $header_name, $context) {
+    $request_stack = new RequestStack();
+    $request = Request::create('/', 'GET');
+    // Request defaults could change, so compare with default values instead of
+    // passed in context value.
+    $request->headers->replace($headers);
+    $request_stack->push($request);
+    $cache_context = new HeadersCacheContext($request_stack);
+    $this->assertSame($cache_context->getContext($header_name), $context);
+  }
+
+  /**
+   * Provides a list of headers and expected cache contexts.
+   */
+  public function providerTestGetContext() {
+    return [
+      [[], NULL, ''],
+      [[], 'foo', ''],
+      // Non-empty headers.
+      [['llama' => 'rocks', 'alpaca' => '', 'panda' => 'drools', 'z' => '0'], NULL, 'alpaca=&llama=rocks&panda=drools&z=0'],
+      [['llama' => 'rocks', 'alpaca' => '', 'panda' => 'drools', 'z' => '0'], 'llama', 'rocks'],
+      [['llama' => 'rocks', 'alpaca' => '', 'panda' => 'drools', 'z' => '0'], 'alpaca', '?valueless?'],
+      [['llama' => 'rocks', 'alpaca' => '', 'panda' => 'drools', 'z' => '0'], 'panda', 'drools'],
+      [['llama' => 'rocks', 'alpaca' => '', 'panda' => 'drools', 'z' => '0'], 'z', '0'],
+      [['llama' => 'rocks', 'alpaca' => '', 'panda' => 'drools', 'z' => '0'], 'chicken', ''],
+      // Header value could be an array.
+      [['z' => ['0', '1']], NULL, 'z=0,1'],
+      // Values are sorted to minimize cache variations.
+      [['z' => ['1', '0'], 'a' => []], NULL, 'a=&z=0,1'],
+      [['a' => [], 'z' => ['1', '0']], NULL, 'a=&z=0,1'],
+    ];
+  }
+
+}