diff --git a/core/core.services.yml b/core/core.services.yml
index 9c3f606..de53ad9 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -999,11 +999,24 @@ services:
   current_user:
     class: Drupal\Core\Session\AccountProxy
     arguments: ['@authentication', '@request_stack']
+  session_handler.storage:
+    class: Drupal\Core\Session\SessionHandler
+    arguments: ['@request_stack', '@database']
+    calls:
+      - [setSessionManager, ['@session_manager']]
+    tags:
+      - { name: backend_overridable }
+  session_handler.write_check:
+    class: Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler
+    tags:
+      - { name: session_handler_proxy, priority: 100 }
   session_manager:
     class: Drupal\Core\Session\SessionManager
     arguments: ['@request_stack', '@database', '@session_manager.metadata_bag', '@settings']
     tags:
       - { name: backend_overridable }
+    calls:
+      - [setSaveHandler, ['@session_handler']]
   session_manager.metadata_bag:
     class: Drupal\Core\Session\MetadataBag
     arguments: ['@settings']
diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index db54f91..4351046 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Cache\ListCacheBinsPass;
 use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
 use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass;
+use Drupal\Core\DependencyInjection\Compiler\StackedSessionHandlerPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterStreamWrappersPass;
 use Drupal\Core\DependencyInjection\ServiceProviderInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
