diff --git a/src/Validator/UpdateVersionValidator.php b/src/Validator/UpdateVersionValidator.php
index 19c01ba13a20eecd9daba34c83dc9a889496f129..d1eb8257152036326724ecebdee51ba2d5caeb3d 100644
--- a/src/Validator/UpdateVersionValidator.php
+++ b/src/Validator/UpdateVersionValidator.php
@@ -95,12 +95,14 @@ class UpdateVersionValidator implements EventSubscriberInterface {
       '@to_version' => $to_version_string,
       '@from_version' => $from_version_string,
     ];
+    $from_version_extra = $from_version->getVersionExtra();
+    $to_version_extra = $to_version->getVersionExtra();
     if (Semver::satisfies($to_version_string, "< $from_version_string")) {
       $event->addError([
         $this->t('Update version @to_version is lower than @from_version, downgrading is not supported.', $variables),
       ]);
     }
-    elseif ($from_version->getVersionExtra() === 'dev') {
+    elseif ($from_version_extra === 'dev') {
       $event->addError([
         $this->t('Drupal cannot be automatically updated from its current version, @from_version, to the recommended version, @to_version, because automatic updates from a dev version to any other version are not supported.', $variables),
       ]);
@@ -122,6 +124,30 @@ class UpdateVersionValidator implements EventSubscriberInterface {
         ]);
       }
     }
+    elseif ($stage instanceof CronUpdater) {
+      if ($from_version_extra || $to_version_extra) {
+        if ($from_version_extra) {
+          $messages[] = $this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, because Automatic Updates only supports updating from stable versions during cron.', $variables);
+          $event->addError($messages);
+        }
+        if ($to_version_extra) {
+          // Because we do not support updating to a new minor version during
+          // cron it is probably impossible to update from a stable version to
+          // a unstable/pre-release version, but we should check this condition
+          // just in case.
+          $messages[] = $this->t('Drupal cannot be automatically updated during cron to the recommended version, @to_version, because Automatic Updates only supports updating to stable versions during cron.', $variables);
+          $event->addError($messages);
+        }
+      }
+      else {
+        $to_patch_version = (int) $this->getPatchVersion($to_version_string);
+        $from_patch_version = (int) $this->getPatchVersion($from_version_string);
+        if ($from_patch_version + 1 !== $to_patch_version) {
+          $messages[] = $this->t('Drupal cannot be automatically updated during cron from its current version, @from_version, to the recommended version, @to_version, because Automatic Updates only supports 1 patch version update during cron.', $variables);
+          $event->addError($messages);
+        }
+      }
+    }
   }
 
   /**
@@ -134,4 +160,26 @@ class UpdateVersionValidator implements EventSubscriberInterface {
     ];
   }
 
+  /**
+   * Gets the patch number for a version string.
+   *
+   * @todo Move this method to \Drupal\Core\Extension\ExtensionVersion in
+   *   https://www.drupal.org/i/3261744.
+   *
+   * @param string $version_string
+   *   The version string.
+   *
+   * @return string
+   *   The patch number.
+   */
+  private function getPatchVersion(string $version_string): string {
+    $version_extra = ExtensionVersion::createFromVersionString($version_string)
+      ->getVersionExtra();
+    if ($version_extra) {
+      $version_string = str_replace("-$version_extra", '', $version_string);
+    }
+    $version_parts = explode('.', $version_string);
+    return $version_parts[2];
+  }
+
 }
diff --git a/tests/fixtures/release-history/drupal.9.8.1.xml b/tests/fixtures/release-history/drupal.9.8.2.xml
similarity index 58%
rename from tests/fixtures/release-history/drupal.9.8.1.xml
rename to tests/fixtures/release-history/drupal.9.8.2.xml
index 4d5268378ef31b4e9c59766f2572dd76c30c730e..7bb2c1a1128adbfd462b41218aa45f6b5916dafd 100644
--- a/tests/fixtures/release-history/drupal.9.8.1.xml
+++ b/tests/fixtures/release-history/drupal.9.8.2.xml
@@ -10,6 +10,18 @@
    <term><name>Projects</name><value>Drupal project</value></term>
   </terms>
 <releases>
