diff --git a/vendor/magento/module-cron/Observer/ProcessCronQueueObserver.php b/vendor/magento/module-cron/Observer/ProcessCronQueueObserver.php
index a4a11156956d..d58e60ba2cab 100644
--- a/vendor/magento/module-cron/Observer/ProcessCronQueueObserver.php
+++ b/vendor/magento/module-cron/Observer/ProcessCronQueueObserver.php
@@ -320,17 +320,11 @@ private function lockGroup(string $groupId, callable $callback): void
      *
      * @return void
      * @throws Exception|Throwable
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     protected function _runJob($scheduledTime, $currentTime, $jobConfig, $schedule, $groupId)
     {
         $jobCode = $schedule->getJobCode();
-        $scheduleLifetime = $this->getCronGroupConfigurationValue($groupId, self::XML_PATH_SCHEDULE_LIFETIME);
-        $scheduleLifetime = $scheduleLifetime * self::SECONDS_IN_MINUTE;
-        if ($scheduledTime < $currentTime - $scheduleLifetime) {
-            $schedule->setStatus(Schedule::STATUS_MISSED);
-            // phpcs:ignore Magento2.Exceptions.DirectThrow
-            throw new Exception(sprintf('Cron Job %s is missed at %s', $jobCode, $schedule->getScheduledAt()));
-        }

         if (!isset($jobConfig['instance'], $jobConfig['method'])) {
             $schedule->setStatus(Schedule::STATUS_ERROR);
@@ -832,7 +826,7 @@ private function processPendingJobs(string $groupId, array $jobsRoot, int $curre
             }

             $scheduledTime = strtotime($schedule->getScheduledAt());
-            if ($scheduledTime > $currentTime) {
+            if (!$this->shouldRunJob($schedule, $groupId, $currentTime, (int) $scheduledTime)) {
                 continue;
             }

@@ -929,4 +923,62 @@ function () use ($scheduleResource, $where) {
             $scheduleResource->getConnection()
         );
     }
+
+    /**
+     * Mark job as missed
+     *
+     * @param Schedule $schedule
+     * @return void
+     */
+    private function markJobAsMissed(Schedule $schedule): void
+    {
+        $jobCode = $schedule->getJobCode();
+        $scheduleId = $schedule->getId();
+        $resource = $schedule->getResource();
+        $connection = $resource->getConnection();
+        $message = sprintf('Cron Job %s is missed at %s', $jobCode, $schedule->getScheduledAt());
+        $result = $this->retrier->execute(
+            function () use ($resource, $connection, $scheduleId, $message) {
+                return $connection->update(
+                    $resource->getTable('cron_schedule'),
+                    ['status' => Schedule::STATUS_MISSED, 'messages' => $message],
+                    ['schedule_id = ?' => $scheduleId, 'status = ?' => Schedule::STATUS_PENDING]
+                );
+            },
+            $connection
+        );
+        if ($result == 1) {
+            $schedule->setStatus(Schedule::STATUS_MISSED);
+            $schedule->setMessages($message);
+            if ($this->state->getMode() === State::MODE_DEVELOPER) {
+                $this->logger->info($message);
+            }
+        }
+    }
+
+    /**
+     * Check if job should be run
+     *
+     * @param Schedule $schedule
+     * @param string $groupId
+     * @param int $currentTime
+     * @param int $scheduledTime
+     * @return bool
+     */
+    private function shouldRunJob(Schedule $schedule, string $groupId, int $currentTime, int $scheduledTime): bool
+    {
+        if ($scheduledTime > $currentTime) {
+            return false;
+        }
+
+        $scheduleLifetime = $this->getCronGroupConfigurationValue($groupId, self::XML_PATH_SCHEDULE_LIFETIME);
+        $scheduleLifetime = $scheduleLifetime * self::SECONDS_IN_MINUTE;
+
+        if ($scheduledTime < $currentTime - $scheduleLifetime) {
+            $this->markJobAsMissed($schedule);
+            return false;
+        }
+
+        return true;
+    }
 }