@@ -54,6 +55,8 @@ public function register(ContainerBuilder $container) {
 
     $container->addCompilerPass(new StackedKernelPass());
 
+    $container->addCompilerPass(new StackedSessionHandlerPass());
+
     // Collect tagged handler services as method calls on consumer services.
     $container->addCompilerPass(new TaggedHandlersPass());
     $container->addCompilerPass(new RegisterStreamWrappersPass());
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/StackedSessionHandlerPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/StackedSessionHandlerPass.php
new file mode 100644
index 0000000..32ef407
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/StackedSessionHandlerPass.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\DependencyInjection\Compiler\StackedSessionHandlerPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Provides a compiler pass for stacked session save handlers.
+ */
+class StackedSessionHandlerPass implements CompilerPassInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function process(ContainerBuilder $container) {
+
+    if ($container->hasDefinition('session_handler')) {
+      return;
+    }
+
+    $session_handler_proxies = [];
+    $priorities = [];
+
+    foreach ($container->findTaggedServiceIds('session_handler_proxy') as $id => $attributes) {
+      $priorities[$id] = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
+      $session_handler_proxies[$id] = $container->getDefinition($id);
+    }
+
+    array_multisort($priorities, SORT_ASC, $session_handler_proxies);
+
+    $decorated_id = 'session_handler.storage';
+    foreach ($session_handler_proxies as $id => $decorator) {
+      // Prepend the inner session handler as first constructor argument.
+      $arguments = $decorator->getArguments();
+      array_unshift($arguments, new Reference($decorated_id));
+      $decorator->setArguments($arguments);
+
+      $decorated_id = $id;
+    }
+
+    $container->setAlias('session_handler', $decorated_id);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Session/SessionHandler.php b/core/lib/Drupal/Core/Session/SessionHandler.php
index 4be86ff..1034261 100644
--- a/core/lib/Drupal/Core/Session/SessionHandler.php
+++ b/core/lib/Drupal/Core/Session/SessionHandler.php
@@ -50,15 +50,12 @@ class SessionHandler extends AbstractProxy implements \SessionHandlerInterface {
   /**
    * Constructs a new SessionHandler instance.
    *
-   * @param \Drupal\Core\Session\SessionManagerInterface $session_manager
-   *   The session manager.
    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
    *   The request stack.
    * @param \Drupal\Core\Database\Connection $connection
    *   The database connection.
    */
-  public function __construct(SessionManagerInterface $session_manager, RequestStack $request_stack, Connection $connection) {
-    $this->sessionManager = $session_manager;
+  public function __construct(RequestStack $request_stack, Connection $connection) {
     $this->requestStack = $request_stack;
     $this->connection = $connection;
   }
@@ -273,6 +270,22 @@ public function gc($lifetime) {
   }
 
   /**
+   * Sets the session manager.
+   *
+   * @param \Drupal\Core\Session\SessionManagerInterface $session_manager
+   *   The session manager.
+   *
+   * @todo Remove dependency on session manager.
+   *   @see https://www.drupal.org/node/2342593
+   *   @see https://www.drupal.org/node/2338727
+   *
+   * @internal
+   */
+  public function setSessionManager(SessionManagerInterface $session_manager) {
+    $this->sessionManager = $session_manager;
+  }
+
+  /**
    * Deletes a session cookie.
    *
    * @param string $name
diff --git a/core/lib/Drupal/Core/Session/SessionManager.php b/core/lib/Drupal/Core/Session/SessionManager.php
index efb3b7a..3c98c2f 100644
--- a/core/lib/Drupal/Core/Session/SessionManager.php
+++ b/core/lib/Drupal/Core/Session/SessionManager.php
@@ -10,10 +10,8 @@
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Session\AnonymousUserSession;
-use Drupal\Core\Session\SessionHandler;
 use Drupal\Core\Site\Settings;
 use Symfony\Component\HttpFoundation\RequestStack;
-use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler;
 use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
 
 /**
@@ -78,6 +76,8 @@ class SessionManager extends NativeSessionStorage implements SessionManagerInter
   /**
    * Constructs a new session manager instance.
    *
+   * @param \Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy|\Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler|\SessionHandlerInterface|NULL $save_handler
+   *   The session save handler.
    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
    *   The request stack.
    * @param \Drupal\Core\Database\Connection $connection
@@ -92,13 +92,7 @@ public function __construct(RequestStack $request_stack, Connection $connection,
     $this->requestStack = $request_stack;
     $this->connection = $connection;
 
-    // Register the default session handler.
-    // @todo Extract session storage from session handler into a service.
-    $save_handler = new SessionHandler($this, $this->requestStack, $this->connection);
-    $write_check_handler = new WriteCheckSessionHandler($save_handler);
-    $this->setSaveHandler($write_check_handler);
-
-    parent::__construct($options, $write_check_handler, $metadata_bag);
+    parent::__construct($options, NULL, $metadata_bag);
 
     $this->setMixedMode($settings->get('mixed_mode_sessions', FALSE));
 
diff --git a/core/modules/system/src/Tests/Session/StackSessionHandlerIntegrationTest.php b/core/modules/system/src/Tests/Session/StackSessionHandlerIntegrationTest.php
new file mode 100644
index 0000000..1e8e6e0
--- /dev/null
+++ b/core/modules/system/src/Tests/Session/StackSessionHandlerIntegrationTest.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Session\StackSessionHandlerIntegrationTest.
+ */
+
+namespace Drupal\system\Tests\Session;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests the stacked session handler functionality.
+ *
+ * @group Session
+ */
+class StackSessionHandlerIntegrationTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('session_test');
+
+  /**
+   * Tests a request.
+   */
+  public function testRequest() {
+    $actual_trace = $this->drupalGetAjax('session-test/trace-handler');
+    $expect_trace = [
+      ['BEGIN', 'test_argument', 'open'],
+      ['BEGIN', NULL,'open'],
+      ['END', NULL,'open'],
+      ['END', 'test_argument', 'open'],
+      ['BEGIN', 'test_argument', 'read', $this->session_id],
+      ['BEGIN', NULL,'read', $this->session_id],
+      ['END', NULL,'read', $this->session_id],
+      ['END', 'test_argument', 'read', $this->session_id],
+      ['BEGIN', 'test_argument', 'write', $this->session_id],
+      ['BEGIN', NULL,'write', $this->session_id],
+      ['END', NULL,'write', $this->session_id],
+      ['END', 'test_argument', 'write', $this->session_id],
+      ['BEGIN', 'test_argument', 'close'],
+      ['BEGIN', NULL,'close'],
+      ['END', NULL,'close'],
+      ['END', 'test_argument', 'close'],
+    ];
+    $this->assertEqual($expect_trace, $actual_trace);
+  }
+
+}
diff --git a/core/modules/system/tests/modules/session_test/session_test.routing.yml b/core/modules/system/tests/modules/session_test/session_test.routing.yml
index accd2c8..bfcd0a6 100644
--- a/core/modules/system/tests/modules/session_test/session_test.routing.yml
+++ b/core/modules/system/tests/modules/session_test/session_test.routing.yml
@@ -75,3 +75,11 @@ session_test.form:
     _title: 'Test form'
   requirements:
     _access: 'TRUE'
+
+session_test.trace_handler:
+  path: '/session-test/trace-handler'
+  defaults:
+    _title: 'Returns the trace recorded by test proxy session handlers as JSON'
+    _controller: '\Drupal\session_test\Controller\SessionTestController::traceHandler'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/session_test/session_test.services.yml b/core/modules/system/tests/modules/session_test/session_test.services.yml
index 281b09d..b38505d 100644
--- a/core/modules/system/tests/modules/session_test/session_test.services.yml
+++ b/core/modules/system/tests/modules/session_test/session_test.services.yml
@@ -4,3 +4,14 @@ services:
     arguments: ['@session_manager']
     tags:
       - { name: event_subscriber }
+  session_test.session_handler.test_proxy:
+    class: Drupal\session_test\Session\TestSessionHandlerProxy
+    tags:
+      - { name: session_handler_proxy }
+  session_test.session_handler.test_proxy2:
+    class: Drupal\session_test\Session\TestSessionHandlerProxy
+    arguments: ['test_argument']
+    tags:
+      - { name: session_handler_proxy, priority: 20 }
+  session_test.session_handler_proxy_trace:
+    class: ArrayObject
diff --git a/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php b/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php
index 9deae9f..2bc20b6 100644
--- a/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php
+++ b/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Controller\ControllerBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 
@@ -123,4 +124,28 @@ public function setMessageButDontSave() {
   public function isLoggedIn() {
     return ['#markup' => $this->t('User is logged in.')];
   }
+
+  /**
+   * Returns the trace recorded by test proxy session handlers as JSON.
+   *
+   * @return Symfony\Component\HttpFoundation\JsonResponse
+   *   The response.
+   */
+  public function traceHandler() {
+    // Start a session if necessary, set a value and then save and close it.
+    \Drupal::service('session_manager')->start();
+    if (empty($_SESSION['trace-handler'])) {
+      $_SESSION['trace-handler'] = 1;
+    }
+    else {
+      $_SESSION['trace-handler']++;
+    }
+    \Drupal::service('session_manager')->save();
+
+    // Collect traces and return them in JSON format.
+    $trace = \Drupal::service('session_test.session_handler_proxy_trace')->getArrayCopy();
+
+    return new JsonResponse($trace);
+  }
+
 }
diff --git a/core/modules/system/tests/modules/session_test/src/Session/TestSessionHandlerProxy.php b/core/modules/system/tests/modules/session_test/src/Session/TestSessionHandlerProxy.php
new file mode 100644
index 0000000..7ee25ae
--- /dev/null
+++ b/core/modules/system/tests/modules/session_test/src/Session/TestSessionHandlerProxy.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\session_test\Session\TestSessionHandlerProxy.
+ */
+
+namespace Drupal\session_test\Session;
+
+/**
+ * Provides a test session handler proxy.
+ */
+class TestSessionHandlerProxy implements \SessionHandlerInterface {
+
+  /**
+   * The decorated session handler.
+   *
+   * @var \SessionHandlerInterface
+   */
+  protected $sessionHandler;
+
+  /**
+   * An optional argument.
+   *
+   * @var mixed
+   */
+  protected $optionalArgument;
+
+  /**
+   * Constructs a new TestSessionHandlerProxy object.
+   *
+   * @param \SessionHandlerInterface $session_handler
+   *   The decorated kernel.
+   * @param mixed $optional_argument
+   *   (optional) An optional argument.
+   */
+  public function __construct(\SessionHandlerInterface $session_handler, $optional_argument = NULL) {
+    $this->sessionHandler = $session_handler;
+    $this->optionalArgument = $optional_argument;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function open($save_path, $name) {
+    $trace = \Drupal::service('session_test.session_handler_proxy_trace');
+    $trace[] = ['BEGIN', $this->optionalArgument, __FUNCTION__];
+    $result = $this->sessionHandler->open($save_path, $name);
+    $trace[] = ['END', $this->optionalArgument, __FUNCTION__];
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function close() {
+    $trace = \Drupal::service('session_test.session_handler_proxy_trace');
+    $trace[] = ['BEGIN', $this->optionalArgument, __FUNCTION__];
+    $result = $this->sessionHandler->close();
+    $trace[] = ['END', $this->optionalArgument, __FUNCTION__];
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function read($session_id) {
+    $trace = \Drupal::service('session_test.session_handler_proxy_trace');
+    $trace[] = ['BEGIN', $this->optionalArgument, __FUNCTION__, $session_id];
+    $result = $this->sessionHandler->read($session_id);
+    $trace[] = ['END', $this->optionalArgument, __FUNCTION__, $session_id];
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function write($session_id, $session_data) {
+    $trace = \Drupal::service('session_test.session_handler_proxy_trace');
+    $trace[] = ['BEGIN', $this->optionalArgument, __FUNCTION__, $session_id];
+    $result = $this->sessionHandler->write($session_id, $session_data);
+    $trace[] = ['END', $this->optionalArgument, __FUNCTION__, $session_id];
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function destroy($session_id) {
+    return $this->sessionHandler->destroy($session_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function gc($max_lifetime) {
+    return $this->sessionHandler->gc($max_lifetime);
+  }
+
+}
