diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 49996a4a35bb290e03c38dcf3d17b68cf5f1a7e5..e2b0fcccb2e136b18a8a9b11b7fcdf778200d52e 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -546,7 +546,15 @@ function node_access_rebuild($batch_mode = FALSE): void {
   $node_storage = \Drupal::entityTypeManager()->getStorage('node');
   /** @var \Drupal\node\NodeAccessControlHandlerInterface $access_control_handler */
   $access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node');
+
+  // If node_access_rebuild() fails to complete, and node_access_needs_rebuild
+  // is not set to TRUE, the node_access table is left in an incomplete state.
+  // Force node_access_needs_rebuild to TRUE once existing grants are deleted,
+  // to signal that the node access table still needs to be rebuilt if this
+  // function does not finish.
+  node_access_needs_rebuild(TRUE);
   $access_control_handler->deleteGrants();
+
   // Only recalculate if the site is using a node_access module.
   if (\Drupal::moduleHandler()->hasImplementations('node_grants')) {
     if ($batch_mode) {
diff --git a/core/modules/node/tests/src/Kernel/NodeAccessTest.php b/core/modules/node/tests/src/Kernel/NodeAccessTest.php
index 62c370c8173d4169b8c2d909313ea031a8fde89a..c2bb65f820334f79bb3ca53ea5cf2206fabd6d29 100644
--- a/core/modules/node/tests/src/Kernel/NodeAccessTest.php
+++ b/core/modules/node/tests/src/Kernel/NodeAccessTest.php
@@ -171,4 +171,15 @@ public function testDuplicateBatchRebuild(): void {
     $this->assertCount(1, $batch['sets']);
   }
 
+  /**
+   * Tests node_access_needs_rebuild is set when node_access_rebuild is called.
+   */
+  public function testNodeAccessRebuildNeedsRebuild(): void {
+    $this->assertFalse(node_access_needs_rebuild());
+    $this->enableModules(['node_access_test']);
+    // Call as batch so rebuild is not run immediately.
+    node_access_rebuild(TRUE);
+    $this->assertTrue(node_access_needs_rebuild());
+  }
+
 }