diff --git a/src/init.cpp b/src/init.cpp
index a4ad2771cc7cf..4e61e030f78f2 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1770,7 +1770,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
 
     node.background_init_thread = std::thread(&util::TraceThread, "initload", [=, &chainman, &args, &node] {
         ScheduleBatchPriority();
-        // Import blocks
+        // Import blocks and ActivateBestChain()
         ImportBlocks(chainman, vImportFiles);
         if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
             LogPrintf("Stopping after block import\n");
@@ -1793,8 +1793,18 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
         }
     });
 
-    // Wait for genesis block to be processed
-    if (WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip() == nullptr)) {
+    /*
+     * Wait for genesis block to be processed. Typically kernel_notifications.m_tip_block
+     * has already been set by a call to LoadChainTip() in CompleteChainstateInitialization().
+     * But this is skipped if the chainstate doesn't exist yet or is being wiped:
+     *
+     * 1. first startup with an empty datadir
+     * 2. reindex
+     * 3. reindex-chainstate
+     *
+     * In these case it's connected by a call to ActivateBestChain() in the initload thread.
+     */
+    {
         WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock);
         kernel_notifications.m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
             return !kernel_notifications.m_tip_block.IsNull() || ShutdownRequested(node);
diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h
index c77f3c30a2638..6f23bf3bb55ea 100644
--- a/src/interfaces/mining.h
+++ b/src/interfaces/mining.h
@@ -75,8 +75,8 @@ class Mining
     virtual std::optional<BlockRef> getTip() = 0;
 
     /**
-     * Waits for the connected tip to change. If the tip was not connected on
-     * startup, this will wait.
+     * Waits for the connected tip to change. During node initialization, this will
+     * wait until the tip is connected.
      *
      * @param[in] current_tip block hash of the current chain tip. Function waits
      *                        for the chain tip to differ from this.
diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h
index 03bc5f4600720..ac6db0558df8f 100644
--- a/src/node/blockstorage.h
+++ b/src/node/blockstorage.h
@@ -430,6 +430,7 @@ class BlockManager
     void CleanupBlockRevFiles() const;
 };
 
+// Calls ActivateBestChain() even if no blocks are imported.
 void ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_paths);
 } // namespace node
 
diff --git a/src/node/kernel_notifications.h b/src/node/kernel_notifications.h
index 296b9c426dcef..35070b5285d5f 100644
--- a/src/node/kernel_notifications.h
+++ b/src/node/kernel_notifications.h
@@ -56,9 +56,9 @@ class KernelNotifications : public kernel::Notifications
 
     Mutex m_tip_block_mutex;
     std::condition_variable m_tip_block_cv GUARDED_BY(m_tip_block_mutex);
-    //! The block for which the last blockTip notification was received for.
-    //! The initial ZERO means that no block has been connected yet, which may
-    //! be true even long after startup, until shutdown.
+    //! The block for which the last blockTip notification was received.
+    //! It's first set when the tip is connected during node initialization.
+    //! Might be unset during an early shutdown.
     uint256 m_tip_block GUARDED_BY(m_tip_block_mutex){uint256::ZERO};
 
 private:
diff --git a/src/validation.cpp b/src/validation.cpp
index 0227ccec2fd41..aff8fe7024dfe 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -4721,6 +4721,13 @@ bool Chainstate::LoadChainTip()
               m_chain.Height(),
               FormatISO8601DateTime(tip->GetBlockTime()),
               GuessVerificationProgress(m_chainman.GetParams().TxData(), tip));
+
+    // Ensure KernelNotifications m_tip_block is set even if no new block arrives.
+    if (this->GetRole() != ChainstateRole::BACKGROUND) {
+        // Ignoring return value for now.
+        (void)m_chainman.GetNotifications().blockTip(GetSynchronizationState(/*init=*/true, m_chainman.m_blockman.m_blockfiles_indexed), *pindex);
+    }
+
     return true;
 }