+  <release>
+    <name>Drupal 9.8.2</name>
+    <version>9.8.2</version>
+    <status>published</status>
+    <release_link>http://example.com/drupal-9-8-2-release</release_link>
+    <download_link>http://example.com/drupal-9-8-2.tar.gz</download_link>
+    <date>1250425521</date>
+    <terms>
+      <term><name>Release type</name><value>New features</value></term>
+      <term><name>Release type</name><value>Bug fixes</value></term>
+    </terms>
+  </release>
  <release>
   <name>Drupal 9.8.1</name>
   <version>9.8.1</version>
@@ -34,5 +46,17 @@
      <term><name>Release type</name><value>Bug fixes</value></term>
    </terms>
  </release>
+  <release>
+    <name>Drupal 9.8.0-alpha1</name>
+    <version>9.8.0-alpha1</version>
+    <status>published</status>
+    <release_link>http://example.com/drupal-9-8-0-alpha1-release</release_link>
+    <download_link>http://example.com/drupal-9-8-0-alpha1.tar.gz</download_link>
+    <date>1250424521</date>
+    <terms>
+      <term><name>Release type</name><value>New features</value></term>
+      <term><name>Release type</name><value>Bug fixes</value></term>
+    </terms>
+  </release>
 </releases>
 </project>
