diff --git a/core/core.services.yml b/core/core.services.yml
index 9213c11b1f919e30665467930fc48bd1c1787a97..77d6ccea0158b3ad907419371f7d100ea88c2854 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -914,6 +914,11 @@ services:
   argument_resolver.default:
     class: Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver
     public: false
+  http_middleware.content_length:
+    class: Drupal\Core\StackMiddleware\ContentLength
+    tags:
+      # Must run before the page_cache and big_pipe middleware.
+      - { name: http_middleware, priority: 140 }
   http_middleware.ajax_page_state:
     class: Drupal\Core\StackMiddleware\AjaxPageState
     tags:
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index b29ed6f4128095a1f76932d0e68be056c97b5d23..da96c9dbfc06eb857d2cb1914cbd82e047b04934 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -300,40 +300,6 @@ protected function setExpiresNoCache(Response $response) {
     $response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 UTC'));
   }
 
-  /**
-   * Sets the Content-Length header on the response.
-   *
-   * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
-   *   The event to process.
-   *
-   * @see \Symfony\Component\HttpFoundation\Response::prepare()
-   * @see https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length
-   */
-  public function setContentLengthHeader(ResponseEvent $event): void {
-    $response = $event->getResponse();
-
-    if ($response->isInformational() || $response->isEmpty()) {
-      return;
-    }
-
-    if ($response->headers->has('Transfer-Encoding')) {
-      return;
-    }
-
-    // Drupal cannot set the correct content length header when there is a
-    // server error.
-    if ($response->isServerError()) {
-      return;
-    }
-
-    $content = $response->getContent();
-    if ($content === FALSE) {
-      return;
-    }
-
-    $response->headers->set('Content-Length', strlen($content), TRUE);
-  }
-
   /**
    * Registers the methods in this class that should be listeners.
    *
@@ -345,10 +311,6 @@ public static function getSubscribedEvents(): array {
     // There is no specific reason for choosing 16 beside it should be executed
     // before ::onRespond().
     $events[KernelEvents::RESPONSE][] = ['onAllResponds', 16];
-    // Run very late, after all other response subscribers have run. However,
-    // any response subscribers that convert a response to a streamed response
-    // must run after this and undo what this does.
-    $events[KernelEvents::RESPONSE][] = ['setContentLengthHeader', -1024];
     return $events;
   }
 
diff --git a/core/lib/Drupal/Core/StackMiddleware/ContentLength.php b/core/lib/Drupal/Core/StackMiddleware/ContentLength.php
new file mode 100644
index 0000000000000000000000000000000000000000..6f3172b810a9e5f89ea75d38733d0edbcb64a6a1
--- /dev/null
+++ b/core/lib/Drupal/Core/StackMiddleware/ContentLength.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Drupal\Core\StackMiddleware;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+/**
+ * Adds a Content-Length HTTP header to responses.
+ */
+class ContentLength implements HttpKernelInterface {
+
+  /**
+   * Constructs a new ContentLength instance.
+   *
+   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $httpKernel
+   *   The wrapped HTTP kernel.
+   */
+  public function __construct(
+    protected readonly HttpKernelInterface $httpKernel,
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function handle(Request $request, $type = self::MAIN_REQUEST, $catch = TRUE): Response {
+    $response = $this->httpKernel->handle($request, $type, $catch);
+    if ($response->isInformational() || $response->isEmpty()) {
+      return $response;
+    }
+
+    if ($response->headers->has('Transfer-Encoding')) {
+      return $response;
+    }
+
+    // Drupal cannot set the correct content length header when there is a
+    // server error.
+    if ($response->isServerError()) {
+      return $response;
+    }
+
+    $content = $response->getContent();
+    if ($content === FALSE) {
+      return $response;
+    }
+
+    $response->headers->set('Content-Length', strlen($content), TRUE);
+    return $response;
+  }
+
+}
diff --git a/core/modules/big_pipe/big_pipe.services.yml b/core/modules/big_pipe/big_pipe.services.yml
index 764a0461e9417ccb0f02049a35781bde213de43e..950d5a11ad93703412cf869031cd38e14c5ff048 100644
--- a/core/modules/big_pipe/big_pipe.services.yml
+++ b/core/modules/big_pipe/big_pipe.services.yml
@@ -23,3 +23,8 @@ services:
     class: Drupal\big_pipe\EventSubscriber\NoBigPipeRouteAlterSubscriber
     tags:
       - { name: event_subscriber }
+  http_middleware.big_pipe:
+    class: \Drupal\big_pipe\StackMiddleware\ContentLength
+    tags:
+      # Must run after the content_length middleware.
+      - { name: http_middleware, priority: 150 }
diff --git a/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php b/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php
index 03e1d4261cd955addefbc202b17f740545a7217c..85397961d33a9054fb19c8bad72ab1348bf52f0b 100644
--- a/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php
+++ b/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php
@@ -82,8 +82,6 @@ public function onRespond(ResponseEvent $event) {
       $content = $response->getContent();
       $content = str_replace('<drupal-big-pipe-scripts-bottom-marker>', '', $content);
       $response->setContent($content);
-      // FinishResponseSubscriber::setContentLengthHeader() already ran.
-      $response->headers->set('Content-Length', strlen($content), TRUE);
     }
 
     // If there are neither BigPipe placeholders nor no-JS BigPipe placeholders,
@@ -96,9 +94,6 @@ public function onRespond(ResponseEvent $event) {
     $big_pipe_response = new BigPipeResponse($response);
     $big_pipe_response->setBigPipeService($this->getBigPipeService($event));
 
-    // A BigPipe response's length is impossible to predict.
-    $big_pipe_response->headers->remove('Content-Length');
-
     $event->setResponse($big_pipe_response);
   }
 
diff --git a/core/modules/big_pipe/src/StackMiddleware/ContentLength.php b/core/modules/big_pipe/src/StackMiddleware/ContentLength.php
new file mode 100644
index 0000000000000000000000000000000000000000..bc24914170e0d2d57688b97b2a058a07cc2c0295
--- /dev/null
+++ b/core/modules/big_pipe/src/StackMiddleware/ContentLength.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\big_pipe\StackMiddleware;
+
+use Drupal\big_pipe\Render\BigPipeResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+/**
+ * Defines a big pipe middleware that removes Content-Length headers.
+ */
+final class ContentLength implements HttpKernelInterface {
+
+  /**
+   * Constructs a new ContentLength instance.
+   *
+   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $httpKernel
+   *   The wrapped HTTP kernel.
+   */
+  public function __construct(
+    protected readonly HttpKernelInterface $httpKernel,
+  ) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function handle(Request $request, $type = self::MAIN_REQUEST, $catch = TRUE): Response {
+    $response = $this->httpKernel->handle($request, $type, $catch);
+    if (!$response instanceof BigPipeResponse) {
+      return $response;
+    }
+    $response->headers->remove('Content-Length');
+    return $response;
+  }
+
+}
diff --git a/core/modules/big_pipe/tests/src/Unit/StackMiddleware/ContentLengthTest.php b/core/modules/big_pipe/tests/src/Unit/StackMiddleware/ContentLengthTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..08682e400c7823fe5852a07a36a2f9189966220e
--- /dev/null
+++ b/core/modules/big_pipe/tests/src/Unit/StackMiddleware/ContentLengthTest.php
@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\big_pipe\Unit\StackMiddleware;
+
+use Drupal\big_pipe\Render\BigPipeResponse;
+use Drupal\big_pipe\StackMiddleware\ContentLength;
+use Drupal\Core\Render\HtmlResponse;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+/**
+ * Defines a test for ContentLength middleware.
+ *
+ * @group big_pipe
+ * @coversDefaultClass \Drupal\big_pipe\StackMiddleware\ContentLength
+ */
+final class ContentLengthTest extends UnitTestCase {
+
+  /**
+   * @covers ::handle
+   * @dataProvider providerTestSetContentLengthHeader
+   */
+  public function testHandle(false|int $expected_header, Response $response) {
+    $kernel = $this->prophesize(HttpKernelInterface::class);
+    $request = Request::create('/');
+    $kernel->handle($request, HttpKernelInterface::MAIN_REQUEST, TRUE)->willReturn($response);
+    $middleware = new ContentLength($kernel->reveal());
+    $response = $middleware->handle($request);
+    if ($expected_header === FALSE) {
+      $this->assertFalse($response->headers->has('Content-Length'));
+      return;
+    }
+    $this->assertSame((string) $expected_header, $response->headers->get('Content-Length'));
+  }
+
+  public function providerTestSetContentLengthHeader() {
+    $response = new Response('Test content', 200);
+    $response->headers->set('Content-Length', (string) strlen('Test content'));
+    return [
+      '200 ok' => [
+        12,
+        $response,
+      ],
+      'Big pipe' => [
+        FALSE,
+        new BigPipeResponse(new HtmlResponse('Test content')),
+      ],
+    ];
+  }
+
+}
diff --git a/core/modules/system/tests/modules/http_middleware_test/http_middleware_test.info.yml b/core/modules/system/tests/modules/http_middleware_test/http_middleware_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9525558c7a636d166b93223e9f47427ed9bb71e3
--- /dev/null
+++ b/core/modules/system/tests/modules/http_middleware_test/http_middleware_test.info.yml
@@ -0,0 +1,5 @@
+name: 'Test HTTP Middleware'
+type: module
+description: 'Provides a test http middleware for automated tests.'
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/http_middleware_test/http_middleware_test.routing.yml b/core/modules/system/tests/modules/http_middleware_test/http_middleware_test.routing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ee5cb24f4bdae67f8bb8d7069c6f75262604f2d0
--- /dev/null
+++ b/core/modules/system/tests/modules/http_middleware_test/http_middleware_test.routing.yml
@@ -0,0 +1,7 @@
+http_middleware_test.test_response:
+  path: '/test-response'
+  defaults:
+    _title: 'Test response'
+    _controller: '\Drupal\http_middleware_test\Controller\TestResponseController::testResponse'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/http_middleware_test/http_middleware_test.services.yml b/core/modules/system/tests/modules/http_middleware_test/http_middleware_test.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4df1905ce1021e7087a880bf085c943286b8cd7d
--- /dev/null
+++ b/core/modules/system/tests/modules/http_middleware_test/http_middleware_test.services.yml
@@ -0,0 +1,5 @@
+services:
+  http_middleware.alter_content_middleware:
+    class: Drupal\http_middleware_test\StackMiddleware\AlterContentMiddleware
+    tags:
+      - { name: http_middleware, priority: 100, responder: true }
diff --git a/core/modules/system/tests/modules/http_middleware_test/src/Controller/TestResponseController.php b/core/modules/system/tests/modules/http_middleware_test/src/Controller/TestResponseController.php
new file mode 100644
index 0000000000000000000000000000000000000000..11819035b6e618e9a3b7d187c78a8feac0b3b138
--- /dev/null
+++ b/core/modules/system/tests/modules/http_middleware_test/src/Controller/TestResponseController.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\http_middleware_test\Controller;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Controller routines for http_middleware_test routes.
+ */
+final class TestResponseController {
+
+  /**
+   * Returns a test response.
+   */
+  public function testResponse(): Response {
+    return new Response('<html><body><p>Mangoes</p></body></html>');
+  }
+
+}
diff --git a/core/modules/system/tests/modules/http_middleware_test/src/StackMiddleware/AlterContentMiddleware.php b/core/modules/system/tests/modules/http_middleware_test/src/StackMiddleware/AlterContentMiddleware.php
new file mode 100644
index 0000000000000000000000000000000000000000..1bb7fff3d146f08756e5b561d83797b0b08b5fbd
--- /dev/null
+++ b/core/modules/system/tests/modules/http_middleware_test/src/StackMiddleware/AlterContentMiddleware.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\http_middleware_test\StackMiddleware;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+/**
+ * Alters the response before content length is calculated.
+ */
+final class AlterContentMiddleware implements HttpKernelInterface {
+
+  public function __construct(
+    private readonly HttpKernelInterface $httpKernel,
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = TRUE): Response {
+    $response = $this->httpKernel->handle($request, $type, $catch);
+    if (\Drupal::getContainer()->hasParameter('no-alter-content-length') && \Drupal::getContainer()->getParameter('no-alter-content-length')) {
+      $response->setContent('<html><body><p>Avocados</p></body></html>');
+    }
+    return $response;
+  }
+
+}
diff --git a/core/tests/Drupal/FunctionalTests/HttpKernel/ContentLengthTest.php b/core/tests/Drupal/FunctionalTests/HttpKernel/ContentLengthTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8400494c05f41c578e0379044bbed08a635a046f
--- /dev/null
+++ b/core/tests/Drupal/FunctionalTests/HttpKernel/ContentLengthTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\FunctionalTests\HttpKernel;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests Content-Length set by Drupal.
+ *
+ * @group Http
+ */
+class ContentLengthTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['system', 'http_middleware_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  public function testContentLength(): void {
+    // Fire off a request.
+    $this->drupalGet(Url::fromRoute('http_middleware_test.test_response'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseHeaderEquals('Content-Length', '40');
+
+    $this->setContainerParameter('no-alter-content-length', TRUE);
+    $this->rebuildContainer();
+
+    // Fire the same exact request but this time length is different.
+    $this->drupalGet(Url::fromRoute('http_middleware_test.test_response'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseHeaderEquals('Content-Length', '41');
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/FinishResponserSubscriberTest.php b/core/tests/Drupal/Tests/Core/StackMiddleware/ContentLengthTest.php
similarity index 51%
rename from core/tests/Drupal/Tests/Core/EventSubscriber/FinishResponserSubscriberTest.php
rename to core/tests/Drupal/Tests/Core/StackMiddleware/ContentLengthTest.php
index aefe20609c6bfc13792c9e60a93028c271588ba5..59212d739790bf8d3840cf7e046304c927892cc2 100644
--- a/core/tests/Drupal/Tests/Core/EventSubscriber/FinishResponserSubscriberTest.php
+++ b/core/tests/Drupal/Tests/Core/StackMiddleware/ContentLengthTest.php
@@ -1,52 +1,37 @@
 <?php
 
-namespace Drupal\Tests\Core\EventSubscriber;
+declare(strict_types=1);
 
-use Drupal\Core\Cache\Context\CacheContextsManager;
-use Drupal\Core\EventSubscriber\FinishResponseSubscriber;
-use Drupal\Core\Language\LanguageManagerInterface;
-use Drupal\Core\PageCache\RequestPolicyInterface;
-use Drupal\Core\PageCache\ResponsePolicyInterface;
+namespace Drupal\Tests\Core\StackMiddleware;
+
+use Drupal\Core\StackMiddleware\ContentLength;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\StreamedResponse;
-use Symfony\Component\HttpKernel\Event\ResponseEvent;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
 
 /**
- * @coversDefaultClass \Drupal\Core\EventSubscriber\FinishResponseSubscriber
- * @group EventSubscriber
+ * @coversDefaultClass \Drupal\Core\StackMiddleware\ContentLength
+ * @group Middleware
  */
-class FinishResponserSubscriberTest extends UnitTestCase {
+class ContentLengthTest extends UnitTestCase {
 
   /**
-   * @covers ::setContentLengthHeader
+   * @covers ::handle
    * @dataProvider providerTestSetContentLengthHeader
    */
-  public function testSetContentLengthHeader(false|int $expected_header, Response $response) {
-    $event_subscriber = new FinishResponseSubscriber(
-      $this->prophesize(LanguageManagerInterface::class)->reveal(),
-      $this->getConfigFactoryStub(),
-      $this->prophesize(RequestPolicyInterface::class)->reveal(),
-      $this->prophesize(ResponsePolicyInterface::class)->reveal(),
-      $this->prophesize(CacheContextsManager::class)->reveal()
-    );
-
-    $event = new ResponseEvent(
-      $this->prophesize(HttpKernelInterface::class)->reveal(),
-      $this->prophesize(Request::class)->reveal(),
-      HttpKernelInterface::MAIN_REQUEST,
-      $response
-    );
-
-    $event_subscriber->setContentLengthHeader($event);
+  public function testHandle(false|int $expected_header, Response $response) {
+    $kernel = $this->prophesize(HttpKernelInterface::class);
+    $request = Request::create('/');
+    $kernel->handle($request, HttpKernelInterface::MAIN_REQUEST, TRUE)->willReturn($response);
+    $middleware = new ContentLength($kernel->reveal());
+    $response = $middleware->handle($request);
     if ($expected_header === FALSE) {
-      $this->assertFalse($event->getResponse()->headers->has('Content-Length'));
-    }
-    else {
-      $this->assertSame((string) $expected_header, $event->getResponse()->headers->get('Content-Length'));
+      $this->assertFalse($response->headers->has('Content-Length'));
+      return;
     }
+    $this->assertSame((string) $expected_header, $response->headers->get('Content-Length'));
   }
 
   public function providerTestSetContentLengthHeader() {