diff --git src/backend/postmaster/walsummarizer.c src/backend/postmaster/walsummarizer.c
index 4f12eaf2c85..8365fe55788 100644
--- src/backend/postmaster/walsummarizer.c
+++ src/backend/postmaster/walsummarizer.c
@@ -921,7 +921,20 @@ SummarizeWAL(TimeLineID tli, XLogRecPtr start_lsn, bool exact,
 	private_data = palloc0_object(SummarizerReadLocalXLogPrivate);
 	private_data->tli = tli;
 	private_data->historic = XLogRecPtrIsValid(switch_lsn);
-	private_data->read_upto = maximum_lsn;
+
+	/*
+	 * When reading a historic timeline, we must not read past the point where
+	 * it was replaced by a newer timeline, even if the flush/replay position
+	 * on the current timeline (maximum_lsn) is much further ahead. Clamp
+	 * read_upto to the switch point in that case; this matches what the page
+	 * read callback does when it discovers mid-read that the timeline has
+	 * become historic, and keeps us from reading into the zeroed tail of a
+	 * timeline that ends at a switch point.
+	 */
+	if (private_data->historic)
+		private_data->read_upto = switch_lsn;
+	else
+		private_data->read_upto = maximum_lsn;
 
 	/* Create xlogreader. */
 	xlogreader = XLogReaderAllocate(wal_segment_size, NULL,
