diff --git a/core/lib/Drupal/Core/Session/SessionManager.php b/core/lib/Drupal/Core/Session/SessionManager.php
index 607103109d..1220c90d75 100644
--- a/core/lib/Drupal/Core/Session/SessionManager.php
+++ b/core/lib/Drupal/Core/Session/SessionManager.php
@@ -67,6 +67,13 @@ class SessionManager extends NativeSessionStorage implements SessionManagerInter
    */
   protected $writeSafeHandler;
 
+  /**
+   * The PHP session handler.
+   *
+   * @var string
+   */
+  protected $PHPSessionHandler;
+
   /**
    * Constructs a new session manager instance.
    *
@@ -88,6 +95,10 @@ public function __construct(RequestStack $request_stack, Connection $connection,
     $this->requestStack = $request_stack;
     $this->connection = $connection;
 
+    // Save the PHP session handler in case we need to retrieve session data
+    // that PHP stores before Drupal starts.
+    $this->PHPSessionHandler = session_module_name();
+
     parent::__construct($options, $handler, $metadata_bag);
 
     // @todo When not using the Symfony Session object, the list of bags in the
@@ -273,6 +284,10 @@ public function setWriteSafeHandler(WriteSafeSessionHandlerInterface $handler) {
     $this->writeSafeHandler = $handler;
   }
 
+  public function getPHPSessionHandler() {
+    return $this->PHPSessionHandler;
+  }
+
   /**
    * Returns whether the current PHP process runs on CLI.
    *
diff --git a/core/modules/file/file.es6.js b/core/modules/file/file.es6.js
index 9ab5dc2da3..cddea2ccdd 100644
--- a/core/modules/file/file.es6.js
+++ b/core/modules/file/file.es6.js
@@ -222,8 +222,9 @@
       if ($progressId.length) {
         const originalName = $progressId.attr('name');
 
-        // Replace the name with the required identifier.
-        $progressId.attr('name', originalName.match(/APC_UPLOAD_PROGRESS|UPLOAD_IDENTIFIER/)[0]);
+        // Replace the name with the last string in the name that does not
+        // contain square brackets.
+        $progressId.attr('name', originalName.match(/[^\[\]]+(?!.*[^\[\]]+)/)[0]);
 
         // Restore the original name after the upload begins.
         setTimeout(() => {
diff --git a/core/modules/file/file.install b/core/modules/file/file.install
index 6274efc7c9..08032285b9 100644
--- a/core/modules/file/file.install
+++ b/core/modules/file/file.install
@@ -109,6 +109,13 @@ function file_requirements($phase) {
     elseif ($implementation == 'uploadprogress') {
       $value = t('Enabled (<a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress</a>)');
     }
+    elseif ($implementation == 'session') {
+      $value = t('Enabled (<a href="http://php.net/manual/en/session.upload-progress.php">PHP session upload progress</a>)');
+      $description = t('The built-in PHP session handler stores file upload progress if the <a href=":url">PHP session name</a> is set to %session_name before Drupal starts.', [
+        ':url' => 'http://php.net/manual/en/session.configuration.php#ini.session.name',
+        '%session_name' => session_name(),
+      ]);
+    }
     $requirements['file_progress'] = [
       'title' => t('Upload progress'),
       'value' => $value,
diff --git a/core/modules/file/file.js b/core/modules/file/file.js
index 4d51bb0fa0..78df2840ce 100644
--- a/core/modules/file/file.js
+++ b/core/modules/file/file.js
@@ -116,7 +116,7 @@
       if ($progressId.length) {
         var originalName = $progressId.attr('name');
 
-        $progressId.attr('name', originalName.match(/APC_UPLOAD_PROGRESS|UPLOAD_IDENTIFIER/)[0]);
+        $progressId.attr('name', originalName.match(/[^\[\]]+(?!.*[^\[\]]+)/)[0]);
 
         setTimeout(function () {
           $progressId.attr('name', originalName);
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index be1e136958..21de6b82ef 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -1075,13 +1075,17 @@ function file_progress_implementation() {
     $implementation = FALSE;
 
     // We prefer the PECL extension uploadprogress because it supports multiple
-    // simultaneous uploads. APCu only supports one at a time.
+    // simultaneous uploads. APCu only supports one at a time. PHP session
+    // upload progress only works if the other two extensions are not enabled.
     if (extension_loaded('uploadprogress')) {
       $implementation = 'uploadprogress';
     }
     elseif (version_compare(PHP_VERSION, '7', '<') && extension_loaded('apc') && ini_get('apc.rfc1867')) {
       $implementation = 'apc';
     }
+    elseif (ini_get('session.upload_progress.enabled')) {
+      $implementation = 'session';
+    }
   }
   return $implementation;
 }
diff --git a/core/modules/file/src/Controller/FileWidgetAjaxController.php b/core/modules/file/src/Controller/FileWidgetAjaxController.php
index c958750443..74e9d50236 100644
--- a/core/modules/file/src/Controller/FileWidgetAjaxController.php
+++ b/core/modules/file/src/Controller/FileWidgetAjaxController.php
@@ -3,11 +3,43 @@
 namespace Drupal\file\Controller;
 
 use Symfony\Component\HttpFoundation\JsonResponse;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Session\SessionManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Defines a controller to respond to file widget AJAX requests.
  */
