diff --git a/core/config/install/core.extension.yml b/core/config/install/core.extension.yml
index 659446cc64c88900a59666f480bf3a475678b5dc..ca917793c9c77d913164de0d102aed25800f6992 100644
--- a/core/config/install/core.extension.yml
+++ b/core/config/install/core.extension.yml
@@ -1,3 +1,3 @@
 module: {}
 theme: {}
-profile: ''
+profile: null
diff --git a/core/config/schema/core.extension.schema.yml b/core/config/schema/core.extension.schema.yml
index 087e2e3b9497f75ca86794bbd34796bb8b302350..19f52db5a3f41a1c5cbeb8fbc48561f6d26b1a5c 100644
--- a/core/config/schema/core.extension.schema.yml
+++ b/core/config/schema/core.extension.schema.yml
@@ -16,4 +16,10 @@ core.extension:
         label: 'Weight'
     profile:
       type: string
+      # Before Drupal is installed the profile is NULL. This allows all install
+      # profiles to be discovered by the installer.
+      nullable: true
+      # After Drupal is installed, if the install profile is uninstalled the key
+      # will be removed.
+      requiredKey: false
       label: 'Install profile'
diff --git a/core/core.services.yml b/core/core.services.yml
index b97667e9e618039c44b62cbf8cb26179d2e2fdef..df762b1a4c6d3ea42afe455bc0c58f1d0c00a17e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -676,6 +676,12 @@ services:
       - { name: module_install.uninstall_validator }
     arguments: ['@string_translation', '@extension.list.module', '@database']
     lazy: true
+  install_profile_uninstall_validator:
+    class: Drupal\Core\Extension\InstallProfileUninstallValidator
+    tags:
+      - { name: module_install.uninstall_validator }
+    arguments: ['@string_translation', '@extension.list.module', '@extension.list.theme', '%install_profile%', '%app.root%', '%site.path%']
+    lazy: true
   theme_handler:
     class: Drupal\Core\Extension\ThemeHandler
     arguments: ['%app.root%', '@config.factory', '@extension.list.theme']
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 9c8a859768a20ab9fc72c3e3d83e7d3cf61837da..62aa02ef5af8df571b84a2c5769e8d165761b3c4 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -109,8 +109,7 @@ function drupal_install_profile_distribution_name() {
     }
   }
   // At all other times, we load the profile via standard methods.
