diff --git a/typo3/sysext/backend/Classes/ServiceProvider.php b/typo3/sysext/backend/Classes/ServiceProvider.php
index 349910b1d5c920c5afa31b41e94ca980d5f07484..791c2b4fb0aaf09d5515e5a49ae110f87d16b46e 100644
--- a/typo3/sysext/backend/Classes/ServiceProvider.php
+++ b/typo3/sysext/backend/Classes/ServiceProvider.php
@@ -25,10 +25,12 @@ use TYPO3\CMS\Backend\Http\RouteDispatcher;
 use TYPO3\CMS\Backend\Routing\Route;
 use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
+use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
 use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\EventDispatcher\ListenerProvider;
 use TYPO3\CMS\Core\Exception as CoreException;
 use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
 use TYPO3\CMS\Core\Http\MiddlewareStackResolver;
@@ -54,6 +56,7 @@ class ServiceProvider extends AbstractServiceProvider
             UriBuilder::class => [ static::class, 'getUriBuilder' ],
             'backend.middlewares' => [ static::class, 'getBackendMiddlewares' ],
             'backend.routes' => [ static::class, 'getBackendRoutes' ],
+            'backend.routes.warmer' => [ static::class, 'getBackendRoutesWarmer' ],
         ];
     }
 
@@ -61,6 +64,7 @@ class ServiceProvider extends AbstractServiceProvider
     {
         return [
             Router::class => [ static::class, 'configureBackendRouter' ],
+            ListenerProvider::class => [ static::class, 'addEventListeners' ],
         ] + parent::getExtensions();
     }
 
@@ -140,4 +144,23 @@ class ServiceProvider extends AbstractServiceProvider
     {
         return new ArrayObject();
     }
+
+    public static function getBackendRoutesWarmer(ContainerInterface $container): \Closure
+    {
+        return function (CacheWarmupEvent $event) use ($container) {
+            if ($event->hasGroup('system')) {
+                $cache = $container->get('cache.core');
+                $cacheIdentifier = 'BackendRoutes_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'BackendRoutes');
+                $routesFromPackages = $container->get('backend.routes')->getArrayCopy();
+                $cache->set($cacheIdentifier, 'return ' . var_export($routesFromPackages, true) . ';');
+            }
+        };
+    }
+
+    public static function addEventListeners(ContainerInterface $container, ListenerProvider $listenerProvider): ListenerProvider
+    {
+        $listenerProvider->addListener(CacheWarmupEvent::class, 'backend.routes.warmer');
+
+        return $listenerProvider;
+    }
 }
diff --git a/typo3/sysext/core/Classes/Cache/CacheManager.php b/typo3/sysext/core/Classes/Cache/CacheManager.php
index 643c17ae32856ad0da920bf682c4ad2b22929438..1bdc965c1b8f5831957a36fe8032842abe4bb8a6 100644
--- a/typo3/sysext/core/Classes/Cache/CacheManager.php
+++ b/typo3/sysext/core/Classes/Cache/CacheManager.php
@@ -280,6 +280,25 @@ class CacheManager implements SingletonInterface
         }
     }
 
+    /**
+     * @return string[]
+     * @internal
+     */
+    public function getCacheGroups(): array
+    {
+        $groups = array_keys($this->cacheGroups);
+
+        foreach ($this->cacheConfigurations as $config) {
+            foreach ($config['groups'] ?? [] as $group) {
+                if (!in_array($group, $groups, true)) {
+                    $groups[] = $group;
+                }
+            }
+        }
+
+        return $groups;
+    }
+
     /**
      * Instantiates all registered caches.
      */
