diff --git a/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php b/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php
index 9e150da61e1e3dc3ced21895e3e7e098aa510484..53f563c47058e6d7513b393ecacfd14a855f2907 100644
--- a/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php
+++ b/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php
@@ -390,12 +390,9 @@ class SiteConfigurationController
                 }
             }
 
-            // keep root config objects not given via GUI
-            // this way extension authors are able to use their own objects on root level
-            // that are not configurable via GUI
-            // however: we overwrite the full subset of any GUI object to make sure we have a clean state
-            $newSysSiteData = array_merge($currentSiteConfiguration, $newSysSiteData);
-            $newSiteConfiguration = $this->validateFullStructure($newSysSiteData);
+            $newSiteConfiguration = $this->validateFullStructure(
+                $this->getMergeSiteData($currentSiteConfiguration, $newSysSiteData)
+            );
 
             // Persist the configuration
             $siteConfigurationManager = GeneralUtility::makeInstance(SiteConfiguration::class);
@@ -808,6 +805,35 @@ class SiteConfigurationController
         return !empty($value) || $value === '0';
     }
 
+    /**
+     * Method keeps root config objects, which are not given via GUI. This way,
+     * extension authors are able to use their own objects on root level that are
+     * not configurable via GUI. However: We overwrite the full subset of any GUI
+     * object to make sure we have a clean state.
+     *
+     * Additionally, we also keep the baseVariants of languages, since they
+     * can't be modified via the GUI, but are part of the public API.
+     */
+    protected function getMergeSiteData(array $currentSiteConfiguration, array $newSysSiteData): array
+    {
+        $newSysSiteData = array_merge($currentSiteConfiguration, $newSysSiteData);
+
+        // @todo: this should go away, once base variants for languages are managable via the GUI.
+        $existingLanguageConfigurationsWithBaseVariants = [];
+        foreach ($currentSiteConfiguration['languages'] ?? [] as $languageConfiguration) {
+            if (isset($languageConfiguration['baseVariants'])) {
+                $existingLanguageConfigurationsWithBaseVariants[$languageConfiguration['languageId']] = $languageConfiguration['baseVariants'];
+            }
+        }
+        foreach ($newSysSiteData['languages'] ?? [] as $key => $languageConfiguration) {
+            if (isset($existingLanguageConfigurationsWithBaseVariants[$languageConfiguration['languageId']])) {
+                $newSysSiteData['languages'][$key]['baseVariants'] = $existingLanguageConfigurationsWithBaseVariants[$languageConfiguration['languageId']];
+            }
+        }
+
+        return $newSysSiteData;
+    }
+
     protected function getLanguageService(): LanguageService
     {
         return $GLOBALS['LANG'];
diff --git a/typo3/sysext/backend/Tests/Unit/Controller/SiteConfigurationControllerTest.php b/typo3/sysext/backend/Tests/Unit/Controller/SiteConfigurationControllerTest.php
index a653f9dfd4e8b2f4d0a258976d5ee4d8bda4ecbf..25792f1eafb12e99f232e72c49d40dcb50d047d4 100644
--- a/typo3/sysext/backend/Tests/Unit/Controller/SiteConfigurationControllerTest.php
+++ b/typo3/sysext/backend/Tests/Unit/Controller/SiteConfigurationControllerTest.php
@@ -126,4 +126,57 @@ class SiteConfigurationControllerTest extends UnitTestCase
 
         self::assertEquals($expected, $mockedSiteConfigurationController->_call('getDuplicatedEntryPoints', $sites, $rootPages));
     }
+
+    /**
+     * @test
+     */
+    public function languageBaseVariantsAreKept(): void
+    {
+        $mockedSiteConfigurationController = $this->getAccessibleMock(SiteConfigurationController::class, ['dummy'], [], '', false);
+
+        $currentSiteConfig = [
+            'base' => '//domain1.tld/',
+            'websiteTitle' => 'domain1',
+            'languages' => [
+                0 => [
+                    'languageId' => 0,
+                    'title' => 'English',
+                    'base' => '/',
+                    'baseVariants' => [
+                        [
+                            'base' => '/en',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+
+        $newSiteConfig = $currentSiteConfig;
+        $newSiteConfig['rootPageId'] = 123;
+        $newSiteConfig['websiteTitle'] = 'domain1 renamed';
+        unset($newSiteConfig['languages'][0]['baseVariants']);
+
+        $expected = [
+            'base' => '//domain1.tld/',
+            'rootPageId' => 123,
+            'websiteTitle' => 'domain1 renamed',
+            'languages' => [
+                0 => [
+                    'languageId' => 0,
+                    'title' => 'English',
+                    'base' => '/',
+                    'baseVariants' => [
+                        [
+                            'base' => '/en',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+
+        self::assertEquals(
+            $expected,
+            $mockedSiteConfigurationController->_call('getMergeSiteData', $currentSiteConfig, $newSiteConfig)
+        );
+    }
 }