diff --git a/core/modules/package_manager/package_manager.install b/core/modules/package_manager/package_manager.install
index 22d42a346ead7fc5b93e132b14b7f150bf057528..840eaf5b0c2d74161af9c94f3d55c5a6151375be 100644
--- a/core/modules/package_manager/package_manager.install
+++ b/core/modules/package_manager/package_manager.install
@@ -12,8 +12,27 @@
 use Drupal\package_manager\Exception\StageFailureMarkerException;
 use Drupal\package_manager\FailureMarker;
 use PhpTuf\ComposerStager\API\Exception\ExceptionInterface;
+use PhpTuf\ComposerStager\API\Exception\LogicException;
 use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
 
+/**
+ * Implements hook_install().
+ */
+function package_manager_install(): void {
+  $executable_finder = \Drupal::service(ExecutableFinderInterface::class);
+
+  $config = \Drupal::configFactory()->getEditable('package_manager.settings');
+  foreach (['composer', 'rsync'] as $executable_name) {
+    try {
+      $config->set("executables.$executable_name", $executable_finder->find($executable_name));
+    }
+    catch (LogicException) {
+      // Couldn't find the executable; don't change the config.
+    }
+  }
+  $config->save();
+}
+
 /**
  * Implements hook_requirements().
  */
diff --git a/core/modules/package_manager/package_manager.links.menu.yml b/core/modules/package_manager/package_manager.links.menu.yml
new file mode 100644
index 0000000000000000000000000000000000000000..65fe5e04146eb8625414b63c4142d762713c0f8b
--- /dev/null
+++ b/core/modules/package_manager/package_manager.links.menu.yml
@@ -0,0 +1,5 @@
+package_manager.settings:
+  title: 'Package Manager'
+  parent: system.admin_config_development
+  description: 'Configure Package Manager settings.'
+  route_name: package_manager.settings
diff --git a/core/modules/package_manager/package_manager.routing.yml b/core/modules/package_manager/package_manager.routing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e0e347c23b35f02750e4460ace033ae5cad86699
--- /dev/null
+++ b/core/modules/package_manager/package_manager.routing.yml
@@ -0,0 +1,6 @@
+package_manager.settings:
+  path: '/admin/config/development/package-manager'
+  defaults:
+    _form: 'Drupal\package_manager\Form\SettingsForm'
+  requirements:
+    _permission: 'administer software updates'
diff --git a/core/modules/package_manager/package_manager.services.yml b/core/modules/package_manager/package_manager.services.yml
index 88a837631012faf1c718d76ca80a1a5a555204c4..2d1cc482ad89d48028d088413ca31f8b336df344 100644
--- a/core/modules/package_manager/package_manager.services.yml
+++ b/core/modules/package_manager/package_manager.services.yml
@@ -43,6 +43,7 @@ services:
     arguments:
       $appRoot: '%app.root%'
   Drupal\package_manager\FailureMarker: {}
+  Drupal\package_manager\EventSubscriber\ConfigSubscriber: {}
   Drupal\package_manager\EventSubscriber\UpdateDataSubscriber: {}
   Drupal\package_manager\EventSubscriber\ChangeLogger:
     calls:
diff --git a/core/modules/package_manager/src/EventSubscriber/ConfigSubscriber.php b/core/modules/package_manager/src/EventSubscriber/ConfigSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..0155ded188837b7ce5fec89d03bd4fe335500385
--- /dev/null
+++ b/core/modules/package_manager/src/EventSubscriber/ConfigSubscriber.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\package_manager\EventSubscriber;
+
+use Drupal\Core\Config\ConfigEvents;
+use Drupal\Core\Config\StorageTransformEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Event subscriber to remove executable paths from exported config.
+ *
+ * @internal
+ *   This is an internal part of Package Manager and may be changed or removed
+ *   at any time without warning. External code should not interact with this
+ *   class.
+ */
+final class ConfigSubscriber implements EventSubscriberInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents(): array {
+    return [
+      ConfigEvents::STORAGE_TRANSFORM_EXPORT => 'onExport',
+    ];
+  }
+
+  /**
+   * Removes executable paths from exported config.
+   *
+   * @param \Drupal\Core\Config\StorageTransformEvent $event
+   *   The event object.
+   */
+  public function onExport(StorageTransformEvent $event): void {
+    $storage = $event->getStorage();
+
+    $settings = $storage->read('package_manager.settings');
+    $settings['executables'] = [
+      'composer' => NULL,
+      'rsync' => NULL,
+    ];
+    $storage->write('package_manager.settings', $settings);
+  }
+
+}
diff --git a/core/modules/package_manager/src/Form/SettingsForm.php b/core/modules/package_manager/src/Form/SettingsForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..0b690c70cd95cd0ead4216cba1216e2062915419
--- /dev/null
+++ b/core/modules/package_manager/src/Form/SettingsForm.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\package_manager\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\ConfigTarget;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Defines a form to configure Package Manager settings.
+ *
+ * @internal
+ *   This is an internal part of Package Manager and may be changed or removed
+ *   at any time without warning. External code should not interact with this
+ *   class.
+ */
+final class SettingsForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId(): string {
+    return 'package_manager_settings_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames(): array {
+    return ['package_manager.settings'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state): array {
+    $value_or_null = fn (string $value): ?string => trim($value) ?: NULL;
+
+    $form['executables']['composer'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Path to Composer'),
+      '#description' => $this->t('The full path to the Composer executable (usually named <code>composer</code> or <code>composer.phar</code>. Leave blank to auto-detect.'),
+      '#config_target' => new ConfigTarget(
+        'package_manager.settings',
+        'executables.composer',
+        toConfig: $value_or_null,
+      ),
+    ];
+    $form['executables']['rsync'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Path to rsync'),
+      '#description' => $this->t('The full path to the <code>rsync</code> executable. Leave blank to auto-detect.'),
+      '#config_target' => new ConfigTarget(
+        'package_manager.settings',
+        'executables.rsync',
+        toConfig: $value_or_null,
+      ),
+    ];
+    return parent::buildForm($form, $form_state);
+  }
+
+}
diff --git a/core/modules/package_manager/tests/src/Kernel/ConfigTest.php b/core/modules/package_manager/tests/src/Kernel/ConfigTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6e7b97c2bfad6bc107405428a14a443be57b5e3c
--- /dev/null
+++ b/core/modules/package_manager/tests/src/Kernel/ConfigTest.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\package_manager\Kernel;
+
+use Drupal\Core\Config\StorageManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
+
+/**
+ * @group package_manager
+ * @internal
+ */
+class ConfigTest extends PackageManagerKernelTestBase {
+
+  /**
+   * Tests that Package Manager auto-detects executable paths on install.
+   */
+  public function testExecutablePathsAutoDetection(): void {
+    $this->container->set(ExecutableFinderInterface::class, new class () implements ExecutableFinderInterface {
+
+      /**
+       * {@inheritdoc}
+       */
+      public function find(string $name): string {
+        return match ($name) {
+          'composer' => '/fake/path/to/composer',
+          'rsync' => '/fake/path/to/rsync',
+        };
+      }
+
+    });
+
+    $executables = $this->config('package_manager.settings')
+      ->get('executables');
+    $this->assertNull($executables['composer']);
+    $this->assertNull($executables['rsync']);
+
+    $this->container->get(ModuleHandlerInterface::class)
+      ->loadInclude('package_manager', 'install');
+    package_manager_install();
+
+    $executables = $this->config('package_manager.settings')
+      ->get('executables');
+    $this->assertSame('/fake/path/to/composer', $executables['composer']);
+    $this->assertSame('/fake/path/to/rsync', $executables['rsync']);
+
+    // The executable paths should be removed from exported config.
+    /** @var \Drupal\Core\Config\StorageInterface $export */
+    $export = $this->container->get(StorageManagerInterface::class)
+      ->getStorage();
+    $exported_settings = $export->read('package_manager.settings');
+    $this->assertIsArray($exported_settings);
+    $this->assertNull($exported_settings['executables']['composer']);
+    $this->assertNull($exported_settings['executables']['rsync']);
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php b/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php
index fcdf72d1b8c33798072556fc31131a22dc9aaaf6..13946e62a624c9f806c9c2801d339837aaf07783 100644
--- a/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php
+++ b/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php
@@ -43,6 +43,7 @@ class DefaultConfigTest extends KernelTestBase {
    */
   public static $skippedConfig = [
     'locale.settings' => ['path: '],
+    'package_manager.settings' => ['composer: ', 'rsync: '],
     'syslog.settings' => ['facility: '],
   ];