diff --git a/typo3/sysext/core/Classes/Cache/Event/CacheWarmupEvent.php b/typo3/sysext/core/Classes/Cache/Event/CacheWarmupEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..25300bb4d5339f45ee8266c47c88d372b1c1dd45
--- /dev/null
+++ b/typo3/sysext/core/Classes/Cache/Event/CacheWarmupEvent.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Cache\Event;
+
+/**
+ * Event fired when caches are to be warmed up
+ */
+final class CacheWarmupEvent
+{
+    private array $groups = [];
+    private array $errors = [];
+
+    public function __construct(array $groups)
+    {
+        $this->groups = $groups;
+    }
+
+    public function getGroups(): array
+    {
+        return $this->groups;
+    }
+
+    public function hasGroup(string $group): bool
+    {
+        return in_array($group, $this->groups, true);
+    }
+
+    public function getErrors(): array
+    {
+        return $this->errors;
+    }
+
+    public function addError(string $error): void
+    {
+        $this->errors[] = $error;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Command/CacheWarmupCommand.php b/typo3/sysext/core/Classes/Command/CacheWarmupCommand.php
new file mode 100644
index 0000000000000000000000000000000000000000..4f6a09bfb982ce6cbf62f3451676cd9f2c4c2f18
--- /dev/null
+++ b/typo3/sysext/core/Classes/Command/CacheWarmupCommand.php
@@ -0,0 +1,113 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Command;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\CMS\Core\Configuration\Event\AfterTcaCompilationEvent;
+use TYPO3\CMS\Core\Core\BootService;
+use TYPO3\CMS\Core\Core\Event\WarmupBaseTcaCache;
+use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
+use TYPO3\CMS\Core\EventDispatcher\ListenerProvider;
+use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+
+class CacheWarmupCommand extends Command
+{
+    protected ContainerBuilder $containerBuilder;
+    protected PackageManager $packageManager;
+    protected BootService $bootService;
+    protected FrontendInterface $dependencyInjectionCache;
+
+    public function __construct(
+        ContainerBuilder $containerBuilder,
+        PackageManager $packageManager,
+        BootService $bootService,
+        FrontendInterface $dependencyInjectionCache
+    ) {
+        $this->containerBuilder = $containerBuilder;
+        $this->packageManager = $packageManager;
+        $this->bootService = $bootService;
+        $this->dependencyInjectionCache = $dependencyInjectionCache;
+        parent::__construct('cache:warmup');
+    }
+
+    /**
+     * Defines the allowed options for this command
+     */
+    protected function configure(): void
+    {
+        $this->setDescription('Warmup TYPO3 caches.');
+        $this->setHelp('This command is useful for deployments to warmup caches during release preparation.');
+        $this->setDefinition([
+            new InputOption('group', 'g', InputOption::VALUE_OPTIONAL, 'The cache group to warmup (system, pages, di or all)', 'all'),
+        ]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $group = $input->getOption('group') ?? 'all';
+
+        if ($group === 'di' || $group === 'system' || $group === 'all') {
+            $this->containerBuilder->warmupCache($this->packageManager, $this->dependencyInjectionCache);
+            if ($group === 'di') {
+                return 0;
+            }
+        }
+
+        $container = $this->bootService->getContainer();
+
+        $allowExtFileCaches = true;
+        if ($group === 'system' || $group === 'all') {
+            $coreCache = $container->get('cache.core');
+            ExtensionManagementUtility::createExtLocalconfCacheEntry($coreCache);
+            ExtensionManagementUtility::createExtTablesCacheEntry($coreCache);
+
+            // Load TCA uncached…
+            $allowExtFileCaches = false;
+            // …but store the fresh base TCA to cache
+            $listenerProvider = $container->get(ListenerProvider::class);
+            $listenerProvider->addListener(AfterTcaCompilationEvent::class, WarmupBaseTcaCache::class, 'storeBaseTcaCache');
+        }
+
+        // Perform a full boot to load localconf as requirement extensions and for TCA loading.
+        // TCA will be cached during dispatch of AfterTcaCompilationEvent.
+        $this->bootService->loadExtLocalconfDatabaseAndExtTables(false, $allowExtFileCaches);
+
+        $eventDispatcher = $container->get(EventDispatcherInterface::class);
+
+        $groups = $group === 'all' ? $container->get(CacheManager::class)->getCacheGroups() : [$group];
+        $event = new CacheWarmupEvent($groups);
+        $eventDispatcher->dispatch($event);
+
+        if (count($event->getErrors()) > 0) {
+            return 1;
+        }
+
+        return 0;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php b/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php
index 340d2fc4d8d291d5627a332a832334fdae4f5dba..40be8d049fc460364fe1c0ec492025fb8ea9538f 100644
--- a/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php
+++ b/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php
@@ -84,7 +84,7 @@ class ExtensionConfiguration
         $hasBeenSynchronized = false;
         if (!$this->hasConfiguration($extension)) {
             // This if() should not be hit at "casual" runtime, but only in early setup phases
-            $this->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions();
+            $this->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions(true);
             $hasBeenSynchronized = true;
             if (!$this->hasConfiguration($extension)) {
                 // If there is still no such entry, even after sync -> throw
@@ -102,7 +102,7 @@ class ExtensionConfiguration
         if (!ArrayUtility::isValidPath($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'], $extension . '/' . $path)) {
             // This if() should not be hit at "casual" runtime, but only in early setup phases
             if (!$hasBeenSynchronized) {
-                $this->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions();
+                $this->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions(true);
             }
             // If there is still no such entry, even after sync -> throw
             if (!ArrayUtility::isValidPath($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'], $extension . '/' . $path)) {
@@ -170,12 +170,15 @@ class ExtensionConfiguration
      * writing and loading LocalConfiguration many times.
      *
      * @param array $configuration Configuration of all extensions
+     * @param bool $skipWriteIfLocalConfiguationDoesNotExist
      * @internal
      */
-    public function setAll(array $configuration): void
+    public function setAll(array $configuration, bool $skipWriteIfLocalConfiguationDoesNotExist = false): void
     {
         $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
-        $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS', $configuration);
+        if ($skipWriteIfLocalConfiguationDoesNotExist === false || @file_exists($configurationManager->getLocalConfigurationFileLocation())) {
+            $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS', $configuration);
+        }
         $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'] = $configuration;
     }
 
@@ -186,9 +189,10 @@ class ExtensionConfiguration
      * Used when entering the install tool, during installation and if calling ->get()
      * with an extension or path that is not yet found in LocalConfiguration
      *
+     * @param bool $skipWriteIfLocalConfiguationDoesNotExist
      * @internal
      */
-    public function synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions(): void
+    public function synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions(bool $skipWriteIfLocalConfiguationDoesNotExist = false): void
     {
         $activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages();
         $fullConfiguration = [];
@@ -207,7 +211,7 @@ class ExtensionConfiguration
         }
         // Write new config if changed. Loose array comparison to not write if only array key order is different
         if ($fullConfiguration != $currentLocalConfiguration) {
-            $this->setAll($fullConfiguration);
+            $this->setAll($fullConfiguration, $skipWriteIfLocalConfiguationDoesNotExist);
         }
     }
 
diff --git a/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php b/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php
index 14690ac5ab3553d149fe6ecfd67a1a5a0ace273b..520a46889ed8fe08e32bbbd7570c38b0d43c24d2 100644
--- a/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php
+++ b/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php
@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Core\Configuration;
 
 use Symfony\Component\Finder\Finder;
 use Symfony\Component\Yaml\Yaml;
+use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
 use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
 use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
@@ -322,4 +323,11 @@ class SiteConfiguration implements SingletonInterface
 
         return $removed;
     }
+
+    public function warmupCaches(CacheWarmupEvent $event): void
+    {
+        if ($event->hasGroup('system')) {
+            $this->getAllSiteConfigurationFromFiles(false);
+        }
+    }
 }
diff --git a/typo3/sysext/core/Classes/Core/Event/WarmupBaseTcaCache.php b/typo3/sysext/core/Classes/Core/Event/WarmupBaseTcaCache.php
new file mode 100644
index 0000000000000000000000000000000000000000..c5f5d2f883322d8088a4545d9d831633c02f63a5
--- /dev/null
+++ b/typo3/sysext/core/Classes/Core/Event/WarmupBaseTcaCache.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Core\Event;
+
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\CMS\Core\Configuration\Event\AfterTcaCompilationEvent;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+
+final class WarmupBaseTcaCache
+{
+    private FrontendInterface $coreCache;
+
+    public function __construct(FrontendInterface $coreCache)
+    {
+        $this->coreCache = $coreCache;
+    }
+
+    /**
+     * Stores TCA caches during cache warmup.
+     *
+     * This event handler is injected dynamically by TYPO3\CMS\Core\Command\CacheWarmupCommand.
+     */
+    public function storeBaseTcaCache(AfterTcaCompilationEvent $event): void
+    {
+        $GLOBALS['TCA'] = $event->getTca();
+        ExtensionManagementUtility::createBaseTcaCacheFile($this->coreCache);
+    }
+}
diff --git a/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php b/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php
index 040f679bce1043c7fc759d57c4ef8169520a5f14..53bea6a9cfdbf576f3a25310add69fe4cd90de68 100644
--- a/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php
+++ b/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php
@@ -57,6 +57,19 @@ class ContainerBuilder
         $this->defaultServices = $earlyInstances + [ self::class => $this ];
     }
 
+    /**
+     * @param PackageManager $packageManager
+     * @param FrontendInterface $cache
+     * @internal
+     */
+    public function warmupCache(PackageManager $packageManager, FrontendInterface $cache): void
+    {
+        $registry = new ServiceProviderRegistry($packageManager);
+        $containerBuilder = $this->buildContainer($packageManager, $registry);
+        $cacheIdentifier = $this->getCacheIdentifier($packageManager);
+        $this->dumpContainer($containerBuilder, $cache, $cacheIdentifier);
+    }
+
     /**
      * @param PackageManager $packageManager
      * @param FrontendInterface $cache
@@ -165,8 +178,9 @@ class ContainerBuilder
     /**
      * @param PackageManager $packageManager
      * @return string
+     * @internal may only be used in this class or in functional tests
      */
-    protected function getCacheIdentifier(PackageManager $packageManager): string
+    public function getCacheIdentifier(PackageManager $packageManager): string
     {
         $packageManagerCacheIdentifier = $packageManager->getCacheIdentifier() ?? '';
         return $this->cacheIdentifiers[$packageManagerCacheIdentifier] ?? $this->createCacheIdentifier($packageManagerCacheIdentifier);
diff --git a/typo3/sysext/core/Classes/ExpressionLanguage/ProviderConfigurationLoader.php b/typo3/sysext/core/Classes/ExpressionLanguage/ProviderConfigurationLoader.php
index 460cef851a13c5b17b554a130e18988fc891c6f9..30a4a830ac11e90acc6eb37c024fbad8580cd0df 100644
--- a/typo3/sysext/core/Classes/ExpressionLanguage/ProviderConfigurationLoader.php
+++ b/typo3/sysext/core/Classes/ExpressionLanguage/ProviderConfigurationLoader.php
@@ -17,6 +17,7 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Core\ExpressionLanguage;
 
+use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
 use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
 use TYPO3\CMS\Core\Package\PackageManager;
 
@@ -68,4 +69,14 @@ class ProviderConfigurationLoader
         $this->cache->set($this->cacheIdentifier, 'return ' . var_export($providers, true) . ';');
         return $providers;
     }
+
+    /**
+     * @internal
+     */
+    public function warmupCaches(CacheWarmupEvent $event): void
+    {
+        if ($event->hasGroup('system')) {
+            $this->createCache();
+        }
+    }
 }
diff --git a/typo3/sysext/core/Classes/Http/MiddlewareStackResolver.php b/typo3/sysext/core/Classes/Http/MiddlewareStackResolver.php
index 5823857c361094eff576d80eecfeaa60f1342502..a6277b2919c2ba73e0a055576292dd965e86208e 100644
--- a/typo3/sysext/core/Classes/Http/MiddlewareStackResolver.php
+++ b/typo3/sysext/core/Classes/Http/MiddlewareStackResolver.php
@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Core\Http;
 
 use ArrayObject;
 use Psr\Container\ContainerInterface;
+use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
 use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend as PhpFrontendCache;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Information\Typo3Version;
@@ -135,4 +136,16 @@ class MiddlewareStackResolver
     {
         return 'middlewares_' . $stackName . '_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath());
     }
+
+    public function warmupCaches(CacheWarmupEvent $event): void
+    {
+        if ($event->hasGroup('system')) {
+            $allMiddlewares = $this->loadConfiguration();
+            $middlewares = $this->sanitizeMiddlewares($allMiddlewares);
+
+            foreach ($middlewares as $stack => $middlewaresOfStack) {
+                $this->cache->set($this->getCacheIdentifier($stack), 'return ' . var_export($middlewaresOfStack, true) . ';');
+            }
+        }
+    }
 }
diff --git a/typo3/sysext/core/Classes/Imaging/IconRegistry.php b/typo3/sysext/core/Classes/Imaging/IconRegistry.php
index a1b001a0ef2cfaa9f26dab33c8058ae7ebe9824f..a65f9389102f8d3d7006f0d1a295a52cfbe69d20 100644
--- a/typo3/sysext/core/Classes/Imaging/IconRegistry.php
+++ b/typo3/sysext/core/Classes/Imaging/IconRegistry.php
@@ -15,6 +15,7 @@
 
 namespace TYPO3\CMS\Core\Imaging;
 
+use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Exception;
@@ -810,4 +811,22 @@ class IconRegistry implements SingletonInterface
         }
         return BitmapIconProvider::class;
     }
+
+    public function warmupCaches(CacheWarmupEvent $event): void
+    {
+        if ($event->hasGroup('system')) {
+            $backupIcons = $this->icons;
+            $backupAliases = $this->iconAliases;
+            $this->icons = [];
+            $this->iconAliases = [];
+
+            $this->registerBackendIcons();
+            // all found icons should now be present, for historic reasons now merge w/ the statically declared icons
+            $this->icons = array_merge($this->icons, $this->iconAliases, $this->staticIcons);
+            $this->cache->set($this->getBackendIconsCacheIdentifier(), $this->icons);
+
+            $this->icons = $backupIcons;
+            $this->iconAliases = $backupAliases;
+        }
+    }
 }
diff --git a/typo3/sysext/core/Classes/Localization/CacheWarmer.php b/typo3/sysext/core/Classes/Localization/CacheWarmer.php
new file mode 100644
index 0000000000000000000000000000000000000000..b3360e6057a7a2ffff3422a7f2bedc51bc5a3760
--- /dev/null
+++ b/typo3/sysext/core/Classes/Localization/CacheWarmer.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Localization;
+
+use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
+use TYPO3\CMS\Core\Package\PackageManager;
+
+/**
+ * @internal
+ */
+class CacheWarmer
+{
+    protected PackageManager $packageManager;
+    protected LocalizationFactory $localizationFactory;
+
+    public function __construct(
+        PackageManager $packageManager,
+        LocalizationFactory $localizationFactory
+    ) {
+        $this->packageManager = $packageManager;
+        $this->localizationFactory = $localizationFactory;
+    }
+
+    public function warmupCaches(CacheWarmupEvent $event): void
+    {
+        if ($event->hasGroup('system')) {
+            $languages = array_merge(['default'], $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'] ?? []);
+            $packages = $this->packageManager->getActivePackages();
+            foreach ($packages as $package) {
+                $dir = $package->getPackagePath() . 'Resources/Private/Language';
+                if (!is_dir($dir)) {
+                    continue;
+                }
+                $recursiveIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));
+                // Search for all files with suffix *.xlf and  without a dot in the file basename
+                $fileIterator = new \RegexIterator($recursiveIterator, '#^.+/[^.]+\.xlf$#', \RegexIterator::GET_MATCH);
+                $shorthand = 'EXT:' . $package->getPackageKey() . '/Resources/Private/Language';
+                foreach ($fileIterator as $match) {
+                    $fileReference = str_replace($dir, $shorthand, $match[0]);
+                    foreach ($languages as $language) {
+                        // @todo: Force cache renewal
+                        $this->localizationFactory->getParsedData($fileReference, $language);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/typo3/sysext/core/Classes/Package/PackageManager.php b/typo3/sysext/core/Classes/Package/PackageManager.php
index 990825024fa3ee5ec87167de07d6a8ad8ab532ca..adf8723f520ce73fd3517984bd33ae3846c4e10e 100644
--- a/typo3/sysext/core/Classes/Package/PackageManager.php
+++ b/typo3/sysext/core/Classes/Package/PackageManager.php
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Core\Package;
 
 use Symfony\Component\Finder\Finder;
 use Symfony\Component\Finder\SplFileInfo;
+use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
 use TYPO3\CMS\Core\Core\ClassLoadingInformation;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Package\Cache\PackageCacheEntry;
@@ -1146,4 +1147,21 @@ class PackageManager implements SingletonInterface
 
         return $frameworkPackageKeys;
     }
+
+    /**
+     * @internal
+     */
+    public function warmupCaches(CacheWarmupEvent $event): void
+    {
+        if (Environment::isComposerMode()) {
+            return;
+        }
+        if ($event->hasGroup('system')) {
+            if (count($this->packageStatesConfiguration) === 0) {
+                $this->loadPackageStates();
+                $this->initializePackageObjects();
+            }
+            $this->saveToPackageCache();
+        }
+    }
 }
diff --git a/typo3/sysext/core/Classes/ServiceProvider.php b/typo3/sysext/core/Classes/ServiceProvider.php
index 23f6af4796ee237c16be933776679a047510912c..6806dac601484a3cb13b8a7e44b770cdfce40453 100644
--- a/typo3/sysext/core/Classes/ServiceProvider.php
+++ b/typo3/sysext/core/Classes/ServiceProvider.php
@@ -49,6 +49,7 @@ class ServiceProvider extends AbstractServiceProvider
             Configuration\SiteConfiguration::class => [ static::class, 'getSiteConfiguration' ],
             Command\ListCommand::class => [ static::class, 'getListCommand' ],
             HelpCommand::class => [ static::class, 'getHelpCommand' ],
+            Command\CacheWarmupCommand::class => [ static::class, 'getCacheWarmupCommand' ],
             Command\DumpAutoloadCommand::class => [ static::class, 'getDumpAutoloadCommand' ],
             Console\CommandApplication::class => [ static::class, 'getConsoleCommandApplication' ],
             Console\CommandRegistry::class => [ static::class, 'getConsoleCommandRegistry' ],
@@ -171,6 +172,16 @@ class ServiceProvider extends AbstractServiceProvider
         return new HelpCommand();
     }
 
+    public static function getCacheWarmupCommand(ContainerInterface $container): Command\CacheWarmupCommand
+    {
+        return new Command\CacheWarmupCommand(
+            $container->get(ContainerBuilder::class),
+            $container->get(Package\PackageManager::class),
+            $container->get(Core\BootService::class),
+            $container->get('cache.di')
+        );
+    }
+
     public static function getDumpAutoloadCommand(ContainerInterface $container): Command\DumpAutoloadCommand
     {
         return new Command\DumpAutoloadCommand();
@@ -213,6 +224,16 @@ class ServiceProvider extends AbstractServiceProvider
             Package\PackageManager::class,
             'packagesMayHaveChanged'
         );
+
+        $cacheWarmers = [
+            Configuration\SiteConfiguration::class,
+            Http\MiddlewareStackResolver::class,
+            Imaging\IconRegistry::class,
+            Package\PackageManager::class,
+        ];
+        foreach ($cacheWarmers as $service) {
+            $listenerProvider->addListener(Cache\Event\CacheWarmupEvent::class, $service, 'warmupCaches');
+        }
         return $listenerProvider;
     }
 
@@ -452,6 +473,8 @@ class ServiceProvider extends AbstractServiceProvider
 
         $commandRegistry->addLazyCommand('help', HelpCommand::class, 'Displays help for a command');
 
+        $commandRegistry->addLazyCommand('cache:warmup', Command\CacheWarmupCommand::class, 'Cache warmup for all, system or frontend caches.');
+
         $commandRegistry->addLazyCommand('dumpautoload', Command\DumpAutoloadCommand::class, 'Updates class loading information in non-composer mode.', Environment::isComposerMode());
         $commandRegistry->addLazyCommand('extensionmanager:extension:dumpclassloadinginformation', Command\DumpAutoloadCommand::class, null, Environment::isComposerMode(), false, 'dumpautoload');
         $commandRegistry->addLazyCommand('extension:dumpclassloadinginformation', Command\DumpAutoloadCommand::class, null, Environment::isComposerMode(), false, 'dumpautoload');
diff --git a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
index fed41fcfa926251891c28347fa00ee46811d4d69..cbdd1e1203156043181bc177478a928fd4b477ba 100644
--- a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
+++ b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
@@ -1519,8 +1519,9 @@ tt_content.' . $key . $suffix . ' {
      * Create cache entry for concatenated ext_localconf.php files
      *
      * @param FrontendInterface $codeCache
+     * @internal
      */
-    protected static function createExtLocalconfCacheEntry(FrontendInterface $codeCache)
+    public static function createExtLocalconfCacheEntry(FrontendInterface $codeCache)
     {
         $phpCodeToCache = [];
         // Set same globals as in loadSingleExtLocalconfFiles()
@@ -1608,8 +1609,9 @@ tt_content.' . $key . $suffix . ' {
      * the file should return an array with content of a specific table.
      *
      * @see Extension core, extensionmanager and others for examples.
+     * @internal
      */
-    protected static function buildBaseTcaFromSingleFiles()
+    public static function buildBaseTcaFromSingleFiles()
     {
         $GLOBALS['TCA'] = [];
 
@@ -1683,8 +1685,9 @@ tt_content.' . $key . $suffix . ' {
      * file for next access instead of cycling through all extensions again.
      *
      * @param FrontendInterface $codeCache
+     * @internal
      */
-    protected static function createBaseTcaCacheFile(FrontendInterface $codeCache)
+    public static function createBaseTcaCacheFile(FrontendInterface $codeCache)
     {
         // @deprecated Remove 'categoryRegistry' in v12
         $codeCache->set(
@@ -1726,7 +1729,7 @@ tt_content.' . $key . $suffix . ' {
             $hasCache = $codeCache->require($cacheIdentifier) !== false;
             if (!$hasCache) {
                 self::loadSingleExtTablesFiles();
-                self::createExtTablesCacheEntry();
+                self::createExtTablesCacheEntry($codeCache);
             }
         } else {
             self::loadSingleExtTablesFiles();
@@ -1749,8 +1752,11 @@ tt_content.' . $key . $suffix . ' {
 
     /**
      * Create concatenated ext_tables.php cache file
+     *
+     * @param FrontendInterface $codeCache
+     * @internal
      */
-    protected static function createExtTablesCacheEntry()
+    public static function createExtTablesCacheEntry(FrontendInterface $codeCache)
     {
         $phpCodeToCache = [];
         // Set same globals as in loadSingleExtTablesFiles()
@@ -1780,7 +1786,7 @@ tt_content.' . $key . $suffix . ' {
         // Remove all start and ending php tags from content
         $phpCodeToCache = preg_replace('/<\\?php|\\?>/is', '', $phpCodeToCache);
         $phpCodeToCache = preg_replace('/declare\\s?+\\(\\s?+strict_types\\s?+=\\s?+1\\s?+\\);/is', '', (string)$phpCodeToCache);
-        self::getCacheManager()->getCache('core')->set(self::getExtTablesCacheIdentifier(), $phpCodeToCache);
+        $codeCache->set(self::getExtTablesCacheIdentifier(), $phpCodeToCache);
     }
 
     /**
diff --git a/typo3/sysext/core/Configuration/Services.yaml b/typo3/sysext/core/Configuration/Services.yaml
index d9da02b59dd9c2200709011a826655d903c1ecc7..bab3b802b10590663d83f2f9120253b2304999e1 100644
--- a/typo3/sysext/core/Configuration/Services.yaml
+++ b/typo3/sysext/core/Configuration/Services.yaml
@@ -176,6 +176,12 @@ services:
       - name: softreference.parser
         parserKey: url
 
+
+  TYPO3\CMS\Core\Core\Event\WarmupBaseTcaCache:
+    public: true
+    arguments:
+      $coreCache: '@cache.core'
+
   # @internal
   # This service entry is provided for legacy code that instantiates LanguageService
   # using GeneralUtility::makeInstance instead of the factory methods which itself
@@ -190,10 +196,20 @@ services:
       version: '11.3'
       message: 'Injection/Instantiation of "%service_id%" is deprecated. Please use TYPO3\CMS\Core\Localization\LanguageServiceFactory->create().'
 
+  TYPO3\CMS\Core\Localization\CacheWarmer:
+    tags:
+      - name: event.listener
+        method: 'warmupCaches'
+        event: TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent
+
   TYPO3\CMS\Core\ExpressionLanguage\ProviderConfigurationLoader:
     public: true
     arguments:
       $coreCache: '@cache.core'
+    tags:
+      - name: event.listener
+        method: 'warmupCaches'
+        event: TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent
 
   TYPO3\CMS\Core\Page\AssetRenderer:
     public: true
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-93436-IntroduceCacheWarmupConsoleCommand.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-93436-IntroduceCacheWarmupConsoleCommand.rst
new file mode 100644
index 0000000000000000000000000000000000000000..83c815ca3e21e7e1d63ee566fe14cd8d624d54cf
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-93436-IntroduceCacheWarmupConsoleCommand.rst
@@ -0,0 +1,80 @@
+.. include:: ../../Includes.txt
+
+========================================================
+Feature: #93436 - Introduce cache:warmup console command
+========================================================
+
+See :issue:`93436`
+
+Description
+===========
+
+It is now possible to warmup TYPO3 caches using the command line.
+
+The administrator can use the following CLI command:
+
+.. code-block:: bash
+
+   ./typo3/sysext/core/bin/typo3 cache:warmup
+
+Specific cache groups can be defined via the group option.
+The usage is described as this:
+
+ .. code-block:: bash
+
+    cache:warmup [--group <all|system|di|pages|…>]
+
+All available cache groups can be supplied as option. The command defaults to
+warm all available cache groups.
+
+Extensions that register custom caches are encouraged to implement cache warmers
+via :php:`TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent`.
+
+Note: TYPO3 frontend caches will not be warmed by TYPO3 core, such functionality
+could be added by third party extensions with the help of
+:php:`TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent`.
+
+Impact
+======
+
+It is common practice to clear all caches during deployment of TYPO3 instance
+updates. This means that the first request after a deployment usually takes
+a major amount of time and blocks other requests due to cache-locks.
+
+TYPO3 caches can now be warmed during deployment in release preparatory steps in
+symlink based deployment/release proceducres. The enables fast first requests
+with all (or at least system) caches being prepared and warmed.
+
+Caches are often filesystem relevant (filepaths are calculated into cache
+hashes), therefore cache warmup should only be performed on the the live system,
+in the *final* folder of a new release, and ideally before switching
+to that new release (via symlink switch). Note that caches that have be
+pre-created in CI will likely be useless as cache hashes will not match.
+
+To summarize: Cache warmup is to be used during deployment, on the live system
+server, inside the new release folder and before switching the new release live.
+
+Deployment steps are:
+
+ * Release preparation:
+   * git-checkout/rsync your codebase (on CI or on live)
+   * `composer install` (on CI or on live)
+   * `vendor/bin/typo3 cache:warmup --group system` (*only* on the live system)
+ * Change release symlink to the new release folder
+ * Release postparation
+   * Clear only the page related caches (e.g. via database truncate or an
+     upcoming `cache:flush` command)
+
+The conceptional idea is to warmup all file-related caches *before* (symlink)
+switching to a new release and to *only* flush database and frontend (shared)
+caches after the symlink switch. Database warmup could be implemented with
+the help of the :php:`TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent` as an
+additionally functionality by third party extensions.
+
+Note that file-related caches (summarized into the group "system") can safely be
+cleared before doing a release switch, as it is recommended to keep file caches
+per release. In other words, share :file:`var/session`, :file:`var/log`,
+:file:`var/lock` and :file:`var/charset` between releases, but keep
+:file:`var/cache` be associated only with one release.
+
+.. index:: CLI, ext:core
diff --git a/typo3/sysext/core/Tests/Functional/Command/CacheWarmupCommandTest.php b/typo3/sysext/core/Tests/Functional/Command/CacheWarmupCommandTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fa64130c0084fb729faceb7e88ac1ad4e837806d
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Command/CacheWarmupCommandTest.php
@@ -0,0 +1,120 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Tests\Functional\Command;
+
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
+use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+/**
+ * Test case
+ */
+class CacheWarmupCommandTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function cachesCanBeWarmed()
+    {
+        $containerBuilder = $this->getContainer()->get(ContainerBuilder::class);
+        $packageManager = $this->getContainer()->get(PackageManager::class);
+        $diCacheIdentifier = $containerBuilder->getCacheIdentifier($packageManager);
+
+        GeneralUtility::rmdir(Environment::getVarPath() . '/cache/', true);
+        $result = $this->executeConsoleCommand('cache:warmup');
+
+        self::assertEquals(0, $result['status']);
+        self::assertFileExists(Environment::getVarPath() . '/cache/code/di/' . $diCacheIdentifier . '.php');
+        self::assertFileExists(Environment::getVarPath() . '/cache/code/core/sites-configuration.php');
+    }
+
+    /**
+     * @test
+     */
+    public function systemCachesCanBeWarmed()
+    {
+        $containerBuilder = $this->getContainer()->get(ContainerBuilder::class);
+        $packageManager = $this->getContainer()->get(PackageManager::class);
+        $diCacheIdentifier = $containerBuilder->getCacheIdentifier($packageManager);
+
+        GeneralUtility::rmdir(Environment::getVarPath() . '/cache/', true);
+        $result = $this->executeConsoleCommand('cache:warmup --group %s', 'system');
+
+        self::assertEquals(0, $result['status']);
+        self::assertFileExists(Environment::getVarPath() . '/cache/code/di/' . $diCacheIdentifier . '.php');
+        self::assertFileExists(Environment::getVarPath() . '/cache/code/core/sites-configuration.php');
+    }
+
+    /**
+     * @test
+     */
+    public function diCachesDoesNotWarmSystemCaches()
+    {
+        $containerBuilder = $this->getContainer()->get(ContainerBuilder::class);
+        $packageManager = $this->getContainer()->get(PackageManager::class);
+        $diCacheIdentifier = $containerBuilder->getCacheIdentifier($packageManager);
+
+        GeneralUtility::rmdir(Environment::getVarPath() . '/cache/', true);
+        $result = $this->executeConsoleCommand('cache:warmup -g %s', 'di');
+
+        self::assertEquals(0, $result['status']);
+        self::assertFileExists(Environment::getVarPath() . '/cache/code/di/' . $diCacheIdentifier . '.php');
+        self::assertFileDoesNotExist(Environment::getVarPath() . '/cache/code/core/sites-configuration.php');
+    }
+
+    /**
+     * @test
+     */
+    public function systemCachesCanBeWarmedIfCacheIsBroken()
+    {
+        $containerBuilder = $this->getContainer()->get(ContainerBuilder::class);
+        $packageManager = $this->getContainer()->get(PackageManager::class);
+        $diCacheIdentifier = $containerBuilder->getCacheIdentifier($packageManager);
+
+        GeneralUtility::mkdir_deep(Environment::getVarPath() . '/cache/code/di');
+        file_put_contents(
+            Environment::getVarPath() . '/cache/code/di/' . $diCacheIdentifier . '.php',
+            'invalid php code'
+        );
+
+        $result = $this->executeConsoleCommand('cache:warmup --group %s', 'system');
+
+        self::assertEquals(0, $result['status']);
+        self::assertFileExists(Environment::getVarPath() . '/cache/code/di/' . $diCacheIdentifier . '.php');
+    }
+
+    private function executeConsoleCommand(string $cmdline, ...$args): array
+    {
+        $cmd = vsprintf(PHP_BINARY . ' ' . GeneralUtility::getFileAbsFileName('EXT:core/bin/typo3') . ' ' . $cmdline, array_map('escapeshellarg', $args));
+
+        $output = '';
+
+        $handle = popen($cmd, 'r');
+        while (!feof($handle)) {
+            $output .= fgets($handle, 4096);
+        }
+        $status = pclose($handle);
+
+        return [
+            'status' => $status,
+            'output' => $output
+        ];
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Utility/AccessibleProxies/ExtensionManagementUtilityAccessibleProxy.php b/typo3/sysext/core/Tests/Unit/Utility/AccessibleProxies/ExtensionManagementUtilityAccessibleProxy.php
index 58ce2bdf8c23bca40673c94e5f0bfbaf33edf46d..5511f35d0d45dbfe0cddb2dd6c8b34cdf36a2382 100644
--- a/typo3/sysext/core/Tests/Unit/Utility/AccessibleProxies/ExtensionManagementUtilityAccessibleProxy.php
+++ b/typo3/sysext/core/Tests/Unit/Utility/AccessibleProxies/ExtensionManagementUtilityAccessibleProxy.php
@@ -18,7 +18,6 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Core\Tests\Unit\Utility\AccessibleProxies;
 
 use TYPO3\CMS\Core\Cache\CacheManager;
-use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 
 /**
@@ -56,16 +55,6 @@ class ExtensionManagementUtilityAccessibleProxy extends ExtensionManagementUtili
         self::$extTablesWasReadFromCacheOnce = false;
     }
 
-    public static function createExtLocalconfCacheEntry(FrontendInterface $cache)
-    {
-        parent::createExtLocalconfCacheEntry($cache);
-    }
-
-    public static function createExtTablesCacheEntry()
-    {
-        parent::createExtTablesCacheEntry();
-    }
-
     public static function getExtTablesCacheIdentifier()
     {
         return parent::getExtTablesCacheIdentifier();
diff --git a/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
index 6dbeac970a61ca734ba26810e14e3b0c3a9cccec..2dfb001fc0ada8be5b47de9f12322875ad3a0cf2 100644
--- a/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
+++ b/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
@@ -1600,14 +1600,8 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->getMock();
         $packageManager->setPackageCache(new PackageStatesPackageCache('vfs://Test/Configuration/PackageStates.php', $mockCache));
 
-        /** @var CacheManager|\PHPUnit\Framework\MockObject\MockObject $mockCacheManager */
-        $mockCacheManager = $this->getMockBuilder(CacheManager::class)
-            ->onlyMethods(['getCache'])
-            ->getMock();
-        $mockCacheManager->expects(self::any())->method('getCache')->willReturn($mockCache);
-        ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
         $mockCache->expects(self::once())->method('set')->with(self::anything(), self::stringContains($uniqueStringInTables), self::anything());
-        ExtensionManagementUtilityAccessibleProxy::createExtTablesCacheEntry();
+        ExtensionManagementUtilityAccessibleProxy::createExtTablesCacheEntry($mockCache);
     }
 
     /**
@@ -1624,16 +1618,10 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->getMock();
         $packageManager->setPackageCache(new PackageStatesPackageCache('vfs://Test/Configuration/PackageStates.php', $mockCache));
 
-        /** @var CacheManager|\PHPUnit\Framework\MockObject\MockObject $mockCacheManager */
-        $mockCacheManager = $this->getMockBuilder(CacheManager::class)
-            ->onlyMethods(['getCache'])
-            ->getMock();
-        $mockCacheManager->expects(self::any())->method('getCache')->willReturn($mockCache);
-        ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
         $mockCache->expects(self::once())
             ->method('set')
             ->with(self::anything(), self::logicalNot(self::stringContains($extensionName)), self::anything());
-        ExtensionManagementUtilityAccessibleProxy::createExtTablesCacheEntry();
+        ExtensionManagementUtilityAccessibleProxy::createExtTablesCacheEntry($mockCache);
     }
 
     /**
@@ -1646,17 +1634,11 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        /** @var CacheManager|\PHPUnit\Framework\MockObject\MockObject $mockCacheManager */
-        $mockCacheManager = $this->getMockBuilder(CacheManager::class)
-            ->onlyMethods(['getCache'])
-            ->getMock();
-        $mockCacheManager->expects(self::any())->method('getCache')->willReturn($mockCache);
-        ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
         $mockCache->expects(self::once())->method('set')->with(self::anything(), self::anything(), self::equalTo([]));
         $packageManager = $this->createMockPackageManagerWithMockPackage(StringUtility::getUniqueId());
         $packageManager->setPackageCache(new PackageStatesPackageCache('vfs://Test/Configuration/PackageStates.php', $mockCache));
         ExtensionManagementUtility::setPackageManager($packageManager);
-        ExtensionManagementUtilityAccessibleProxy::createExtTablesCacheEntry();
+        ExtensionManagementUtilityAccessibleProxy::createExtTablesCacheEntry($mockCache);
     }
 
     /////////////////////////////////////////
diff --git a/typo3/sysext/dashboard/Classes/ServiceProvider.php b/typo3/sysext/dashboard/Classes/ServiceProvider.php
index 0c58351973fef8f53d69d573a8c3e79ce07118d6..780da4c052658f565b60e930d53076bf1fbb291c 100644
--- a/typo3/sysext/dashboard/Classes/ServiceProvider.php
+++ b/typo3/sysext/dashboard/Classes/ServiceProvider.php
@@ -19,7 +19,9 @@ namespace TYPO3\CMS\Dashboard;
 
 use ArrayObject;
 use Psr\Container\ContainerInterface;
+use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
 use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\EventDispatcher\ListenerProvider;
 use TYPO3\CMS\Core\Information\Typo3Version;
 use TYPO3\CMS\Core\Package\AbstractServiceProvider;
 use TYPO3\CMS\Core\Package\PackageManager;
@@ -48,6 +50,7 @@ class ServiceProvider extends AbstractServiceProvider
             'dashboard.presets' => [ static::class, 'getDashboardPresets' ],
             'dashboard.widgetGroups' => [ static::class, 'getWidgetGroups' ],
             'dashboard.widgets' => [ static::class, 'getWidgets' ],
+            'dashboard.configuration.warmer' => [ static::class, 'getConfigurationWarmer' ],
         ];
     }
 
@@ -55,6 +58,7 @@ class ServiceProvider extends AbstractServiceProvider
     {
         return [
             DashboardPresetRegistry::class => [ static::class, 'configureDashboardPresetRegistry' ],
+            ListenerProvider::class => [ static::class, 'addEventListeners' ],
             WidgetGroupRegistry::class => [ static::class, 'configureWidgetGroupRegistry' ],
             'dashboard.presets' => [ static::class, 'configureDashboardPresets' ],
             'dashboard.widgetGroups' => [ static::class, 'configureWidgetGroups' ],
@@ -77,6 +81,11 @@ class ServiceProvider extends AbstractServiceProvider
         return new ArrayObject();
     }
 
+    private static function getCacheIdentifier($type): string
+    {
+        return 'Dashboard_' . $type . '_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . $type);
+    }
+
     public static function configureDashboardPresetRegistry(
         ContainerInterface $container,
         DashboardPresetRegistry $dashboardPresetRegistry = null
@@ -84,7 +93,7 @@ class ServiceProvider extends AbstractServiceProvider
         $dashboardPresetRegistry = $dashboardPresetRegistry ?? self::new($container, DashboardPresetRegistry::class);
         $cache = $container->get('cache.core');
 
-        $cacheIdentifier = 'Dashboard_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'DashboardPresets');
+        $cacheIdentifier = self::getCacheIdentifier('Presets');
         if ($cache->has($cacheIdentifier)) {
             $dashboardPresetsFromPackages = $cache->require($cacheIdentifier);
         } else {
@@ -114,7 +123,7 @@ class ServiceProvider extends AbstractServiceProvider
         $widgetGroupRegistry = $widgetGroupRegistry ?? self::new($container, WidgetGroupRegistry::class);
         $cache = $container->get('cache.core');
 
-        $cacheIdentifier = 'Dashboard_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'WidgetGroups');
+        $cacheIdentifier = self::getCacheIdentifier('WidgetGroups');
         if ($cache->has($cacheIdentifier)) {
             $widgetGroupsFromPackages = $cache->require($cacheIdentifier);
         } else {
@@ -210,4 +219,28 @@ class ServiceProvider extends AbstractServiceProvider
 
         return $paths;
     }
+
+    public static function getConfigurationWarmer(ContainerInterface $container): \Closure
+    {
+        $presetsCacheIdentifier = self::getCacheIdentifier('Presets');
+        $widgetGroupsCacheIdentifier = self::getCacheIdentifier('WidgetGroups');
+        return function (CacheWarmupEvent $event) use ($container, $presetsCacheIdentifier, $widgetGroupsCacheIdentifier) {
+            if ($event->hasGroup('system')) {
+                $cache = $container->get('cache.core');
+
+                $dashboardPresetsFromPackages = $container->get('dashboard.presets')->getArrayCopy();
+                $cache->set($presetsCacheIdentifier, 'return ' . var_export($dashboardPresetsFromPackages, true) . ';');
+
+                $widgetGroupsFromPackages = $container->get('dashboard.widgetGroups')->getArrayCopy();
+                $cache->set($widgetGroupsCacheIdentifier, 'return ' . var_export($widgetGroupsFromPackages, true) . ';');
+            }
+        };
+    }
+
+    public static function addEventListeners(ContainerInterface $container, ListenerProvider $listenerProvider): ListenerProvider
+    {
+        $listenerProvider->addListener(CacheWarmupEvent::class, 'dashboard.configuration.warmer');
+
+        return $listenerProvider;
+    }
 }