diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index 413c650d4f..1ea6d85e35 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -148,6 +148,7 @@ public function alter(ContainerBuilder $container) {
 
           case 'path_alias.repository':
             $definition->addArgument(new Reference('database'));
+            $definition->addArgument(new Reference('language_manager'));
             break;
 
           case 'path_alias.whitelist':
diff --git a/core/lib/Drupal/Core/Path/AliasRepository.php b/core/lib/Drupal/Core/Path/AliasRepository.php
index d6f3717b76..1a3c40e095 100644
--- a/core/lib/Drupal/Core/Path/AliasRepository.php
+++ b/core/lib/Drupal/Core/Path/AliasRepository.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Database\Query\Condition;
 use Drupal\Core\Database\Query\SelectInterface;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 
 /**
  * Provides the default path alias lookup operations.
@@ -24,15 +25,24 @@ class AliasRepository implements AliasRepositoryInterface {
    */
   protected $connection;
 
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
   /**
    * Constructs an AliasRepository object.
    *
    * @param \Drupal\Core\Database\Connection $connection
    *   A database connection for reading and writing path aliases.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   Language manager service.
    */
-  public function __construct(Connection $connection) {
+  public function __construct(Connection $connection, LanguageManagerInterface $language_manager) {
     $this->connection = $connection;
-
+    $this->languageManager = $language_manager;
     // This is used as base class by the new class, so we do not trigger
     // deprecation notices when that or any child class is instantiated.
     $new_class = 'Drupal\path_alias\AliasRepository';
@@ -135,20 +145,29 @@ protected function getBaseQuery() {
    *   that language it will search paths without language.
    */
   protected function addLanguageFallback(SelectInterface $query, $langcode) {
-    // Always get the language-specific alias before the language-neutral one.
-    // For example 'de' is less than 'und' so the order needs to be ASC, while
-    // 'xx-lolspeak' is more than 'und' so the order needs to be DESC.
-    $langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED];
-    if ($langcode === LanguageInterface::LANGCODE_NOT_SPECIFIED) {
-      array_pop($langcode_list);
-    }
-    elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
-      $query->orderBy('base_table.langcode', 'DESC');
-    }
-    else {
-      $query->orderBy('base_table.langcode', 'ASC');
+    // Always get the language-specific alias before the language-neutral one,
+    // and always ensure those are candidates even if the language manager does
+    // not return them. For example 'de' is less than 'und' so the order needs
+    // to be ASC, while 'xx-lolspeak' is more than 'und' so the order needs to
+    // be DESC. Modules that extend the list of languages to fall back on by
+    // implementing hook_language_fallback_candidates_path_alias_alter() may
+    // also need to alter the ordering in the query, which they can do via
+    // hook_tag_path_alias_language_fallback_alter().
+    $langcode_list = [$langcode => $langcode] + $this->languageManager->getFallbackCandidates([
+      'langcode' => $langcode,
+      'operation' => 'path_alias',
+    ]) + [LanguageInterface::LANGCODE_NOT_SPECIFIED => LanguageInterface::LANGCODE_NOT_SPECIFIED];
+
+    if ($langcode !== LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+      if ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+        $query->orderBy('base_table.langcode', 'DESC');
+      }
+      else {
+        $query->orderBy('base_table.langcode', 'ASC');
+      }
     }
     $query->condition('base_table.langcode', $langcode_list, 'IN');
+    $query->addTag('path_alias_language_fallback');
   }
 
 }
diff --git a/core/modules/language/src/ConfigurableLanguageManager.php b/core/modules/language/src/ConfigurableLanguageManager.php
index 143adf604b..43bef0be86 100644
--- a/core/modules/language/src/ConfigurableLanguageManager.php
+++ b/core/modules/language/src/ConfigurableLanguageManager.php
@@ -367,12 +367,16 @@ public function getFallbackCandidates(array $context = []) {
     if ($this->isMultilingual()) {
       $candidates = [];
       if (empty($context['operation']) || $context['operation'] != 'locale_lookup') {
-        // If the fallback context is not locale_lookup, initialize the
-        // candidates with languages ordered by weight and add
+        // If the fallback context is not locale_lookup or path_alias,
+        // initialize the candidates with languages ordered by weight and add
         // LanguageInterface::LANGCODE_NOT_SPECIFIED at the end. Interface
         // translation fallback should only be based on explicit configuration
-        // gathered via the alter hooks below.
-        $candidates = array_keys($this->getLanguages());
+        // gathered via the alter hooks below. Path aliases fall back to
+        // LanguageInterface::LANGCODE_NOT_SPECIFIED but not other languages,
+        // unless the candidates are altered.
+        if (empty($context['operation']) || $context['operation'] != 'path_alias') {
+          $candidates = array_keys($this->getLanguages());
+        }
         $candidates[] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
         $candidates = array_combine($candidates, $candidates);
 
diff --git a/core/modules/path_alias/path_alias.services.yml b/core/modules/path_alias/path_alias.services.yml
index c36eee191e..64a2d85c52 100644
--- a/core/modules/path_alias/path_alias.services.yml
+++ b/core/modules/path_alias/path_alias.services.yml
@@ -15,7 +15,7 @@ services:
     arguments: ['@path_alias.repository', '@path_alias.whitelist', '@language_manager', '@cache.data']
   path_alias.repository:
     class: Drupal\path_alias\AliasRepository
-    arguments: ['@database']
+    arguments: ['@database', '@language_manager']
     tags:
       - { name: backend_overridable }
   path_alias.whitelist:
diff --git a/core/modules/path_alias/tests/modules/path_alias_language_fallback_test/path_alias_language_fallback_test.info.yml b/core/modules/path_alias/tests/modules/path_alias_language_fallback_test/path_alias_language_fallback_test.info.yml
new file mode 100644
index 0000000000..8d9bc39846
--- /dev/null
+++ b/core/modules/path_alias/tests/modules/path_alias_language_fallback_test/path_alias_language_fallback_test.info.yml
@@ -0,0 +1,5 @@
+name: 'Path Alias language fallback test'
+type: module
+description: 'Support module for testing language fallback for path aliases.'
+package: Testing
+version: VERSION
diff --git a/core/modules/path_alias/tests/modules/path_alias_language_fallback_test/path_alias_language_fallback_test.module b/core/modules/path_alias/tests/modules/path_alias_language_fallback_test/path_alias_language_fallback_test.module
new file mode 100644
index 0000000000..f45b4bd68f
--- /dev/null
+++ b/core/modules/path_alias/tests/modules/path_alias_language_fallback_test/path_alias_language_fallback_test.module
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Mock module for path alias language fallback tests.
+ */
+
+use Drupal\Core\Database\Query\AlterableInterface;
+
+/**
+ * Implements hook_language_fallback_candidates_OPERATION_alter().
+ */
+function path_alias_language_fallback_test_language_fallback_candidates_path_alias_alter(array &$candidates, array $context) {
+  if (\Drupal::state()->get('path_alias_language_fallback_test.fallback_path_alias_alter.candidates')) {
+    array_unshift($candidates, 'af');
+  }
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ */
+function path_alias_language_fallback_test_query_path_alias_language_fallback_alter(AlterableInterface $query) {
+  /** @var \Drupal\Core\Database\Query\SelectInterface $query */
+  // Replace the existing language code ordering.
+  $fields = &$query->getOrderBy();
+  unset($fields['base_table.langcode']);
+
+  // Sort results according to the order of the language fallback chain, which
+  // can be found in the values of the langcode condition.
+  $conditions = &$query->conditions();
+  foreach ($conditions as $condition) {
+    if (is_array($condition) && isset($condition['field']) && $condition['field'] === 'base_table.langcode') {
+      $order = '(CASE ';
+      foreach (array_values($condition['value']) as $index => $lang) {
+        $order .= "WHEN base_table.langcode = '$lang' THEN $index ";
+      }
+      $order .= 'END)';
+      $order_alias = $query->addExpression($order, 'order');
+      $fields = [$order_alias => 'ASC'] + $fields;
+      break;
+    }
+  }
+}
diff --git a/core/modules/path_alias/tests/src/Kernel/AliasLanguageFallbackTest.php b/core/modules/path_alias/tests/src/Kernel/AliasLanguageFallbackTest.php
new file mode 100644
index 0000000000..2aa16d5d89
--- /dev/null
+++ b/core/modules/path_alias/tests/src/Kernel/AliasLanguageFallbackTest.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Drupal\Tests\path_alias\Kernel;
+
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\Tests\language\Kernel\LanguageTestBase;
+use Drupal\Tests\Traits\Core\PathAliasTestTrait;
+
+/**
+ * Tests path alias language fallback functionality.
+ *
+ * @coversDefaultClass \Drupal\path_alias\AliasRepository
+ *
+ * @group path_alias
+ */
+class AliasLanguageFallbackTest extends LanguageTestBase {
+
+  use PathAliasTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['system', 'language', 'path_alias', 'path_alias_language_fallback_test'];
+
+  /**
+   * The alias repository.
+   *
+   * @var \Drupal\path_alias\AliasRepositoryInterface
+   */
+  protected $aliasRepository;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('path_alias');
+
+    $language = ConfigurableLanguage::createFromLangcode('af');
+    $language->save();
+
+    $this->aliasRepository = $this->container->get('path_alias.repository');
+  }
+
+  /**
+   * Ensure aliases can be looked up with extended language fallbacks.
+   *
+   * @covers ::lookupByAlias
+   * @see path_alias_language_fallback_test_language_fallback_candidates_path_alias_alter()
+   * @see path_alias_language_fallback_test_query_path_alias_language_fallback_alter()
+   */
+  public function testLookupByAlias() {
+    // Create an alias for a path in Afrikaans.
+    $test_source = '/user/1';
+    $test_alias = '/users/my-test-path';
+    $this->createPathAlias($test_source, $test_alias, 'af');
+    $this->assertNull($this->aliasRepository->lookupByAlias($test_alias, 'en'), 'No path alias is found if we specify English to the alias repository and Afrikaans is not a valid fallback candidate for English.');
+
+    // Enable configured fallback from English to Afrikaans.
+    $this->state->set('path_alias_language_fallback_test.fallback_path_alias_alter.candidates', TRUE);
+    $this->assertEquals($test_source, $this->aliasRepository->lookupByAlias($test_alias, 'en')['path'], 'The Afrikaans path alias is returned if we specify English to the alias repository and Afrikaans is a valid fallback candidate for English.');
+    // Test that standard path lookup still works.
+    $this->assertEquals($test_source, $this->aliasRepository->lookupByAlias($test_alias, 'af')['path'], 'Directly looking up the path alias in Afrikaans still works when Afrikaans is a fallback language.');
+
+    // Create an identical alias in English, for a different source path.
+    $en_source_path = '/user/2';
+    $this->createPathAlias($en_source_path, $test_alias, 'en');
+    $this->assertEquals($en_source_path, $this->aliasRepository->lookupByAlias($test_alias, 'en')['path'], 'The more specific English path alias is returned if we specify English to the alias repository.');
+
+    // Check that no alias is found when none exists.
+    $this->assertNull($this->aliasRepository->lookupByAlias('/this-alias-does-not-exist', 'en'), 'No alias is found when none exists.');
+  }
+
+  /**
+   * Ensure looking up aliases for paths work with extended language fallbacks.
+   *
+   * @covers ::lookupBySystemPath
+   * @see path_alias_language_fallback_test_language_fallback_candidates_path_alias_alter()
+   * @see path_alias_language_fallback_test_query_path_alias_language_fallback_alter()
+   */
+  public function testlookupBySystemPath() {
+    // Create an alias for a path in Afrikaans.
+    $test_source = '/user/login';
+    $test_alias = '/test-login-alias';
+    $this->createPathAlias($test_source, $test_alias, 'af');
+    $this->assertNull($this->aliasRepository->lookupBySystemPath($test_source, 'en'), 'No path alias is found if we specify English to the alias repository and Afrikaans is not a valid fallback candidate for English.');
+
+    // Enable configured fallback from English to Afrikaans.
+    $this->state->set('path_alias_language_fallback_test.fallback_path_alias_alter.candidates', TRUE);
+    $this->assertEquals($test_alias, $this->aliasRepository->lookupBySystemPath($test_source, 'en')['alias'], 'The Afrikaans path alias is returned if we specify English to the alias repository and Afrikaans is a valid fallback candidate for English.');
+    // Test that standard path lookup still works.
+    $this->assertEquals($test_alias, $this->aliasRepository->lookupBySystemPath($test_source, 'af')['alias'], 'Directly looking up the system path for an alias in Afrikaans still works when Afrikaans is a fallback language.');
+
+    // Create an identical alias in English, for a different source path.
+    $en_test_alias = '/test-english-login-alias';
+    $this->createPathAlias($test_source, $en_test_alias, 'en');
+    $this->assertEquals($en_test_alias, $this->aliasRepository->lookupBySystemPath($test_source, 'en')['alias'], 'The more specific English path alias is returned if we specify English to the alias repository.');
+
+    // Check that no alias is found when none exists.
+    $this->assertNull($this->aliasRepository->lookupBySystemPath('/this-alias-does-not-exist', 'en'), 'No alias is found when none exists.');
+  }
+
+}
