diff --git a/typo3/sysext/core/Classes/Hooks/CreateSiteConfiguration.php b/typo3/sysext/core/Classes/Hooks/CreateSiteConfiguration.php
index d0d0b70354b13848472a913a63599b824990fd02..b27062c281eb1e41a0efcaf2099aade3839d0a4b 100644
--- a/typo3/sysext/core/Classes/Hooks/CreateSiteConfiguration.php
+++ b/typo3/sysext/core/Classes/Hooks/CreateSiteConfiguration.php
@@ -96,13 +96,8 @@ class CreateSiteConfiguration
             $serverParams = $GLOBALS['TYPO3_REQUEST']->getServerParams();
         }
 
-        if (!($normalizedParams instanceof NormalizedParams)) {
-            $normalizedParams = new NormalizedParams(
-                $serverParams,
-                $GLOBALS['TYPO3_CONF_VARS']['SYS'],
-                Environment::getCurrentScript(),
-                Environment::getPublicPath()
-            );
+        if (!$normalizedParams instanceof NormalizedParams) {
+            $normalizedParams = NormalizedParams::createFromServerParams($serverParams);
         }
 
         return $normalizedParams;
diff --git a/typo3/sysext/core/Classes/Http/NormalizedParams.php b/typo3/sysext/core/Classes/Http/NormalizedParams.php
index 26676729297894897c40137389c6e410edd4677f..d0f188e9ddc8da13d2e3cbc9a55ab5cafceab3c1 100644
--- a/typo3/sysext/core/Classes/Http/NormalizedParams.php
+++ b/typo3/sysext/core/Classes/Http/NormalizedParams.php
@@ -15,6 +15,8 @@ namespace TYPO3\CMS\Core\Http;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -284,21 +286,35 @@ class NormalizedParams
      * dependency to $this. This ensures the chain of inter-property dependencies
      * is visible by only looking at the construct() method.
      *
-     * @param array $serverParams, usually coming from $_SERVER or $request->getServerParams()
+     * @param array $serverParams , usually coming from $_SERVER or $request->getServerParams()
      * @param array $configuration $GLOBALS['TYPO3_CONF_VARS']['SYS']
      * @param string $pathThisScript Absolute server entry script path, usually found within Environment::getCurrentScript()
      * @param string $pathSite Absolute server path to document root, Environment::getPublicPath()
      */
     public function __construct(array $serverParams, array $configuration, string $pathThisScript, string $pathSite)
     {
-        $isBehindReverseProxy = $this->isBehindReverseProxy = self::determineIsBehindReverseProxy($serverParams, $configuration);
+        $isBehindReverseProxy = $this->isBehindReverseProxy = self::determineIsBehindReverseProxy(
+            $serverParams,
+            $configuration
+        );
         $httpHost = $this->httpHost = self::determineHttpHost($serverParams, $configuration, $isBehindReverseProxy);
         $isHttps = $this->isHttps = self::determineHttps($serverParams, $configuration);
         $requestHost = $this->requestHost = ($isHttps ? 'https://' : 'http://') . $httpHost;
         $requestHostOnly = $this->requestHostOnly = self::determineRequestHostOnly($httpHost);
         $this->requestPort = self::determineRequestPort($httpHost, $requestHostOnly);
-        $scriptName = $this->scriptName = self::determineScriptName($serverParams, $configuration, $isHttps, $isBehindReverseProxy);
-        $requestUri = $this->requestUri = self::determineRequestUri($serverParams, $configuration, $isHttps, $scriptName, $isBehindReverseProxy);
+        $scriptName = $this->scriptName = self::determineScriptName(
+            $serverParams,
+            $configuration,
+            $isHttps,
+            $isBehindReverseProxy
+        );
+        $requestUri = $this->requestUri = self::determineRequestUri(
+            $serverParams,
+            $configuration,
+            $isHttps,
+            $scriptName,
+            $isBehindReverseProxy
+        );
         $requestUrl = $this->requestUrl = $requestHost . $requestUri;
         $this->requestScript = $requestHost . $scriptName;
         $requestDir = $this->requestDir = $requestHost . GeneralUtility::dirname($scriptName) . '/';
@@ -534,8 +550,11 @@ class NormalizedParams
      * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
      * @return string Sanitized HTTP_HOST
      */
-    protected static function determineHttpHost(array $serverParams, array $configuration, bool $isBehindReverseProxy): string
-    {
+    protected static function determineHttpHost(
+        array $serverParams,
+        array $configuration,
+        bool $isBehindReverseProxy
+    ): string {
         $httpHost = $serverParams['HTTP_HOST'] ?? '';
         if ($isBehindReverseProxy) {
             // If the request comes from a configured proxy which has set HTTP_X_FORWARDED_HOST, then
@@ -607,8 +626,12 @@ class NormalizedParams
      * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
      * @return string Sanitized script name
      */
-    protected static function determineScriptName(array $serverParams, array $configuration, bool $isHttps, bool $isBehindReverseProxy): string
-    {
+    protected static function determineScriptName(
+        array $serverParams,
+        array $configuration,
+        bool $isHttps,
+        bool $isBehindReverseProxy
+    ): string {
         $scriptName = ($serverParams['ORIG_SCRIPT_NAME'] ?? '') ?: ($serverParams['SCRIPT_NAME'] ?? '');
         if ($isBehindReverseProxy) {
             // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
@@ -632,8 +655,13 @@ class NormalizedParams
      * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
      * @return string Sanitized REQUEST_URI
      */
-    protected static function determineRequestUri(array $serverParams, array $configuration, bool $isHttps, string $scriptName, bool $isBehindReverseProxy): string
-    {
+    protected static function determineRequestUri(
+        array $serverParams,
+        array $configuration,
+        bool $isHttps,
+        string $scriptName,
+        bool $isBehindReverseProxy
+    ): string {
         $proxyPrefixApplied = false;
         if (!empty($configuration['requestURIvar'])) {
             // This is for URL rewriter that store the original URI in a server
@@ -669,8 +697,11 @@ class NormalizedParams
      * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
      * @return string Resolved REMOTE_ADDR
      */
-    protected static function determineRemoteAddress(array $serverParams, array $configuration, bool $isBehindReverseProxy): string
-    {
+    protected static function determineRemoteAddress(
+        array $serverParams,
+        array $configuration,
+        bool $isBehindReverseProxy
+    ): string {
         $remoteAddress = trim($serverParams['REMOTE_ADDR'] ?? '');
         if ($isBehindReverseProxy) {
             $ip = GeneralUtility::trimExplode(',', $serverParams['HTTP_X_FORWARDED_FOR'] ?? '', true);
@@ -699,7 +730,10 @@ class NormalizedParams
      */
     protected static function determineIsBehindReverseProxy($serverParams, $configuration): bool
     {
-        return GeneralUtility::cmpIP(trim($serverParams['REMOTE_ADDR'] ?? ''), trim($configuration['reverseProxyIP'] ?? ''));
+        return GeneralUtility::cmpIP(
+            trim($serverParams['REMOTE_ADDR'] ?? ''),
+            trim($configuration['reverseProxyIP'] ?? '')
+        );
     }
 
     /**
@@ -712,7 +746,11 @@ class NormalizedParams
     {
         $httpHostBracketPosition = strpos($httpHost, ']');
         $httpHostParts = explode(':', $httpHost);
-        return $httpHostBracketPosition !== false ? substr($httpHost, 0, $httpHostBracketPosition + 1) : array_shift($httpHostParts);
+        return $httpHostBracketPosition !== false ? substr(
+            $httpHost,
+            0,
+            $httpHostBracketPosition + 1
+        ) : array_shift($httpHostParts);
     }
 
     /**
@@ -803,4 +841,36 @@ class NormalizedParams
     {
         return (string)substr($requestUrl, strlen($siteUrl));
     }
+
+    /**
+     * Factory method, to allow TYPO3 to handle configuration options directly.
+     *
+     * @param array $serverParams - could be fulfilled by $_SERVER (on web requests)
+     * @param array|null $systemConfiguration
+     * @return static
+     */
+    public static function createFromServerParams(array $serverParams, array $systemConfiguration = null): self
+    {
+        return new NormalizedParams(
+            $serverParams,
+            $systemConfiguration ?? $GLOBALS['TYPO3_CONF_VARS']['SYS'],
+            Environment::getCurrentScript(),
+            Environment::getPublicPath()
+        );
+    }
+
+    /**
+     * Factory method for creating normalized params from a PSR-7 server request object
+     *
+     * @param ServerRequestInterface $request
+     * @param array|null $systemConfiguration
+     * @return static
+     */
+    public static function createFromRequest(ServerRequestInterface $request, array $systemConfiguration = null): self
+    {
+        return static::createFromServerParams(
+            $request->getServerParams(),
+            $systemConfiguration ?? $GLOBALS['TYPO3_CONF_VARS']['SYS']
+        );
+    }
 }
diff --git a/typo3/sysext/core/Classes/Middleware/NormalizedParamsAttribute.php b/typo3/sysext/core/Classes/Middleware/NormalizedParamsAttribute.php
index f1496bdb5d30a8e04053e61dc6c1e4edb54f654b..64b586ae0532afe4d4c76cd987d71f9c7f5f7dcf 100644
--- a/typo3/sysext/core/Classes/Middleware/NormalizedParamsAttribute.php
+++ b/typo3/sysext/core/Classes/Middleware/NormalizedParamsAttribute.php
@@ -19,7 +19,6 @@ use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
-use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Http\NormalizedParams;
 
 /**
@@ -40,15 +39,7 @@ class NormalizedParamsAttribute implements MiddlewareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
-        $request = $request->withAttribute(
-            'normalizedParams',
-            new NormalizedParams(
-                $request->getServerParams(),
-                $GLOBALS['TYPO3_CONF_VARS']['SYS'],
-                Environment::getCurrentScript(),
-                Environment::getPublicPath()
-            )
-        );
+        $request = $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request));
         return $handler->handle($request);
     }
 }
diff --git a/typo3/sysext/install/Classes/Controller/InstallerController.php b/typo3/sysext/install/Classes/Controller/InstallerController.php
index b2dbcdbf6482a1642d8ba5ca909dac852b8cbeda..75474e705f4cacc8c37aec57914a76a22d10048d 100644
--- a/typo3/sysext/install/Classes/Controller/InstallerController.php
+++ b/typo3/sysext/install/Classes/Controller/InstallerController.php
@@ -1222,12 +1222,7 @@ For each website you need a TypoScript template on the main page of your website
     {
         $normalizedParams = $request->getAttribute('normalizedParams', null);
         if (!($normalizedParams instanceof NormalizedParams)) {
-            $normalizedParams = new NormalizedParams(
-                $request->getServerParams(),
-                $GLOBALS['TYPO3_CONF_VARS']['SYS'],
-                Environment::getCurrentScript(),
-                Environment::getPublicPath()
-            );
+            $normalizedParams = NormalizedParams::createFromRequest($request);
         }
 
         // Create a default site configuration called "main" as best practice
diff --git a/typo3/sysext/install/Classes/ServiceProvider.php b/typo3/sysext/install/Classes/ServiceProvider.php
index e9689df4c4df56fff587f5b0920450ea761a8202..0f9475d2248bb8e91fa70fb561fc99acdd8610f9 100644
--- a/typo3/sysext/install/Classes/ServiceProvider.php
+++ b/typo3/sysext/install/Classes/ServiceProvider.php
@@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
 use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
 use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
+use TYPO3\CMS\Core\Middleware\NormalizedParamsAttribute as NormalizedParamsMiddleware;
 use TYPO3\CMS\Core\Package\AbstractServiceProvider;
 use TYPO3\CMS\Core\Package\PackageManager;
 
@@ -55,6 +56,7 @@ class ServiceProvider extends AbstractServiceProvider
         // Stack of middlewares, executed LIFO
         $dispatcher->lazy(Middleware\Installer::class);
         $dispatcher->add($container->get(Middleware\Maintenance::class));
+        $dispatcher->lazy(NormalizedParamsMiddleware::class);
 
         return new Http\Application($dispatcher, $container->get(Context::class));
     }