-class FileWidgetAjaxController {
+class FileWidgetAjaxController implements ContainerInjectionInterface {
+
+  /**
+   * The PHP session handler.
+   *
+   * @var string
+   */
+  protected $PHPSessionHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('session_manager'));
+  }
+
+  /**
+   * Constructs a FileWidgetAjaxController object.
+   *
+   * @param \Drupal\Core\Session\SessionManagerInterface $session_manager
+   *   The session manager.
+   */
+  public function __construct(SessionManagerInterface $session_manager) {
+    if (method_exists($session_manager, 'getPHPSessionHandler')) {
+      $this->PHPSessionHandler = $session_manager->getPHPSessionHandler();
+    }
+    else {
+      $this->PHPSessionHandler = 'files';
+    }
+  }
 
   /**
    * Returns the progress status for a file upload process.
@@ -39,6 +71,41 @@ public function progress($key) {
         $progress['percentage'] = round(100 * $status['current'] / $status['total']);
       }
     }
+    elseif ($implementation == 'session') {
+      // PHP saves file upload data to the session before Drupal starts.  This
+      // works only if the default session name has been configured to match the
+      // one generated by Drupal so PHP can recognize the browser’s cookie
+      // before Drupal starts.
+
+      $save_handler = session_module_name();
+      $session = $_SESSION;
+
+      // Stop the current session without saving any data to keep your original data
+      // in your path.
+      session_abort();
+
+      // Get upload status from the built-in PHP session data.
+      $status = [];
+      session_module_name($this->PHPSessionHandler);
+      // Fail silently if built-in PHP session handler generates an error.
+      @session_start();
+      $prefix = ini_get('session.upload_progress.prefix');
+      if (isset($_SESSION[$prefix . $key])) {
+        $status = $_SESSION[$prefix . $key];
+      }
+
+      // Close the built-in PHP session without saving and restore the current
+      // session.
+      session_abort();
+      session_module_name($save_handler);
+      session_start();
+      $_SESSION = $session;
+
+      if (isset($status['bytes_processed']) && !empty($status['content_length'])) {
+        $progress['message'] = t('Uploading... (@current of @total)', ['@current' => format_size($status['bytes_processed']), '@total' => format_size($status['content_length'])]);
+        $progress['percentage'] = round(100 * $status['bytes_processed'] / $status['content_length']);
+      }
+    }
 
     return new JsonResponse($progress);
   }
diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php
index ca4e887a1b..fbc14bf493 100644
--- a/core/modules/file/src/Element/ManagedFile.php
+++ b/core/modules/file/src/Element/ManagedFile.php
@@ -292,6 +292,16 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
           '#weight' => -20,
         ];
       }
+      elseif ($implementation == 'session') {
+        $element[ini_get('session.upload_progress.name')] = [
+          '#type' => 'hidden',
+          '#value' => $upload_progress_key,
+          '#attributes' => ['class' => ['file-progress']],
+          // Uploadprogress extension requires this field to be at the top of
+          // the form.
+          '#weight' => -20,
+        ];
+      }
 
       // Add the upload progress callback.
       $element['upload_button']['#ajax']['progress']['url'] = Url::fromRoute('file.ajax_progress', ['key' => $upload_progress_key]);