-  else {
-    $profile = \Drupal::installProfile();
+  elseif ($profile = \Drupal::installProfile()) {
     $info = \Drupal::service('extension.list.profile')->getExtensionInfo($profile);
   }
   return $info['distribution']['name'] ?? 'Drupal';
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 99db2aec84534981e3c78ce8244a69f2d66a69ea..c42f5043d3481cef31bf4d0a354b9458e714b21c 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -223,8 +223,10 @@ public static function root() {
   /**
    * Gets the active install profile.
    *
-   * @return string|null
-   *   The name of the active install profile.
+   * @return string|false|null
+   *   The name of the active install profile. FALSE indicates that the site is
+   *   not using an install profile. NULL indicates that the site has not yet
+   *   been installed.
    */
   public static function installProfile() {
     return static::getContainer()->getParameter('install_profile');
diff --git a/core/lib/Drupal/Core/Asset/LibrariesDirectoryFileFinder.php b/core/lib/Drupal/Core/Asset/LibrariesDirectoryFileFinder.php
index 9102efc475c663bd2ef279bb2f1fa0fa10dab5b0..adc00f0e61420e535ac9e087eb1e62cc0494229f 100644
--- a/core/lib/Drupal/Core/Asset/LibrariesDirectoryFileFinder.php
+++ b/core/lib/Drupal/Core/Asset/LibrariesDirectoryFileFinder.php
@@ -33,7 +33,7 @@ class LibrariesDirectoryFileFinder {
   /**
    * The install profile.
    *
-   * @var string
+   * @var string|false|null
    */
   protected $installProfile;
 
diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php
index 8331c1bc7c0400b523286b1fe30b40e3edfb0ab2..9ecd177e6f4b3687fd8c25adaf37de37a54bf3a6 100644
--- a/core/lib/Drupal/Core/Config/ConfigImporter.php
+++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
@@ -455,12 +455,18 @@ protected function createExtensionChangelist() {
 
     $this->extensionChangelist['module']['install'] = array_keys($install_required + $install_non_required);
 
-    // If we're installing the install profile ensure it comes last. This will
-    // occur when installing a site from configuration.
-    $install_profile_key = array_search($new_extensions['profile'], $this->extensionChangelist['module']['install'], TRUE);
-    if ($install_profile_key !== FALSE) {
-      unset($this->extensionChangelist['module']['install'][$install_profile_key]);
-      $this->extensionChangelist['module']['install'][] = $new_extensions['profile'];
+    // If we're installing the install profile ensure it comes last in the
+    // list of modules to be installed. This will occur when installing a site
+    // from configuration.
+    if (isset($new_extensions['profile'])) {
+      $install_profile_key = array_search($new_extensions['profile'], $this->extensionChangelist['module']['install'], TRUE);
+      // If the profile is not in the list of modules to be installed this will
+      // generate a validation error. See
+      // \Drupal\Core\EventSubscriber\ConfigImportSubscriber::validateModules().
+      if ($install_profile_key !== FALSE) {
+        unset($this->extensionChangelist['module']['install'][$install_profile_key]);
+        $this->extensionChangelist['module']['install'][] = $new_extensions['profile'];
+      }
     }
 
     // Get a list of themes with dependency weights as values.
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index a7f4ce4dd4d1c43d847632ff7385bac75b1ce7fe..ec94fb331c7535f95dde201ce73a330d9498c399 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -63,7 +63,7 @@ class ConfigInstaller implements ConfigInstallerInterface {
   /**
    * The name of the currently active installation profile.
    *
-   * @var string
+   * @var string|false|null
    */
   protected $installProfile;
 
diff --git a/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php b/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
index 1006013f5f2dd7fb0c44b9bd93372913bd0dd861..e48b255ed3f5ac331013aa2cdd629b6f22262d0b 100644
--- a/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
+++ b/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
@@ -31,7 +31,7 @@ class ExtensionInstallStorage extends InstallStorage {
    *
    * In the early installer this value can be NULL.
    *
-   * @var string|null
+   * @var string|false|null
    */
   protected $installProfile;
 
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 53af40828788e1e841478bffdb2212413208e032..229b388f71908f871a9756cf73127aea2280ba1c 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -1712,13 +1712,15 @@ protected function collectServiceIdMapping() {
   /**
    * Gets the active install profile.
    *
-   * @return string|null
-   *   The name of the any active install profile or distribution.
+   * @return string|false|null
+   *   The name of the active install profile or distribution, FALSE if there is
+   *   no install profile or NULL if Drupal is being installed.
    */
   protected function getInstallProfile() {
     $config = $this->getConfigStorage()->read('core.extension');
-
-    // Normalize an empty string to a NULL value.
+    if (is_array($config) && !array_key_exists('profile', $config)) {
+      return FALSE;
+    }
     return $config['profile'] ?? NULL;
   }
 
diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
index 29d78a24dadda35a8314d4b6c7698ee22430d065..d2259643b44ed4c1dfd0ab90b0e86ee4a1d1bd1c 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
@@ -112,24 +112,30 @@ protected function validateModules(ConfigImporter $config_importer) {
     // Get the install profile from the site's configuration.
     $current_core_extension = $config_importer->getStorageComparer()->getTargetStorage()->read('core.extension');
     $install_profile = $current_core_extension['profile'] ?? NULL;
+    $new_install_profile = $core_extension['profile'] ?? NULL;
 
     // Ensure the profile is not changing.
-    if ($install_profile !== $core_extension['profile']) {
+    if ($install_profile !== $new_install_profile) {
       if (InstallerKernel::installationAttempted()) {
         $config_importer->logError($this->t('The selected installation profile %install_profile does not match the profile stored in configuration %config_profile.', [
           '%install_profile' => $install_profile,
-          '%config_profile' => $core_extension['profile'],
+          '%config_profile' => $new_install_profile,
         ]));
         // If this error has occurred the other checks are irrelevant.
         return;
       }
-      else {
+      elseif ($new_install_profile) {
         $config_importer->logError($this->t('Cannot change the install profile from %profile to %new_profile once Drupal is installed.', [
           '%profile' => $install_profile,
-          '%new_profile' => $core_extension['profile'],
+          '%new_profile' => $new_install_profile,
         ]));
       }
     }
+    elseif ($new_install_profile && !isset($core_extension['module'][$new_install_profile])) {
+      $config_importer->logError($this->t('The install profile %profile is not in the list of installed modules.', [
+        '%profile' => $new_install_profile,
+      ]));
+    }
 
     // Get a list of modules with dependency weights as values.
     $module_data = $this->moduleExtensionList->getList();
@@ -180,12 +186,6 @@ protected function validateModules(ConfigImporter $config_importer) {
         }
       }
     }
-
-    // Ensure that the install profile is not being uninstalled.
-    if (in_array($install_profile, $uninstalls, TRUE)) {
-      $profile_name = $module_data[$install_profile]->info['name'];
-      $config_importer->logError($this->t('Unable to uninstall the %profile profile since it is the install profile.', ['%profile' => $profile_name]));
-    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
index b0cfa7d13e5a529287c7a785e9e7952bdf755736..307afa9aac12263a7c5b163ab5ecad0764329497 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -234,7 +234,18 @@ public function setProfileDirectoriesFromSettings() {
     if (!\Drupal::hasContainer() || !\Drupal::getContainer()->hasParameter('install_profile')) {
       return $this;
     }
-    if ($profile = \Drupal::installProfile()) {
+
+    $profile = \Drupal::installProfile();
+    // If $profile is FALSE then we need to add a fake directory as a profile
+    // directory in order to filter out profile provided modules. This ensures
+    // that, after uninstalling a profile, a site cannot install modules
+    // contained in an install profile. During installation $profile will be
+    // NULL, so we need to discover all modules and profiles.
+    if ($profile === FALSE) {
+      // cspell:ignore CNKDSIUSYFUISEFCB
+      $this->profileDirectories[] = '_does_not_exist_profile_CNKDSIUSYFUISEFCB';
+    }
+    elseif ($profile) {
       $this->profileDirectories[] = \Drupal::service('extension.list.profile')->getPath($profile);
     }
     return $this;
diff --git a/core/lib/Drupal/Core/Extension/ExtensionList.php b/core/lib/Drupal/Core/Extension/ExtensionList.php
index 6f4c4b7c3ac3b27b359bb952db955d7ba6b6923f..26dce53016e55e9480273c4e37d47e11919b69ca 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionList.php
@@ -112,7 +112,7 @@ abstract class ExtensionList {
   /**
    * The install profile used by the site.
    *
-   * @var string
+   * @var string|false|null
    */
   protected $installProfile;
 
diff --git a/core/lib/Drupal/Core/Extension/InstallProfileUninstallValidator.php b/core/lib/Drupal/Core/Extension/InstallProfileUninstallValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..97eb71f67e1255447a3126a7187c4db72ea6798e
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/InstallProfileUninstallValidator.php
@@ -0,0 +1,89 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Ensures install profile can only be uninstalled if the modules are available.
+ */
+class InstallProfileUninstallValidator implements ModuleUninstallValidatorInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * Extension discovery that scans all folders except profiles.
+   *
+   * @var \Drupal\Core\Extension\ExtensionDiscovery
+   */
+  protected ExtensionDiscovery $noProfileExtensionDiscovery;
+
+  public function __construct(
+    TranslationInterface $string_translation,
+    protected ModuleExtensionList $moduleExtensionList,
+    protected ThemeExtensionList $themeExtensionList,
+    protected string|false|null $installProfile,
+    protected string $root,
+    protected string $sitePath
+  ) {
+    $this->setStringTranslation($string_translation);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($module): array {
+    $reasons = [];
+
+    // When there are modules installed that only exist in the install profile's
+    // directory an install profile can not be uninstalled.
+    if ($module === $this->installProfile) {
+      $profile_name = $this->moduleExtensionList->get($module)->info['name'];
+
+      $profile_only_modules = array_diff_key($this->moduleExtensionList->getAllInstalledInfo(), $this->getExtensionDiscovery()->scan('module'));
+      // Remove the install profile as we're uninstalling it.
+      unset($profile_only_modules[$module]);
+      if (!empty($profile_only_modules)) {
+        $reasons[] = $this->t("The install profile '@profile_name' is providing the following module(s): @profile_modules",
+          ['@profile_name' => $profile_name, '@profile_modules' => implode(', ', array_keys($profile_only_modules))]);
+      }
+
+      $profile_only_themes = array_diff_key($this->themeExtensionList->getAllInstalledInfo(), $this->getExtensionDiscovery()->scan('theme'));
+      if (!empty($profile_only_themes)) {
+        $reasons[] = $this->t("The install profile '@profile_name' is providing the following theme(s): @profile_themes",
+          ['@profile_name' => $profile_name, '@profile_themes' => implode(', ', array_keys($profile_only_themes))]);
+      }
+    }
+    elseif (!empty($this->installProfile)) {
+      $extension = $this->moduleExtensionList->get($module);
+      // Ensure that the install profile does not depend on the module being
+      // uninstalled.
+      if (isset($extension->required_by[$this->installProfile])) {
+        $profile_name = $this->moduleExtensionList->get($this->installProfile)->info['name'];
+        $reasons[] = $this->t("The '@profile_name' install profile requires '@module_name'",
+          ['@profile_name' => $profile_name, '@module_name' => $extension->info['name']]);
+      }
+    }
+
+    return $reasons;
+  }
+
+  /**
+   * Gets an extension discovery object that ignores the install profile.
+   *
+   * @return \Drupal\Core\Extension\ExtensionDiscovery
+   *   An extension discovery object to look for extensions not in a profile
+   *   directory.
+   */
+  protected function getExtensionDiscovery(): ExtensionDiscovery {
+    if (!isset($this->noProfileExtensionDiscovery)) {
+      // cspell:ignore CNKDSIUSYFUISEFCB
+      $this->noProfileExtensionDiscovery = new ExtensionDiscovery($this->root, TRUE, ['_does_not_exist_profile_CNKDSIUSYFUISEFCB'], $this->sitePath);
+    }
+    return $this->noProfileExtensionDiscovery;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
index 6337cd08940b16fb66c986b787585da0db1e8ba6..1176b566796e89d2f890adea768f773adbf3c54b 100644
--- a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
@@ -180,8 +180,6 @@ protected function doList() {
         $active_profile->info['hidden'] = TRUE;
       }
 
-      // The installation profile is required.
-      $active_profile->info['required'] = TRUE;
       // Add a default distribution name if the profile did not provide one.
       // @see install_profile_info()
       // @see drupal_install_profile_distribution_name()
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index 289ecffe41fbaf3d3884851ee6050985775e1005..7323af67e9188f370a5d909fc608a960451bf065 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -500,7 +500,15 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
 
       // Remove the module's entry from the config. Don't check schema when
       // uninstalling a module since we are only clearing a key.
-      \Drupal::configFactory()->getEditable('core.extension')->clear("module.$module")->save(TRUE);
+      $core_extension = \Drupal::configFactory()->getEditable('core.extension');
+      $core_extension->clear("module.$module");
+      // If the install profile is being uninstalled then remove the site's
+      // profile key to indicate that the site no longer has an installation
+      // profile.
+      if ($core_extension->get('profile') === $module) {
+        $core_extension->clear('profile');
+      }
+      $core_extension->save(TRUE);
 
       // Update the module handler to remove the module.
       // The current ModuleHandler instance is obsolete with the kernel rebuild
diff --git a/core/lib/Drupal/Core/ProxyClass/Extension/InstallProfileUninstallValidator.php b/core/lib/Drupal/Core/ProxyClass/Extension/InstallProfileUninstallValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..9c7213dea6a20e7ce2fa84c236b79097d24fddd6
--- /dev/null
+++ b/core/lib/Drupal/Core/ProxyClass/Extension/InstallProfileUninstallValidator.php
@@ -0,0 +1,88 @@
+<?php
+// phpcs:ignoreFile
+
+/**
+ * This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\Core\Extension\InstallProfileUninstallValidator' "core/lib/Drupal/Core".
+ */
+
+namespace Drupal\Core\ProxyClass\Extension {
+
+    /**
+     * Provides a proxy class for \Drupal\Core\Extension\InstallProfileUninstallValidator.
+     *
+     * @see \Drupal\Component\ProxyBuilder
+     */
+    class InstallProfileUninstallValidator implements \Drupal\Core\Extension\ModuleUninstallValidatorInterface
+    {
+
+        use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
+
+        /**
+         * The id of the original proxied service.
+         *
+         * @var string
+         */
+        protected $drupalProxyOriginalServiceId;
+
+        /**
+         * The real proxied service, after it was lazy loaded.
+         *
+         * @var \Drupal\Core\Extension\InstallProfileUninstallValidator
+         */
+        protected $service;
+
+        /**
+         * The service container.
+         *
+         * @var \Symfony\Component\DependencyInjection\ContainerInterface
+         */
+        protected $container;
+
+        /**
+         * Constructs a ProxyClass Drupal proxy object.
+         *
+         * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+         *   The container.
+         * @param string $drupal_proxy_original_service_id
+         *   The service ID of the original service.
+         */
+        public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
+        {
+            $this->container = $container;
+            $this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
+        }
+
+        /**
+         * Lazy loads the real service from the container.
+         *
+         * @return object
+         *   Returns the constructed real service.
+         */
+        protected function lazyLoadItself()
+        {
+            if (!isset($this->service)) {
+                $this->service = $this->container->get($this->drupalProxyOriginalServiceId);
+            }
+
+            return $this->service;
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function validate($module)
+        {
+            return $this->lazyLoadItself()->validate($module);
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function setStringTranslation(\Drupal\Core\StringTranslation\TranslationInterface $translation)
+        {
+            return $this->lazyLoadItself()->setStringTranslation($translation);
+        }
+
+    }
+
+}
diff --git a/core/modules/config/tests/src/Functional/ConfigImportAllTest.php b/core/modules/config/tests/src/Functional/ConfigImportAllTest.php
index 581434e0e273a60a9274a661bc54640e07b25301..8b574c83807dfe9aadbac9e21b1d06dac6b6e93f 100644
--- a/core/modules/config/tests/src/Functional/ConfigImportAllTest.php
+++ b/core/modules/config/tests/src/Functional/ConfigImportAllTest.php
@@ -110,7 +110,7 @@ public function testInstallUninstall() {
 
     $all_modules = \Drupal::service('extension.list.module')->getList();
     $database_module = \Drupal::service('database')->getProvider();
-    $expected_modules = ['path_alias', 'system', 'user', 'testing', $database_module];
+    $expected_modules = ['path_alias', 'system', 'user', $database_module];
 
     // Ensure that only core required modules and the install profile can not be uninstalled.
     $validation_reasons = \Drupal::service('module_installer')->validateUninstall(array_keys($all_modules));
@@ -118,8 +118,8 @@ public function testInstallUninstall() {
     $this->assertEqualsCanonicalizing($expected_modules, $validation_modules);
 
     $modules_to_uninstall = array_filter($all_modules, function ($module) {
-      // Filter required and not enabled modules.
-      if (!empty($module->info['required']) || $module->status == FALSE) {
+      // Filter profiles, and required and not enabled modules.
+      if (!empty($module->info['required']) || $module->status == FALSE || $module->getType() === 'profile') {
         return FALSE;
       }
       return TRUE;
diff --git a/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php b/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php
index c3fac44ff29f1722afb7dbca772cae485752f9f6..67b3f93e520780c2dbbb442ad3f600089f6881f3 100644
--- a/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php
+++ b/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php
@@ -51,7 +51,10 @@ protected function setUp(): void {
   }
 
   /**
-   * Tests config importer cannot uninstall install profiles.
+   * Tests config importer can uninstall install profiles.
+   *
+   * Install profiles can be uninstalled when none of the modules or themes
+   * they contain are installed.
    *
    * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
    */
@@ -67,10 +70,11 @@ public function testInstallProfileValidation() {
     $this->drupalGet('admin/config/development/configuration');
     $this->submitForm([], 'Import all');
     $this->assertSession()->pageTextContains('The configuration cannot be imported because it failed validation for the following reasons:');
-    $this->assertSession()->pageTextContains('Unable to uninstall the Testing config import profile since it is the install profile.');
+    $this->assertSession()->pageTextContains("The install profile 'Testing config import' is providing the following module(s): testing_config_import_module");
 
     // Uninstall dependencies of testing_config_import.
     unset($core['module']['syslog']);
+    unset($core['module']['testing_config_import_module']);
     unset($core['theme']['stark']);
     $core['module']['testing_config_import'] = 0;
     $core['theme']['test_theme_theme'] = 0;
@@ -84,8 +88,26 @@ public function testInstallProfileValidation() {
     $this->assertSession()->pageTextContains('The configuration was imported successfully.');
     $this->rebuildContainer();
     $this->assertFalse(\Drupal::moduleHandler()->moduleExists('syslog'), 'The syslog module has been uninstalled.');
+    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('testing_config_import_module'), 'The testing_config_import_module module has been uninstalled.');
     $this->assertFalse(\Drupal::service('theme_handler')->themeExists('stark'), 'The stark theme has been uninstalled.');
     $this->assertTrue(\Drupal::service('theme_handler')->themeExists('test_theme_theme'), 'The test_theme_theme theme has been installed.');
+
+    // Uninstall testing_config_import profile without removing the profile key.
+    unset($core['module']['testing_config_import']);
+    $sync->write('core.extension', $core);
+    $this->drupalGet('admin/config/development/configuration');
+    $this->submitForm([], 'Import all');
+    $this->assertSession()->pageTextContains('The configuration cannot be imported because it failed validation for the following reasons:');
+    $this->assertSession()->pageTextContains('The install profile testing_config_import is not in the list of installed modules.');
+
+    // Uninstall testing_config_import profile properly.
+    unset($core['profile']);
+    $sync->write('core.extension', $core);
+    $this->drupalGet('admin/config/development/configuration');
+    $this->submitForm([], 'Import all');
+    $this->assertSession()->pageTextContains('The configuration was imported successfully.');
+    $this->rebuildContainer();
+    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('testing_config_import'), 'The testing_config_import profile has been uninstalled.');
   }
 
 }
diff --git a/core/modules/system/src/Form/ModulesUninstallConfirmForm.php b/core/modules/system/src/Form/ModulesUninstallConfirmForm.php
index 0af5e4dade92d19f9b4e7aa0ffe1847542201802..7014e9144ed601f6cb698d3b384255737d4be773 100644
--- a/core/modules/system/src/Form/ModulesUninstallConfirmForm.php
+++ b/core/modules/system/src/Form/ModulesUninstallConfirmForm.php
@@ -76,13 +76,16 @@ class ModulesUninstallConfirmForm extends ConfirmFormBase {
    *   The entity type manager.
    * @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module
    *   The module extension list.
+   * @param string|false|null $installProfile
+   *   The install profile.
    */
-  public function __construct(ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, ConfigManagerInterface $config_manager, EntityTypeManagerInterface $entity_type_manager, ModuleExtensionList $extension_list_module) {
+  public function __construct(ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, ConfigManagerInterface $config_manager, EntityTypeManagerInterface $entity_type_manager, ModuleExtensionList $extension_list_module, protected string|false|null $installProfile = NULL) {
     $this->moduleInstaller = $module_installer;
     $this->keyValueExpirable = $key_value_expirable;
     $this->configManager = $config_manager;
     $this->entityTypeManager = $entity_type_manager;
     $this->moduleExtensionList = $extension_list_module;
+    $this->installProfile ??= \Drupal::installProfile();
   }
 
   /**
@@ -94,7 +97,8 @@ public static function create(ContainerInterface $container) {
       $container->get('keyvalue.expirable')->get('modules_uninstall'),
       $container->get('config.manager'),
       $container->get('entity_type.manager'),
-      $container->get('extension.list.module')
+      $container->get('extension.list.module'),
+      $container->getParameter('install_profile')
     );
   }
 
@@ -156,6 +160,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       }, $this->modules),
     ];
 
+    if (!empty($this->installProfile) && in_array($this->installProfile, $this->modules, TRUE)) {
+      $form['profile']['#markup'] = '<p>' . $this->t('Once uninstalled, the %install_profile profile cannot be reinstalled.', ['%install_profile' => $data[$this->installProfile]->info['name']]) . '</p>';
+    }
+
     // List the dependent entities.
     $this->addDependencyListsToForm($form, 'module', $this->modules, $this->configManager, $this->entityTypeManager);
 
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 09ea1af714f8f2d54d4cfabca8b037589ef66233..26cc2577742486e23ed865c643d9adf1861143c3 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -85,7 +85,7 @@ function system_requirements($phase) {
     // Display the currently active installation profile, if the site
     // is not running the default installation profile.
     $profile = \Drupal::installProfile();
-    if ($profile != 'standard') {
+    if ($profile != 'standard' && !empty($profile)) {
       $info = $module_extension_list->getExtensionInfo($profile);
       $requirements['install_profile'] = [
         'title' => t('Installation profile'),
diff --git a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
index faf58cb8f49a4ecabd74e22110e6265e18e6563b..a2d1535da6616a60a63280348166b18578d8608e 100644
--- a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
+++ b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
@@ -201,9 +201,25 @@ public function testUninstallProfileDependency() {
     $this->assertNotContains($profile, $uninstalled_modules, 'The installation profile is not in the list of uninstalled modules.');
 
     // Try uninstalling the required module.
-    $this->expectException(ModuleUninstallValidatorException::class);
-    $this->expectExceptionMessage('The following reasons prevent the modules from being uninstalled: The Testing install profile dependencies module is required');
-    $this->moduleInstaller()->uninstall([$dependency]);
+    try {
+      $this->moduleInstaller()->uninstall([$dependency]);
+      $this->fail('Expected ModuleUninstallValidatorException not thrown');
+    }
+    catch (ModuleUninstallValidatorException $e) {
+      $this->assertEquals("The following reasons prevent the modules from being uninstalled: The 'Testing install profile dependencies' install profile requires 'Database Logging'", $e->getMessage());
+    }
+
+    // Try uninstalling the install profile.
+    $this->assertSame('testing_install_profile_dependencies', $this->container->getParameter('install_profile'));
+    $result = $this->moduleInstaller()->uninstall([$profile]);
+    $this->assertTrue($result, 'ModuleInstaller::uninstall() returns TRUE.');
+    $this->assertFalse($this->moduleHandler()->moduleExists($profile));
+    $this->assertFalse($this->container->getParameter('install_profile'));
+
+    // Try uninstalling the required module again.
+    $result = $this->moduleInstaller()->uninstall([$dependency]);
+    $this->assertTrue($result, 'ModuleInstaller::uninstall() returns TRUE.');
+    $this->assertFalse($this->moduleHandler()->moduleExists($dependency));
   }
 
   /**
@@ -235,7 +251,7 @@ public function testProfileAllDependencies() {
 
     // Try uninstalling the dependencies.
     $this->expectException(ModuleUninstallValidatorException::class);
-    $this->expectExceptionMessage('The following reasons prevent the modules from being uninstalled: The Testing install profile all dependencies module is required');
+    $this->expectExceptionMessage("The following reasons prevent the modules from being uninstalled: The 'Testing install profile all dependencies' install profile requires 'Database Logging'; The 'Testing install profile all dependencies' install profile requires 'Ban'");
     $this->moduleInstaller()->uninstall($dependencies);
   }
 
diff --git a/core/profiles/testing_config_import/testing_config_import.info.yml b/core/profiles/testing_config_import/testing_config_import.info.yml
index 29f5e5239e10f0586a50e1b92ab90681e5da18d6..b1ae768473d8bd87259b3ceac262fee731c30e2b 100644
--- a/core/profiles/testing_config_import/testing_config_import.info.yml
+++ b/core/profiles/testing_config_import/testing_config_import.info.yml
@@ -5,5 +5,6 @@ version: VERSION
 hidden: true
 install:
   - syslog
+  - testing_config_import_module
 themes:
   - stark
diff --git a/core/profiles/testing_config_import/testing_config_import_module/testing_config_import_module.info.yml b/core/profiles/testing_config_import/testing_config_import_module/testing_config_import_module.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2370c42591aa161034fa2857065b2a14fac5af85
--- /dev/null
+++ b/core/profiles/testing_config_import/testing_config_import_module/testing_config_import_module.info.yml
@@ -0,0 +1,5 @@
+name: 'Testing config import module'
+type: module
+description: 'Tests install profiles in the config importer.'
+version: VERSION
+package: Testing
diff --git a/core/profiles/testing_config_import/testing_config_import_theme/testing_config_import_theme.info.yml b/core/profiles/testing_config_import/testing_config_import_theme/testing_config_import_theme.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7e5157d78a835fbeaac3017de6332b4ced7afad7
--- /dev/null
+++ b/core/profiles/testing_config_import/testing_config_import_theme/testing_config_import_theme.info.yml
@@ -0,0 +1,13 @@
+name: 'Testing config import theme'
+type: theme
+base theme: stable9
+description: 'Theme for testing profile uninstall'
+version: VERSION
+regions:
+  sidebar_first: 'Left sidebar'
+  sidebar_second: 'Right sidebar'
+  content: Content
+  header: Header
+  footer: Footer
+  highlighted: Highlighted
+  help: Help
diff --git a/core/tests/Drupal/FunctionalTests/Installer/InstallProfileDependenciesTest.php b/core/tests/Drupal/FunctionalTests/Installer/InstallProfileDependenciesTest.php
index f5c75f52b8dc63f5f749756260e4e454032bdc31..7087cecf8fc74ff92825a16d7e27355bd73fb770 100644
--- a/core/tests/Drupal/FunctionalTests/Installer/InstallProfileDependenciesTest.php
+++ b/core/tests/Drupal/FunctionalTests/Installer/InstallProfileDependenciesTest.php
@@ -46,7 +46,7 @@ public function testUninstallingModules() {
       $this->fail('Uninstalled dblog module.');
     }
     catch (ModuleUninstallValidatorException $e) {
-      $this->assertStringContainsString('The Testing install profile dependencies module is required', $e->getMessage());
+      $this->assertStringContainsString("The 'Testing install profile dependencies' install profile requires 'Database Logging'", $e->getMessage());
     }
   }
 
diff --git a/core/tests/Drupal/FunctionalTests/Installer/InstallProfileUninstallTest.php b/core/tests/Drupal/FunctionalTests/Installer/InstallProfileUninstallTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..57720e6b20b243180e62f93c9979f3eea82242dc
--- /dev/null
+++ b/core/tests/Drupal/FunctionalTests/Installer/InstallProfileUninstallTest.php
@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\FunctionalTests\Installer;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests that an install profile can be uninstalled.
+ *
+ * @group Extension
+ */
+class InstallProfileUninstallTest extends BrowserTestBase {
+
+  /**
+   * The profile to install as a basis for testing.
+   *
+   * This profile is used because it contains a submodule.
+   *
+   * @var string
+   */
+  protected $profile = 'testing_config_import';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['config'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * Tests a user can uninstall install profiles.
+   */
+  public function testUninstallInstallProfile(): void {
+    $this->drupalLogin($this->drupalCreateUser(admin: TRUE));
+
+    // Ensure that the installation profile is present on the status report.
+    $this->drupalGet('admin/reports/status');
+    $this->assertSession()->pageTextContains("Installation profile");
+    $this->assertSession()->pageTextContains("Testing config import");
+
+    // Test uninstalling a module provided by the install profile.
+    $this->drupalGet('admin/modules/uninstall');
+    $this->assertSession()->pageTextContains("The install profile 'Testing config import' is providing the following module(s): testing_config_import_module");
+    $this->assertSession()->fieldDisabled('uninstall[testing_config_import]');
+    $this->assertSession()->fieldEnabled('uninstall[testing_config_import_module]')->check();
+    $this->getSession()->getPage()->pressButton('Uninstall');
+    $this->getSession()->getPage()->pressButton('Uninstall');
+    $this->assertSession()->pageTextContains('The selected modules have been uninstalled.');
+    $this->assertSession()->fieldNotExists('uninstall[testing_config_import_module]');
+    $this->assertSession()->pageTextNotContains("The install profile 'Testing config import' is providing the following module(s): testing_config_import_module");
+
+    // Test that we can reinstall the module from the profile.
+    $this->drupalGet('admin/modules');
+    $this->assertSession()->pageTextContains('Testing config import module');
+    $this->assertSession()->fieldEnabled('modules[testing_config_import_module][enable]')->check();
+    $this->getSession()->getPage()->pressButton('Install');
+    $this->assertSession()->pageTextContains('Module Testing config import module has been installed.');
+
+    // Install a theme provided by the module.
+    $this->drupalGet('admin/appearance');
+    $this->clickLink("Install Testing config import theme theme");
+    $this->assertSession()->pageTextContains("The Testing config import theme theme has been installed.");
+
+    // Test that uninstalling the module and then the profile works.
+    $this->drupalGet('admin/modules/uninstall');
+    $this->assertSession()->pageTextContains("The install profile 'Testing config import' is providing the following module(s): testing_config_import_module");
+    $this->assertSession()->pageTextContains("The install profile 'Testing config import' is providing the following theme(s): testing_config_import_theme");
+    $this->assertSession()->fieldEnabled('uninstall[testing_config_import_module]')->check();
+    $this->getSession()->getPage()->pressButton('Uninstall');
+    $this->getSession()->getPage()->pressButton('Uninstall');
+    $this->assertSession()->pageTextContains('The selected modules have been uninstalled.');
+    $this->assertSession()->fieldNotExists('uninstall[testing_config_import_module]');
+    $this->drupalGet('admin/appearance');
+    $this->clickLink("Uninstall Testing config import theme theme");
+    $this->assertSession()->pageTextContains("The Testing config import theme theme has been uninstalled.");
+    $this->drupalGet('admin/modules/uninstall');
+    $this->assertSession()->pageTextNotContains("The install profile 'Testing config import' is providing the following module(s): testing_config_import_module");
+    $this->assertSession()->pageTextNotContains("The install profile 'Testing config import' is providing the following theme(s): testing_config_import_theme");
+    $this->assertSession()->fieldEnabled('uninstall[testing_config_import]')->check();
+    $this->getSession()->getPage()->pressButton('Uninstall');
+    $this->assertSession()->pageTextContains('Once uninstalled, the Testing config import profile cannot be reinstalled.');
+    $this->getSession()->getPage()->pressButton('Uninstall');
+    $this->assertSession()->pageTextContains('The selected modules have been uninstalled.');
+    $this->assertSession()->fieldNotExists('uninstall[testing_config_import]');
+
+    // Test that the module contained in the profile is no longer available to
+    // install.
+    $this->drupalGet('admin/modules');
+    $this->assertSession()->pageTextNotContains('Testing config import module');
+    $this->assertSession()->fieldNotExists('modules[testing_config_import_module][enable]');
+
+    // Ensure that the installation profile is not present on the status report.
+    $this->drupalGet('admin/reports/status');
+    $this->assertSession()->pageTextNotContains("Installation profile");
+    $this->assertSession()->pageTextNotContains("Testing config import");
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php b/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php
index 5ecb85d0afb688f5258b400638437794e1302d94..abe08cb9a2e22b5e25be38555a8b1caea0978d8e 100644
--- a/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php
+++ b/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php
@@ -217,7 +217,10 @@ public static function providerMappingInterpretation(): \Generator {
         'theme',
         'profile',
       ],
-      ['_core'],
+      [
+        '_core',
+        'profile',
+      ],
       [],
     ];
 
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
index e690f6d016e72ace585ea4e8364906778c04fda2..091c8a8f6031d134579cc379e77b6e278db3a9e8 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
@@ -752,6 +752,10 @@ public function testInstallProfile() {
    * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
    */
   public function testInstallProfileMisMatch() {
+    // Install profiles can not be changed. They can only be uninstalled. We
+    // need to set an install profile prior to testing because KernelTestBase
+    // tests do not use one.
+    $this->setInstallProfile('minimal');
     $sync = $this->container->get('config.storage.sync');
 
     $extensions = $sync->read('core.extension');
@@ -765,14 +769,10 @@ public function testInstallProfileMisMatch() {
       $this->fail('ConfigImporterException not thrown; an invalid import was not stopped due to missing dependencies.');
     }
     catch (ConfigImporterException $e) {
-      $expected = static::FAIL_MESSAGE . PHP_EOL . 'Cannot change the install profile from <em class="placeholder"></em> to <em class="placeholder">this_will_not_work</em> once Drupal is installed.';
+      $expected = static::FAIL_MESSAGE . PHP_EOL . 'Cannot change the install profile from <em class="placeholder">minimal</em> to <em class="placeholder">this_will_not_work</em> once Drupal is installed.';
       $this->assertEquals($expected, $e->getMessage(), 'There were errors validating the config synchronization.');
       $error_log = $config_importer->getErrors();
-      // Install profiles can not be changed. Note that KernelTestBase currently
-      // does not use an install profile. This situation should be impossible
-      // to get in but site's can removed the install profile setting from
-      // settings.php so the test is valid.
-      $this->assertEquals(['Cannot change the install profile from  to this_will_not_work once Drupal is installed.'], $error_log);
+      $this->assertEquals(['Cannot change the install profile from minimal to this_will_not_work once Drupal is installed.'], $error_log);
     }
   }
 
diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php
index 2e449e8c7d1d863bda2a8abe541fd0db9575e3a1..7e3fc2d995f4fca23a42e301e5e04703afc5bf40 100644
--- a/core/tests/Drupal/KernelTests/KernelTestBase.php
+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -414,7 +414,6 @@ protected function bootKernel() {
     $this->container->get('config.storage')->write('core.extension', [
       'module' => array_fill_keys($modules, 0),
       'theme' => [],
-      'profile' => '',
     ]);
 
     $settings = Settings::getAll();
diff --git a/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php b/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php
index 44a01e2f6c7b4eae270a305f2b5ca9594a1ac266..5818422fdcddaa1a5a2a5a2c45fc7b0365fa8719 100644
--- a/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php
@@ -29,7 +29,7 @@ public function testConfigIsEmpty() {
     $expected = [
       'module' => [],
       'theme' => [],
-      'profile' => '',
+      'profile' => NULL,
     ];
     $this->assertEquals($expected, $config);
   }