diff --git a/tests/src/Functional/ReadinessValidationTest.php b/tests/src/Functional/ReadinessValidationTest.php
index 93334493b4d59b367e87855933b60a08384cd9d9..186da7deef788bf609259b8992246eba5a71ed12 100644
--- a/tests/src/Functional/ReadinessValidationTest.php
+++ b/tests/src/Functional/ReadinessValidationTest.php
@@ -54,7 +54,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase {
    */
   protected function setUp(): void {
     parent::setUp();
-    $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1.xml');
+    $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.2.xml');
     $this->setCoreVersion('9.8.1');
 
     $this->reportViewerUser = $this->createUser([
@@ -386,9 +386,8 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase {
     // version.
     $this->setCoreVersion('9.8.0');
 
-    // Flag a validation warning, which will be displayed in the messages area,
-    // but not block or abort the update.
-    $results = $this->testResults['checker_1']['1 warning'];
+    // Flag a validation error, which will be displayed in the messages area.
+    $results = $this->testResults['checker_1']['1 error'];
     TestChecker1::setTestResult($results, ReadinessCheckEvent::class);
     $message = $results[0]->getMessages()[0];
 
@@ -398,7 +397,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase {
       'package_manager_bypass',
     ]);
 
-    // The warning should be persistently visible, even after the checker stops
+    // The error should be persistently visible, even after the checker stops
     // flagging it.
     $this->drupalGet('/admin/structure');
     $assert_session->pageTextContains($message);
@@ -407,7 +406,10 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase {
     $assert_session->pageTextContains($message);
 
     // Do the update; we don't expect any errors or special conditions to appear
-    // during it.
+    // during it. The Update button is displayed because the form does its own
+    // readiness check (without storing the results), and the checker is no
+    // longer raising an error.
+    // @todo Fine-tune this in https://www.drupal.org/node/3261758.
     $this->drupalGet('/admin/modules/automatic-update');
     $page->pressButton('Update');
     $this->checkForMetaRefresh();
diff --git a/tests/src/Functional/UpdateLockTest.php b/tests/src/Functional/UpdateLockTest.php
index 7bf9378797d2c701603d95da781767933b37f71c..73d87d98b32740fd65261911f828e017744bb003 100644
--- a/tests/src/Functional/UpdateLockTest.php
+++ b/tests/src/Functional/UpdateLockTest.php
@@ -29,7 +29,7 @@ class UpdateLockTest extends AutomaticUpdatesFunctionalTestBase {
   protected function setUp(): void {
     parent::setUp();
 
-    $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1.xml');
+    $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.2.xml');
     $this->drupalLogin($this->rootUser);
     $this->checkForUpdates();
   }
diff --git a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
index 1ba9df56bfc1796a9bbde9f2d49998d4e7266ca0..f5a54a676d8cdaa33ab8cae9fc1bfc1664dd35eb 100644
--- a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
+++ b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
@@ -75,8 +75,8 @@ abstract class AutomaticUpdatesKernelTestBase extends KernelTestBase {
 
     // By default, pretend we're running Drupal core 9.8.0 and a non-security
     // update to 9.8.1 is available.
-    $this->setCoreVersion('9.8.0');
-    $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1.xml');
+    $this->setCoreVersion('9.8.1');
+    $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.2.xml');
 
     // Set a last cron run time so that the cron frequency validator will run
     // from a sane state.
diff --git a/tests/src/Kernel/CronUpdaterTest.php b/tests/src/Kernel/CronUpdaterTest.php
index 4f293fa4e41288ab08e44f1efcbb39e9f73e73f3..4166e7ba5dba5b34679fb3f88eef4b74696f0400 100644
--- a/tests/src/Kernel/CronUpdaterTest.php
+++ b/tests/src/Kernel/CronUpdaterTest.php
@@ -45,7 +45,7 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
     return [
       'disabled, normal release' => [
         CronUpdater::DISABLED,
-        "$fixture_dir/drupal.9.8.1.xml",
+        "$fixture_dir/drupal.9.8.2.xml",
         FALSE,
       ],
       'disabled, security release' => [
@@ -60,12 +60,12 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
       ],
       'security only, normal release' => [
         CronUpdater::SECURITY,
-        "$fixture_dir/drupal.9.8.1.xml",
+        "$fixture_dir/drupal.9.8.2.xml",
         FALSE,
       ],
       'enabled, normal release' => [
         CronUpdater::ALL,
-        "$fixture_dir/drupal.9.8.1.xml",
+        "$fixture_dir/drupal.9.8.2.xml",
         TRUE,
       ],
       'enabled, security release' => [
@@ -94,6 +94,7 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
     // Our form alter does not refresh information on available updates, so
     // ensure that the appropriate update data is loaded beforehand.
     $this->setReleaseMetadata($release_data);
+    $this->setCoreVersion('9.8.0');
     update_get_available(TRUE);
 
     // Submit the configuration form programmatically, to prove our alterations
diff --git a/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php b/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php
index 77fd2d855684d2c7ab7a1868b8ff9134381fbe05..b161007b238b02b7bf6b4a8e3f74dc234a26e15f 100644
--- a/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php
+++ b/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php
@@ -242,7 +242,7 @@ class ReadinessValidationManagerTest extends AutomaticUpdatesKernelTestBase {
       ->install(['automatic_updates']);
 
     // Ensure there's a simulated core release to update to.
-    $this->setReleaseMetadata(__DIR__ . '/../../../fixtures/release-history/drupal.9.8.1.xml');
+    $this->setReleaseMetadata(__DIR__ . '/../../../fixtures/release-history/drupal.9.8.2.xml');
 
     // The readiness checker should raise a warning, so that the update is not
     // blocked or aborted.
diff --git a/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php b/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php
index 396c0b4c811938db3843d1e8da2a1958c8378a17..80ae5fb602f7c9d344109646ced24a30dc35f4e6 100644
--- a/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php
@@ -41,7 +41,7 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase {
 
     /** @var \Drupal\Tests\automatic_updates\Kernel\TestCronUpdater $updater */
     $updater = $this->container->get('automatic_updates.cron_updater');
-    $updater->begin(['drupal' => '9.8.1']);
+    $updater->begin(['drupal' => '9.8.2']);
     $updater->stage();
 
     $stage_dir = $updater->getStageDirectory();
diff --git a/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php b/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php
index d946bbe745b07b377868dfd57e7465717521101f..0d6b91f816ca5e5ea36133b449485f4aa711ac7a 100644
--- a/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php
@@ -33,6 +33,10 @@ class UpdateVersionValidatorTest extends AutomaticUpdatesKernelTestBase {
    * Tests an update version that is same major & minor version as the current.
    */
   public function testNoMajorOrMinorUpdates(): void {
+    $this->setCoreVersion('9.8.0');
+    $this->config('automatic_updates.settings')
+      ->set('cron', CronUpdater::DISABLED)
+      ->save();
     $this->assertCheckerResultsFromManager([], TRUE);
   }
 
@@ -42,7 +46,7 @@ class UpdateVersionValidatorTest extends AutomaticUpdatesKernelTestBase {
   public function testMajorUpdates(): void {
     $this->setCoreVersion('8.9.1');
     $result = ValidationResult::createError([
-      'Drupal cannot be automatically updated from its current version, 8.9.1, to the recommended version, 9.8.1, because automatic updates from one major version to another are not supported.',
+      'Drupal cannot be automatically updated from its current version, 8.9.1, to the recommended version, 9.8.2, because automatic updates from one major version to another are not supported.',
     ]);
     $this->assertCheckerResultsFromManager([$result], TRUE);
   }
@@ -162,8 +166,8 @@ class UpdateVersionValidatorTest extends AutomaticUpdatesKernelTestBase {
    * Tests an update version that is a lower version than the current.
    */
   public function testDowngrading(): void {
-    $this->setCoreVersion('9.8.2');
-    $result = ValidationResult::createError(['Update version 9.8.1 is lower than 9.8.2, downgrading is not supported.']);
+    $this->setCoreVersion('9.8.3');
+    $result = ValidationResult::createError(['Update version 9.8.2 is lower than 9.8.3, downgrading is not supported.']);
     $this->assertCheckerResultsFromManager([$result], TRUE);
   }
 
@@ -172,8 +176,79 @@ class UpdateVersionValidatorTest extends AutomaticUpdatesKernelTestBase {
    */
   public function testUpdatesFromDevVersion(): void {
     $this->setCoreVersion('9.8.0-dev');
-    $result = ValidationResult::createError(['Drupal cannot be automatically updated from its current version, 9.8.0-dev, to the recommended version, 9.8.1, because automatic updates from a dev version to any other version are not supported.']);
+    $result = ValidationResult::createError(['Drupal cannot be automatically updated from its current version, 9.8.0-dev, to the recommended version, 9.8.2, because automatic updates from a dev version to any other version are not supported.']);
+    $this->assertCheckerResultsFromManager([$result], TRUE);
+  }
+
+  /**
+   * Tests a cron update two patch releases ahead of the current version.
+   */
+  public function testCronUpdateTwoPatchReleasesAhead(): void {
+    $this->setCoreVersion('9.8.0');
+    $cron = $this->container->get('cron');
+    $config = $this->config('automatic_updates.settings');
+
+    $logger = new TestLogger();
+    $this->container->get('logger.factory')
+      ->get('automatic_updates')
+      ->addLogger($logger);
+
+    // The latest version is two patch releases ahead, so we won't update to it
+    // during cron, even if configuration allows it, and this should be flagged
+    // as an error during readiness checking. Trying to run the update anyway
+    // should raise an error.
+    $config->set('cron', CronUpdater::ALL)->save();
+    $result = ValidationResult::createError(['Drupal cannot be automatically updated during cron from its current version, 9.8.0, to the recommended version, 9.8.2, because Automatic Updates only supports 1 patch version update during cron.']);
+    $this->assertCheckerResultsFromManager([$result], TRUE);
+    $cron->run();
+    $this->assertUpdateStagedTimes(0);
+    $this->assertTrue($logger->hasRecord("<h2>Unable to complete the update because of errors.</h2>Drupal cannot be automatically updated during cron from its current version, 9.8.0, to the recommended version, 9.8.2, because Automatic Updates only supports 1 patch version update during cron.", RfcLogLevel::ERROR));
+
+    // If cron updates are totally disabled, there's no problem here and no
+    // errors should be raised.
+    $config->set('cron', CronUpdater::DISABLED)->save();
+    $this->assertCheckerResultsFromManager([], TRUE);
+
+    // Even if cron is configured to allow security updates only, the update
+    // will be blocked if it's more than one patch version ahead.
+    $config->set('cron', CronUpdater::SECURITY)->save();
+    $cron->run();
+    $this->assertUpdateStagedTimes(0);
+    $this->assertTrue($logger->hasRecord("<h2>Unable to complete the update because of errors.</h2>Drupal cannot be automatically updated during cron from its current version, 9.8.0, to the recommended version, 9.8.2, because Automatic Updates only supports 1 patch version update during cron.", RfcLogLevel::ERROR));
+  }
+
+  /**
+   * Tests a cron update one patch release ahead of the current version.
+   */
+  public function testCronUpdateOnePatchReleaseAhead(): void {
+    $cron = $this->container->get('cron');
+    $this->config('automatic_updates.settings')
+      ->set('cron', CronUpdater::ALL)
+      ->save();
+    $this->assertCheckerResultsFromManager([], TRUE);
+    $cron->run();
+    $this->assertUpdateStagedTimes(1);
+  }
+
+  /**
+   * Tests a cron update where the current version is not stable.
+   */
+  public function testCronUpdateFromUnstableVersion(): void {
+    $this->setCoreVersion('9.8.0-alpha1');
+    $this->config('automatic_updates.settings')
+      ->set('cron', CronUpdater::ALL)
+      ->save();
+    $logger = new TestLogger();
+    $this->container->get('logger.factory')
+      ->get('automatic_updates')
+      ->addLogger($logger);
+    $message = 'Drupal cannot be automatically updated during cron from its current version, 9.8.0-alpha1, because Automatic Updates only supports updating from stable versions during cron.';
+    $result = ValidationResult::createError([$message]);
     $this->assertCheckerResultsFromManager([$result], TRUE);
+
+    $this->container->get('cron')->run();
+    $this->assertUpdateStagedTimes(0);
+    $this->assertTrue($logger->hasRecord("<h2>Unable to complete the update because of errors.</h2>$message", RfcLogLevel::ERROR));
   }
 
 }
diff --git a/tests/src/Kernel/UpdateRecommenderTest.php b/tests/src/Kernel/UpdateRecommenderTest.php
index 33a82448d970a3f03431e0d7b8fa47ad6fcf5ea4..be699afe00693e2939cbee7d37d87cd97a950d6b 100644
--- a/tests/src/Kernel/UpdateRecommenderTest.php
+++ b/tests/src/Kernel/UpdateRecommenderTest.php
@@ -26,7 +26,7 @@ class UpdateRecommenderTest extends AutomaticUpdatesKernelTestBase {
     $recommender = new UpdateRecommender();
     $recommended_release = $recommender->getRecommendedRelease(TRUE);
     $this->assertNotEmpty($recommended_release);
-    $this->assertSame('9.8.1', $recommended_release->getVersion());
+    $this->assertSame('9.8.2', $recommended_release->getVersion());
     // Getting the recommended release again should not trigger another request.
     $this->assertNotEmpty($recommender->getRecommendedRelease());
   }
@@ -35,7 +35,7 @@ class UpdateRecommenderTest extends AutomaticUpdatesKernelTestBase {
    * Tests fetching the recommended release when there is no update available.
    */
   public function testNoUpdateAvailable(): void {
-    $this->setCoreVersion('9.8.1');
+    $this->setCoreVersion('9.8.2');
 
     $recommender = new UpdateRecommender();
     $recommended_release = $recommender->getRecommendedRelease(TRUE);