diff -Naur a/app/etc/di.xml b/app/etc/di.xml
--- a/app/etc/di.xml
+++ b/app/etc/di.xml
@@ -46,6 +46,7 @@
     <preference for="Magento\Framework\App\RequestSafetyInterface" type="Magento\Framework\App\Request\Http" />
     <preference for="\Magento\Framework\Setup\SchemaSetupInterface" type="\Magento\Setup\Module\Setup" />
     <preference for="\Magento\Framework\Setup\ModuleDataSetupInterface" type="\Magento\Setup\Module\DataSetup" />
+    <preference for="Magento\Framework\App\ExceptionHandlerInterface" type="Magento\Framework\App\ExceptionHandler" />
     <type name="Magento\Store\Model\Store">
         <arguments>
             <argument name="currencyInstalled" xsi:type="string">system/currency/installed</argument>
diff -Naur a/vendor/magento/framework/App/ExceptionHandler.php b/vendor/magento/framework/App/ExceptionHandler.php
--- /dev/null
+++ b/vendor/magento/framework/App/ExceptionHandler.php
@@ -0,0 +1,284 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Framework\App;
+
+use Magento\Framework\App\Response\Http as ResponseHttp;
+use Magento\Framework\App\Request\Http as RequestHttp;
+use Magento\Framework\Encryption\EncryptorInterface;
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Debug;
+use Magento\Framework\Filesystem;
+use Psr\Log\LoggerInterface;
+use Magento\Framework\Exception\SessionException;
+use Magento\Framework\Exception\State\InitException;
+
+/**
+ * Handler of HTTP web application exception
+ */
+class ExceptionHandler implements ExceptionHandlerInterface
+{
+    /**
+     * @var Filesystem
+     */
+    private $filesystem;
+
+    /**
+     * @var LoggerInterface
+     */
+    private $logger;
+
+    /**
+     * @var EncryptorInterface
+     */
+    private $encryptor;
+
+    /**
+     * @param EncryptorInterface $encryptor
+     * @param Filesystem $filesystem
+     * @param LoggerInterface $logger
+     */
+    public function __construct(
+        EncryptorInterface $encryptor,
+        Filesystem $filesystem,
+        LoggerInterface $logger
+    ) {
+        $this->encryptor = $encryptor;
+        $this->filesystem = $filesystem;
+        $this->logger = $logger;
+    }
+
+    /**
+     * Handles exception of HTTP web application
+     *
+     * @param Bootstrap $bootstrap
+     * @param \Exception $exception
+     * @param ResponseHttp $response
+     * @param RequestHttp $request
+     * @return bool
+     */
+    public function handle(
+        Bootstrap $bootstrap,
+        \Exception $exception,
+        ResponseHttp $response,
+        RequestHttp $request
+    ): bool {
+        $result = $this->handleDeveloperMode($bootstrap, $exception, $response)
+            || $this->handleBootstrapErrors($bootstrap, $exception, $response)
+            || $this->handleSessionException($exception, $response, $request)
+            || $this->handleInitException($exception)
+            || $this->handleGenericReport($bootstrap, $exception);
+        return $result;
+    }
+
+    /**
+     * Error handler for developer mode
+     *
+     * @param Bootstrap $bootstrap
+     * @param \Exception $exception
+     * @param ResponseHttp $response
+     * @return bool
+     */
+    private function handleDeveloperMode(
+        Bootstrap $bootstrap,
+        \Exception $exception,
+        ResponseHttp $response
+    ): bool {
+        if ($bootstrap->isDeveloperMode()) {
+            if (Bootstrap::ERR_IS_INSTALLED == $bootstrap->getErrorCode()) {
+                try {
+                    $this->redirectToSetup($bootstrap, $exception, $response);
+                    return true;
+                } catch (\Exception $e) {
+                    $exception = $e;
+                }
+            }
+            $response->setHttpResponseCode(500);
+            $response->setHeader('Content-Type', 'text/plain');
+            $response->setBody($this->buildContentFromException($exception));
+            $response->sendResponse();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Build content based on an exception
+     *
+     * @param \Exception $exception
+     * @return string
+     */
+    private function buildContentFromException(\Exception $exception): string
+    {
+        /** @var \Exception[] $exceptions */
+        $exceptions = [];
+
+        do {
+            $exceptions[] = $exception;
+        } while ($exception = $exception->getPrevious());
+
+        $buffer = sprintf("%d exception(s):\n", count($exceptions));
+
+        foreach ($exceptions as $index => $exception) {
+            $buffer .= sprintf(
+                "Exception #%d (%s): %s\n",
+                $index,
+                get_class($exception),
+                $exception->getMessage()
+            );
+        }
+
+        foreach ($exceptions as $index => $exception) {
+            $buffer .= sprintf(
+                "\nException #%d (%s): %s\n%s\n",
+                $index,
+                get_class($exception),
+                $exception->getMessage(),
+                Debug::trace(
+                    $exception->getTrace(),
+                    true,
+                    true,
+                    (bool)getenv('MAGE_DEBUG_SHOW_ARGS')
+                )
+            );
+        }
+
+        return $buffer;
+    }
+
+    /**
+     * Handler for bootstrap errors
+     *
+     * @param Bootstrap $bootstrap
+     * @param \Exception $exception
+     * @param ResponseHttp $response
+     * @return bool
+     */
+    private function handleBootstrapErrors(
+        Bootstrap $bootstrap,
+        \Exception &$exception,
+        ResponseHttp $response
+    ): bool {
+        $bootstrapCode = $bootstrap->getErrorCode();
+        if (Bootstrap::ERR_MAINTENANCE == $bootstrapCode) {
+            // phpcs:ignore Magento2.Security.IncludeFile
+            require $this->filesystem
+                ->getDirectoryRead(DirectoryList::PUB)
+                ->getAbsolutePath('errors/503.php');
+            return true;
+        }
+        if (Bootstrap::ERR_IS_INSTALLED == $bootstrapCode) {
+            try {
+                $this->redirectToSetup($bootstrap, $exception, $response);
+                return true;
+            } catch (\Exception $e) {
+                $exception = $e;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Handler for session errors
+     *
+     * @param \Exception $exception
+     * @param ResponseHttp $response
+     * @param RequestHttp $request
+     * @return bool
+     */
+    private function handleSessionException(
+        \Exception $exception,
+        ResponseHttp $response,
+        RequestHttp $request
+    ): bool {
+        if ($exception instanceof SessionException) {
+            $response->setRedirect($request->getDistroBaseUrl());
+            $response->sendHeaders();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handler for application initialization errors
+     *
+     * @param \Exception $exception
+     * @return bool
+     */
+    private function handleInitException(\Exception $exception): bool
+    {
+        if ($exception instanceof InitException) {
+            $this->logger->critical($exception);
+            // phpcs:ignore Magento2.Security.IncludeFile
+            require $this->filesystem
+                ->getDirectoryRead(DirectoryList::PUB)
+                ->getAbsolutePath('errors/404.php');
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle for any other errors
+     *
+     * @param Bootstrap $bootstrap
+     * @param \Exception $exception
+     * @return bool
+     */
+    private function handleGenericReport(Bootstrap $bootstrap, \Exception $exception): bool
+    {
+        $reportData = [
+            $exception->getMessage(),
+            Debug::trace(
+                $exception->getTrace(),
+                true,
+                false,
+                (bool)getenv('MAGE_DEBUG_SHOW_ARGS')
+            )
+        ];
+        $params = $bootstrap->getParams();
+        if (isset($params['REQUEST_URI'])) {
+            $reportData['url'] = $params['REQUEST_URI'];
+        }
+        if (isset($params['SCRIPT_NAME'])) {
+            $reportData['script_name'] = $params['SCRIPT_NAME'];
+        }
+        $reportData['report_id'] = $this->encryptor->getHash(implode('', $reportData));
+        $this->logger->critical($exception, ['report_id' => $reportData['report_id']]);
+        // phpcs:ignore Magento2.Security.IncludeFile
+        require $this->filesystem
+            ->getDirectoryRead(DirectoryList::PUB)
+            ->getAbsolutePath('errors/report.php');
+        return true;
+    }
+
+    /**
+     * If not installed, try to redirect to installation wizard
+     *
+     * @param Bootstrap $bootstrap
+     * @param \Exception $exception
+     * @param ResponseHttp $response
+     * @return void
+     * @throws \Exception
+     */
+    private function redirectToSetup(Bootstrap $bootstrap, \Exception $exception, ResponseHttp $response)
+    {
+        $setupInfo = new SetupInfo($bootstrap->getParams());
+        $projectRoot = $this->filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath();
+        if ($setupInfo->isAvailable()) {
+            $response->setRedirect($setupInfo->getUrl());
+            $response->sendHeaders();
+        } else {
+            $newMessage = $exception->getMessage() . "\nNOTE: You cannot install Magento using the Setup Wizard "
+                . "because the Magento setup directory cannot be accessed. \n"
+                . 'You can install Magento using either the command line or you must restore access '
+                . 'to the following directory: ' . $setupInfo->getDir($projectRoot) . "\n";
+            // phpcs:ignore Magento2.Exceptions.DirectThrow
+            throw new \Exception($newMessage, 0, $exception);
+        }
+    }
+}
diff -Naur a/vendor/magento/framework/App/ExceptionHandlerInterface.php b/vendor/magento/framework/App/ExceptionHandlerInterface.php
--- /dev/null
+++ b/vendor/magento/framework/App/ExceptionHandlerInterface.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Framework\App;
+
+use Magento\Framework\App\Response\Http as ResponseHttp;
+use Magento\Framework\App\Request\Http as RequestHttp;
+
+/**
+ * Interface ExceptionHandler
+ */
+interface ExceptionHandlerInterface
+{
+    /**
+     * Handles exception of HTTP web application
+     *
+     * @param Bootstrap $bootstrap
+     * @param \Exception $exception
+     * @param ResponseHttp $response
+     * @param RequestHttp $request
+     * @return bool
+     */
+    public function handle(
+        Bootstrap $bootstrap,
+        \Exception $exception,
+        ResponseHttp $response,
+        RequestHttp $request
+    ): bool;
+}
diff -Naur a/vendor/magento/framework/App/Http.php b/vendor/magento/framework/App/Http.php
--- a/vendor/magento/framework/App/Http.php
+++ b/vendor/magento/framework/App/Http.php
@@ -5,14 +5,15 @@
  */
 namespace Magento\Framework\App;
 
-use Magento\Framework\App\Filesystem\DirectoryList;
-use Magento\Framework\ObjectManager\ConfigLoaderInterface;
 use Magento\Framework\App\Request\Http as RequestHttp;
 use Magento\Framework\App\Response\Http as ResponseHttp;
 use Magento\Framework\App\Response\HttpInterface;
 use Magento\Framework\Controller\ResultInterface;
-use Magento\Framework\Event;
-use Magento\Framework\Filesystem;
+use Magento\Framework\ObjectManager\ConfigLoaderInterface;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\Framework\Event\Manager;
+use Magento\Framework\Registry;
 
 /**
  * HTTP web application. Called from webroot index.php to serve web requests.
@@ -22,12 +23,12 @@ use Magento\Framework\Filesystem;
 class Http implements \Magento\Framework\AppInterface
 {
     /**
-     * @var \Magento\Framework\ObjectManagerInterface
+     * @var ObjectManagerInterface
      */
     protected $_objectManager;
 
     /**
-     * @var \Magento\Framework\Event\Manager
+     * @var Manager
      */
     protected $_eventManager;
 
@@ -51,47 +52,42 @@ class Http implements \Magento\Framework\AppInterface
      */
     protected $_state;
 
-    /**
-     * @var Filesystem
-     */
-    protected $_filesystem;
-
     /**
      * @var ResponseHttp
      */
     protected $_response;
 
     /**
-     * @var \Magento\Framework\Registry
+     * @var Registry
      */
     protected $registry;
 
     /**
-     * @var \Psr\Log\LoggerInterface
+     * @var ExceptionHandlerInterface
      */
-    private $logger;
+    private $exceptionHandler;
 
     /**
-     * @param \Magento\Framework\ObjectManagerInterface $objectManager
-     * @param Event\Manager $eventManager
+     * @param ObjectManagerInterface $objectManager
+     * @param Manager $eventManager
      * @param AreaList $areaList
      * @param RequestHttp $request
      * @param ResponseHttp $response
      * @param ConfigLoaderInterface $configLoader
      * @param State $state
-     * @param Filesystem $filesystem,
-     * @param \Magento\Framework\Registry $registry
+     * @param Registry $registry
+     * @param ExceptionHandlerInterface $exceptionHandler
      */
     public function __construct(
-        \Magento\Framework\ObjectManagerInterface $objectManager,
-        Event\Manager $eventManager,
+        ObjectManagerInterface $objectManager,
+        Manager $eventManager,
         AreaList $areaList,
         RequestHttp $request,
         ResponseHttp $response,
         ConfigLoaderInterface $configLoader,
         State $state,
-        Filesystem $filesystem,
-        \Magento\Framework\Registry $registry
+        Registry $registry,
+        ExceptionHandlerInterface $exceptionHandler = null
     ) {
         $this->_objectManager = $objectManager;
         $this->_eventManager = $eventManager;
@@ -100,30 +96,15 @@ class Http implements \Magento\Framework\AppInterface
         $this->_response = $response;
         $this->_configLoader = $configLoader;
         $this->_state = $state;
-        $this->_filesystem = $filesystem;
         $this->registry = $registry;
-    }
-
-    /**
-     * Add new dependency
-     *
-     * @return \Psr\Log\LoggerInterface
-     *
-     * @deprecated 100.1.0
-     */
-    private function getLogger()
-    {
-        if (!$this->logger instanceof \Psr\Log\LoggerInterface) {
-            $this->logger = \Magento\Framework\App\ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class);
-        }
-        return $this->logger;
+        $this->exceptionHandler = $exceptionHandler ?: $this->_objectManager->get(ExceptionHandlerInterface::class);
     }
 
     /**
      * Run application
      *
-     * @throws \InvalidArgumentException
      * @return ResponseInterface
+     * @throws LocalizedException|\InvalidArgumentException
      */
     public function launch()
     {
@@ -142,6 +123,9 @@ class Http implements \Magento\Framework\AppInterface
         } else {
             throw new \InvalidArgumentException('Invalid return type');
         }
+        if ($this->_request->isHead() && $this->_response->getHttpResponseCode() == 200) {
+            $this->handleHeadRequest();
+        }
         // This event gives possibility to launch something before sending output (allow cookie setting)
         $eventParams = ['request' => $this->_request, 'response' => $this->_response];
         $this->_eventManager->dispatch('controller_front_send_response_before', $eventParams);
@@ -149,178 +133,26 @@ class Http implements \Magento\Framework\AppInterface
     }
 
     /**
-     * {@inheritdoc}
-     */
-    public function catchException(Bootstrap $bootstrap, \Exception $exception)
-    {
-        $result = $this->handleDeveloperMode($bootstrap, $exception)
-            || $this->handleBootstrapErrors($bootstrap, $exception)
-            || $this->handleSessionException($exception)
-            || $this->handleInitException($exception)
-            || $this->handleGenericReport($bootstrap, $exception);
-        return $result;
-    }
-
-    /**
-     * Error handler for developer mode
-     *
-     * @param Bootstrap $bootstrap
-     * @param \Exception $exception
-     * @return bool
-     */
-    private function handleDeveloperMode(Bootstrap $bootstrap, \Exception $exception)
-    {
-        if ($bootstrap->isDeveloperMode()) {
-            if (Bootstrap::ERR_IS_INSTALLED == $bootstrap->getErrorCode()) {
-                try {
-                    $this->redirectToSetup($bootstrap, $exception);
-                    return true;
-                } catch (\Exception $e) {
-                    $exception = $e;
-                }
-            }
-            $this->_response->setHttpResponseCode(500);
-            $this->_response->setHeader('Content-Type', 'text/plain');
-            $this->_response->setBody($this->buildContentFromException($exception));
-            $this->_response->sendResponse();
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Build content based on an exception
-     *
-     * @param \Exception $exception
-     * @return string
-     */
-    private function buildContentFromException(\Exception $exception)
-    {
-        /** @var \Exception[] $exceptions */
-        $exceptions = [];
-        do {
-            $exceptions[] = $exception;
-        } while ($exception = $exception->getPrevious());
-
-        $buffer = sprintf("%d exception(s):\n", count($exceptions));
-
-        foreach ($exceptions as $index => $exception) {
-            $buffer .= sprintf("Exception #%d (%s): %s\n", $index, get_class($exception), $exception->getMessage());
-        }
-
-        foreach ($exceptions as $index => $exception) {
-            $buffer .= sprintf(
-                "\nException #%d (%s): %s\n%s\n",
-                $index,
-                get_class($exception),
-                $exception->getMessage(),
-                $exception->getTraceAsString()
-            );
-        }
-
-        return $buffer;
-    }
-
-    /**
-     * If not installed, try to redirect to installation wizard
+     * Handle HEAD requests by adding the Content-Length header and removing the body from the response.
      *
-     * @param Bootstrap $bootstrap
-     * @param \Exception $exception
      * @return void
-     * @throws \Exception
      */
-    private function redirectToSetup(Bootstrap $bootstrap, \Exception $exception)
+    private function handleHeadRequest()
     {
-        $setupInfo = new SetupInfo($bootstrap->getParams());
-        $projectRoot = $this->_filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath();
-        if ($setupInfo->isAvailable()) {
-            $this->_response->setRedirect($setupInfo->getUrl());
-            $this->_response->sendHeaders();
-        } else {
-            $newMessage = $exception->getMessage() . "\nNOTE: You cannot install Magento using the Setup Wizard "
-                . "because the Magento setup directory cannot be accessed. \n"
-                . 'You can install Magento using either the command line or you must restore access '
-                . 'to the following directory: ' . $setupInfo->getDir($projectRoot) . "\n";
-
-            throw new \Exception($newMessage, 0, $exception);
-        }
+        // It is possible that some PHP installations have overloaded strlen to use mb_strlen instead.
+        // This means strlen might return the actual number of characters in a non-ascii string instead
+        // of the number of bytes. Use mb_strlen explicitly with a single byte character encoding to ensure
+        // that the content length is calculated in bytes.
+        $contentLength = mb_strlen($this->_response->getContent(), '8bit');
+        $this->_response->clearBody();
+        $this->_response->setHeader('Content-Length', $contentLength);
     }
 
     /**
-     * Handler for bootstrap errors
-     *
-     * @param Bootstrap $bootstrap
-     * @param \Exception &$exception
-     * @return bool
-     */
-    private function handleBootstrapErrors(Bootstrap $bootstrap, \Exception &$exception)
-    {
-        $bootstrapCode = $bootstrap->getErrorCode();
-        if (Bootstrap::ERR_MAINTENANCE == $bootstrapCode) {
-            require $this->_filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/503.php');
-            return true;
-        }
-        if (Bootstrap::ERR_IS_INSTALLED == $bootstrapCode) {
-            try {
-                $this->redirectToSetup($bootstrap, $exception);
-                return true;
-            } catch (\Exception $e) {
-                $exception = $e;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Handler for session errors
-     *
-     * @param \Exception $exception
-     * @return bool
+     * @inheritdoc
      */
-    private function handleSessionException(\Exception $exception)
+    public function catchException(Bootstrap $bootstrap, \Exception $exception): bool
     {
-        if ($exception instanceof \Magento\Framework\Exception\SessionException) {
-            $this->_response->setRedirect($this->_request->getDistroBaseUrl());
-            $this->_response->sendHeaders();
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Handler for application initialization errors
-     *
-     * @param \Exception $exception
-     * @return bool
-     */
-    private function handleInitException(\Exception $exception)
-    {
-        if ($exception instanceof \Magento\Framework\Exception\State\InitException) {
-            $this->getLogger()->critical($exception);
-            require $this->_filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/404.php');
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Handle for any other errors
-     *
-     * @param Bootstrap $bootstrap
-     * @param \Exception $exception
-     * @return bool
-     */
-    private function handleGenericReport(Bootstrap $bootstrap, \Exception $exception)
-    {
-        $reportData = [$exception->getMessage(), $exception->getTraceAsString()];
-        $params = $bootstrap->getParams();
-        if (isset($params['REQUEST_URI'])) {
-            $reportData['url'] = $params['REQUEST_URI'];
-        }
-        if (isset($params['SCRIPT_NAME'])) {
-            $reportData['script_name'] = $params['SCRIPT_NAME'];
-        }
-        require $this->_filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/report.php');
-        return true;
+        return $this->exceptionHandler->handle($bootstrap, $exception, $this->_response, $this->_request);
     }
 }
diff -Naur a/pub/errors/local.xml.sample b/pub/errors/local.xml.sample
--- a/pub/errors/local.xml.sample
+++ b/pub/errors/local.xml.sample
@@ -27,5 +27,22 @@
             value "delete" is for cleaning
         -->
         <trash>leave</trash>
+        <!--
+            The number of subdirectories that will be created to save the report.
+            Valid Values: Integers from 0 to 32
+
+            Example:
+            If we have the report name as hash sha256('') = 44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7
+
+            dir_nesting_level=0 -> <magento_root>/var/report/44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7
+            dir_nesting_level=1 -> <magento_root>/var/report/44/44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7
+            dir_nesting_level=2 -> <magento_root>/var/report/44/ff/44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7
+            ...
+            dir_nesting_level=32 -> <magento_root>/var/report/44/ff/b1/08/7a/44/e6/1b/01/8b/3c/de/e7/22/84/d0/17/f2/2e/52/75/5c/24/e5/c8/5c/ba/c1/64/7a/e7/a7/44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7
+
+            If you use an environment variable MAGE_ERROR_REPORT_DIR_NESTING_LEVEL, this property will be ignored.
+            Environment variable has a higher priority.
+        -->
+        <dir_nesting_level>0</dir_nesting_level>
     </report>
 </config>
diff -Naur a/pub/errors/processor.php b/pub/errors/processor.php
--- a/pub/errors/processor.php
+++ b/pub/errors/processor.php
@@ -3,14 +3,21 @@
  * Copyright © Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
+declare(strict_types=1);
+
 namespace Magento\Framework\Error;
 
 use Magento\Framework\Serialize\Serializer\Json;
+use Magento\Framework\Escaper;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\App\Response\Http;
 
 /**
  * Error processor
  *
  * @SuppressWarnings(PHPMD.TooManyFields)
+ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
+ * phpcs:ignoreFile
  */
 class Processor
 {
@@ -18,6 +25,7 @@ class Processor
     const MAGE_ERRORS_DESIGN_XML = 'design.xml';
     const DEFAULT_SKIN = 'default';
     const ERROR_DIR = 'pub/errors';
+    const NUMBER_SYMBOLS_IN_SUBDIR_NAME = 2;
 
     /**
      * Page title
@@ -64,7 +72,7 @@ class Processor
     /**
      * Report ID
      *
-     * @var int
+     * @var string
      */
     public $reportId;
 
@@ -125,7 +133,7 @@ class Processor
     /**
      * Http response
      *
-     * @var \Magento\Framework\App\Response\Http
+     * @var Http
      */
     protected $_response;
 
@@ -137,15 +145,25 @@ class Processor
     private $serializer;
 
     /**
-     * @param \Magento\Framework\App\Response\Http $response
+     * @var Escaper
+     */
+    private $escaper;
+
+    /**
+     * @param Http $response
      * @param Json $serializer
+     * @param Escaper $escaper
      */
-    public function __construct(\Magento\Framework\App\Response\Http $response, Json $serializer = null)
-    {
+    public function __construct(
+        Http $response,
+        Json $serializer = null,
+        Escaper $escaper = null
+    ) {
         $this->_response = $response;
         $this->_errorDir  = __DIR__ . '/';
         $this->_reportDir = dirname(dirname($this->_errorDir)) . '/var/report/';
-        $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Json::class);
+        $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class);
+        $this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class);
 
         if (!empty($_SERVER['SCRIPT_NAME'])) {
             if (in_array(basename($_SERVER['SCRIPT_NAME'], '.php'), ['404', '503', 'report'])) {
@@ -155,11 +173,6 @@ class Processor
             }
         }
 
-        $reportId = (isset($_GET['id'])) ? (int)$_GET['id'] : null;
-        if ($reportId) {
-            $this->loadReport($reportId);
-        }
-
         $this->_indexDir = $this->_getIndexDir();
         $this->_root  = is_dir($this->_indexDir . 'app');
 
@@ -167,6 +180,9 @@ class Processor
         if (isset($_GET['skin'])) {
             $this->_setSkin($_GET['skin']);
         }
+        if (isset($_GET['id'])) {
+            $this->loadReport($_GET['id']);
+        }
     }
 
     /**
@@ -257,25 +273,38 @@ class Processor
         /**
          * Define server http host
          */
-        if (!empty($_SERVER['HTTP_HOST'])) {
-            $host = $_SERVER['HTTP_HOST'];
-        } elseif (!empty($_SERVER['SERVER_NAME'])) {
-            $host = $_SERVER['SERVER_NAME'];
-        } else {
-            $host = 'localhost';
-        }
+        $host = $this->resolveHostName();
 
-        $isSecure = (!empty($_SERVER['HTTPS'])) && ($_SERVER['HTTPS'] != 'off');
+        $isSecure = (!empty($_SERVER['HTTPS'])) && ($_SERVER['HTTPS'] !== 'off')
+            || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
         $url = ($isSecure ? 'https://' : 'http://') . $host;
 
-        if (!empty($_SERVER['SERVER_PORT']) && !in_array($_SERVER['SERVER_PORT'], [80, 443])
+        $port = explode(':', $host);
+        if (isset($port[1]) && !in_array($port[1], [80, 443])
             && !preg_match('/.*?\:[0-9]+$/', $url)
         ) {
-            $url .= ':' . $_SERVER['SERVER_PORT'];
+            $url .= ':' . $port[1];
         }
         return  $url;
     }
 
+    /**
+     * Resolve hostname
+     *
+     * @return string
+     */
+    private function resolveHostName() : string
+    {
+        if (!empty($_SERVER['HTTP_HOST'])) {
+            $host = $_SERVER['HTTP_HOST'];
+        } elseif (!empty($_SERVER['SERVER_NAME'])) {
+            $host = $_SERVER['SERVER_NAME'];
+        } else {
+            $host = 'localhost';
+        }
+        return $host;
+    }
+
     /**
      * Retrieve base URL
      *
@@ -355,6 +384,9 @@ class Processor
             if ((string)$local->report->trash) {
                 $config->trash = $local->report->trash;
             }
+            if ($local->report->dir_nesting_level) {
+                $config->dir_nesting_level = (int)$local->report->dir_nesting_level;
+            }
             if ((string)$local->skin) {
                 $this->_setSkin((string)$local->skin, $config);
             }
@@ -379,6 +411,8 @@ class Processor
     }
 
     /**
+     * Render page
+     *
      * @param string $template
      * @return string
      */
@@ -449,7 +483,7 @@ class Processor
             $this->reportData['url'] = $this->getHostUrl() . $reportData['url'];
         }
 
-        if ($this->reportData['script_name']) {
+        if (isset($this->reportData['script_name'])) {
             $this->_scriptName = $this->reportData['script_name'];
         }
     }
@@ -460,18 +494,20 @@ class Processor
      * @param array $reportData
      * @return string
      */
-    public function saveReport($reportData)
+    public function saveReport(array $reportData): string
     {
-        $this->reportData = $reportData;
-        $this->reportId   = abs(intval(microtime(true) * random_int(100, 1000)));
-        $this->_reportFile = $this->_reportDir . '/' . $this->reportId;
-        $this->_setReportData($reportData);
-
-        if (!file_exists($this->_reportDir)) {
-            @mkdir($this->_reportDir, 0777, true);
+        $this->reportId = $reportData['report_id'];
+        $this->_reportFile = $this->getReportPath(
+            $this->getReportDirNestingLevel($this->reportId),
+            $this->reportId
+        );
+        $reportDirName = dirname($this->_reportFile);
+        if (!file_exists($reportDirName)) {
+            @mkdir($reportDirName, 0777, true);
         }
+        $this->_setReportData($reportData);
 
-        @file_put_contents($this->_reportFile, $this->serializer->serialize($reportData));
+        @file_put_contents($this->_reportFile, $this->serializer->serialize($reportData). PHP_EOL);
 
         if (isset($reportData['skin']) && self::DEFAULT_SKIN != $reportData['skin']) {
             $this->_setSkin($reportData['skin']);
@@ -484,20 +520,117 @@ class Processor
     /**
      * Get report
      *
-     * @param int $reportId
+     * @param string $reportId
      * @return void
-     * @SuppressWarnings(PHPMD.ExitExpression)
      */
     public function loadReport($reportId)
     {
-        $this->reportId = $reportId;
-        $this->_reportFile = $this->_reportDir . '/' . $reportId;
+        try {
+            if (!$this->isReportIdValid($reportId)) {
+                throw new \RuntimeException("Report Id is invalid");
+            }
+            $reportFile = $this->findReportFile($reportId);
+            if (!is_readable($reportFile)) {
+                throw new \RuntimeException("Report file cannot be read");
+            }
+            $this->reportId = $reportId;
+            $this->_reportFile = $reportFile;
+            $this->_setReportData($this->serializer->unserialize(file_get_contents($this->_reportFile)));
+        } catch (\RuntimeException $e) {
+            $this->redirectToBaseUrl();
+        }
+    }
 
-        if (!file_exists($this->_reportFile) || !is_readable($this->_reportFile)) {
-            header("Location: " . $this->getBaseUrl());
-            die();
+    /**
+     * Searches for the report file and returns the path to it
+     *
+     * @param string $reportId
+     * @return string
+     * @throws \RuntimeException
+     */
+    private function findReportFile(string $reportId): string
+    {
+        $reportFile = $this->getReportPath(
+            $this->getReportDirNestingLevel($reportId),
+            $reportId
+        );
+        if (file_exists($reportFile)) {
+            return $reportFile;
         }
-        $this->_setReportData($this->serializer->unserialize(file_get_contents($this->_reportFile)));
+        $maxReportDirNestingLevel = $this->getMaxReportDirNestingLevel($reportId);
+        for ($i = 0; $i <= $maxReportDirNestingLevel; $i++) {
+            $reportFile = $this->getReportPath($i, $reportId);
+            if (file_exists($reportFile)) {
+                return $reportFile;
+            }
+        }
+        throw new \RuntimeException("Report file not found");
+    }
+
+    /**
+     * Redirect to a base url
+     * @return void
+     */
+    private function redirectToBaseUrl()
+    {
+        header("Location: " . $this->getBaseUrl());
+        die();
+    }
+
+    /**
+     * Checks report id
+     *
+     * @param string $reportId
+     * @return bool
+     */
+    private function isReportIdValid(string $reportId): bool
+    {
+        return (bool)preg_match('/[a-fA-F0-9]{64}/', $reportId);
+    }
+
+    /**
+     * Get path to reports
+     *
+     * @param integer $reportDirNestingLevel
+     * @param string $reportId
+     * @return string
+     */
+    private function getReportPath(int $reportDirNestingLevel, string $reportId): string
+    {
+        $reportDirPath = $this->_reportDir;
+        for ($i = 0, $j = 0; $j < $reportDirNestingLevel; $i += 2, $j++) {
+            $reportDirPath .= $reportId[$i] . $reportId[$i + 1] . '/';
+        }
+        return $reportDirPath . $reportId;
+    }
+
+    /**
+     * Returns nesting Level for the report files
+     *
+     * @var $reportId
+     * @return int
+     */
+    private function getReportDirNestingLevel(string $reportId): int
+    {
+        $envName = 'MAGE_ERROR_REPORT_DIR_NESTING_LEVEL';
+        $value = $_ENV[$envName] ?? getenv($envName);
+        if(false === $value && property_exists($this->_config, 'dir_nesting_level')) {
+            $value = $this->_config->dir_nesting_level;
+        }
+        $value = (int)$value;
+        $maxValue= $this->getMaxReportDirNestingLevel($reportId);
+        return 0 < $value && $maxValue >= $value ? $value : 0;
+    }
+
+    /**
+     * Returns maximum nesting level directories of report files
+     *
+     * @param string $reportId
+     * @return integer
+     */
+    private function getMaxReportDirNestingLevel(string $reportId): int
+    {
+        return (int)floor(strlen($reportId) / self::NUMBER_SYMBOLS_IN_SUBDIR_NAME);
     }
 
     /**
@@ -511,11 +644,16 @@ class Processor
     {
         $this->pageTitle = 'Error Submission Form';
 
-        $this->postData['firstName'] = (isset($_POST['firstname'])) ? trim(htmlspecialchars($_POST['firstname'])) : '';
-        $this->postData['lastName']  = (isset($_POST['lastname'])) ? trim(htmlspecialchars($_POST['lastname'])) : '';
-        $this->postData['email']     = (isset($_POST['email'])) ? trim(htmlspecialchars($_POST['email'])) : '';
-        $this->postData['telephone'] = (isset($_POST['telephone'])) ? trim(htmlspecialchars($_POST['telephone'])) : '';
-        $this->postData['comment']   = (isset($_POST['comment'])) ? trim(htmlspecialchars($_POST['comment'])) : '';
+        $this->postData['firstName'] = (isset($_POST['firstname']))
+            ? trim($this->escaper->escapeHtml($_POST['firstname'])) : '';
+        $this->postData['lastName'] = (isset($_POST['lastname']))
+            ? trim($this->escaper->escapeHtml($_POST['lastname'])) : '';
+        $this->postData['email'] = (isset($_POST['email']))
+            ? trim($this->escaper->escapeHtml($_POST['email'])) : '';
+        $this->postData['telephone'] = (isset($_POST['telephone']))
+            ? trim($this->escaper->escapeHtml($_POST['telephone'])) : '';
+        $this->postData['comment'] = (isset($_POST['comment']))
+            ? trim($this->escaper->escapeHtml($_POST['comment'])) : '';
 
         if (isset($_POST['submit'])) {
             if ($this->_validate()) {
