diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index e29c5ad086..3db59db26c 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -6484,7 +6484,8 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
 			 * aborted or crashed then it's okay to ignore it, otherwise not.
 			 * Note that an updater older than cutoff_xid cannot possibly be
 			 * committed, because HeapTupleSatisfiesVacuum would have returned
-			 * HEAPTUPLE_DEAD and we would not be trying to freeze the tuple.
+			 * HEAPTUPLE_DEAD or HEAPTUPLE_RECENTLY_DEAD and we would not be
+			 * trying to freeze the tuple.
 			 *
 			 * As with all tuple visibility routines, it's critical to test
 			 * TransactionIdIsInProgress before TransactionIdDidCommit,
@@ -6514,8 +6515,9 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
 			 */
 
 			/*
-			 * Since the tuple wasn't marked HEAPTUPLE_DEAD by vacuum, the
-			 * update Xid cannot possibly be older than the xid cutoff.
+			 * Since the tuple wasn't marked HEAPTUPLE_DEAD or
+			 * HEAPTUPLE_RECENTLY_DEAD by vacuum, the update Xid cannot
+			 * possibly be older than the xid cutoff.
 			 */
 			Assert(!TransactionIdIsValid(update_xid) ||
 				   !TransactionIdPrecedes(update_xid, cutoff_xid));
@@ -6597,8 +6599,9 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
  * Caller is responsible for setting the offset field, if appropriate.
  *
  * It is assumed that the caller has checked the tuple with
- * HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD
- * (else we should be removing the tuple, not freezing it).
+ * HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD,
+ * in which case we should be removing the tuple, not freezing it, or
+ * HEAPTUPLE_RECENTLY_DEAD, which would be removed by the next pruning.
  *
  * NB: cutoff_xid *must* be <= the current global xmin, to ensure that any
  * XID older than it could neither be running nor seen as running by any
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 45b1859475..da1d2bd557 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -593,6 +593,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 		OffsetNumber offnum,
 					maxoff;
 		bool		tupgone,
+					tupkeep,
 					hastup;
 		int			prev_dead_count;
 		int			nfrozen;
@@ -974,6 +975,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 			tuple.t_tableOid = RelationGetRelid(onerel);
 
 			tupgone = false;
+			tupkeep = false;
 
 			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
 			{
@@ -992,11 +994,16 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 					 * it were RECENTLY_DEAD.  Also, if it's a heap-only
 					 * tuple, we choose to keep it, because it'll be a lot
 					 * cheaper to get rid of it in the next pruning pass than
-					 * to treat it like an indexed tuple.
+					 * to treat it like an indexed tuple. Note that the
+					 * tuple cannot be frozen as well as it cannot become
+					 * visible.
 					 */
 					if (HeapTupleIsHotUpdated(&tuple) ||
 						HeapTupleIsHeapOnly(&tuple))
+					{
 						nkeep += 1;
+						tupkeep = true;
+					}
 					else
 						tupgone = true; /* we can delete the tuple */
 					all_visible = false;
@@ -1047,9 +1054,11 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 
 					/*
 					 * If tuple is recently deleted then we must not remove it
-					 * from relation.
+					 * from relation. It cannot be frozen as well as it should
+					 * not become visible.
 					 */
 					nkeep += 1;
+					tupkeep = true;
 					all_visible = false;
 					break;
 				case HEAPTUPLE_INSERT_IN_PROGRESS:
@@ -1081,16 +1090,21 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 				hastup = true;
 
 				/*
-				 * Each non-removable tuple must be checked to see if it needs
-				 * freezing.  Note we already have exclusive buffer lock.
+				 * Each non-removable tuple that we do not keep must be checked
+				 * to see if it needs freezing.  Note we already have exclusive
+				 * buffer lock.
 				 */
-				if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit,
-											  MultiXactCutoff, &frozen[nfrozen],
-											  &tuple_totally_frozen))
-					frozen[nfrozen++].offset = offnum;
-
-				if (!tuple_totally_frozen)
-					all_frozen = false;
+				if (!tupkeep)
+				{
+					if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit,
+												  MultiXactCutoff,
+												  &frozen[nfrozen],
+												  &tuple_totally_frozen))
+						frozen[nfrozen++].offset = offnum;
+
+					if (!tuple_totally_frozen)
+						all_frozen = false;
+				}
 			}
 		}						/* scan along page */
 
diff --git a/src/test/isolation/expected/multixact-freeze.out b/src/test/isolation/expected/multixact-freeze.out
new file mode 100644
index 0000000000..dd045613f9
--- /dev/null
+++ b/src/test/isolation/expected/multixact-freeze.out
@@ -0,0 +1,101 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_update s1_commit s1_vacuum s2_key_share s2_commit
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s1_commit: COMMIT;
+step s1_vacuum: VACUUM FREEZE tab_freeze;
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id             
+
+3              
+step s2_commit: COMMIT;
+
+starting permutation: s1_update s1_commit s2_key_share s1_vacuum s2_commit
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s1_commit: COMMIT;
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id             
+
+3              
+step s1_vacuum: VACUUM FREEZE tab_freeze;
+step s2_commit: COMMIT;
+
+starting permutation: s1_update s1_commit s2_key_share s2_commit s1_vacuum
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s1_commit: COMMIT;
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id             
+
+3              
+step s2_commit: COMMIT;
+step s1_vacuum: VACUUM FREEZE tab_freeze;
+
+starting permutation: s1_update s2_key_share s1_commit s1_vacuum s2_commit
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id             
+
+3              
+step s1_commit: COMMIT;
+step s1_vacuum: VACUUM FREEZE tab_freeze;
+step s2_commit: COMMIT;
+
+starting permutation: s1_update s2_key_share s1_commit s2_commit s1_vacuum
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id             
+
+3              
+step s1_commit: COMMIT;
+step s2_commit: COMMIT;
+step s1_vacuum: VACUUM FREEZE tab_freeze;
+
+starting permutation: s1_update s2_key_share s2_commit s1_commit s1_vacuum
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id             
+
+3              
+step s2_commit: COMMIT;
+step s1_commit: COMMIT;
+step s1_vacuum: VACUUM FREEZE tab_freeze;
+
+starting permutation: s2_key_share s1_update s1_commit s1_vacuum s2_commit
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id             
+
+3              
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s1_commit: COMMIT;
+step s1_vacuum: VACUUM FREEZE tab_freeze;
+step s2_commit: COMMIT;
+
+starting permutation: s2_key_share s1_update s1_commit s2_commit s1_vacuum
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id             
+
+3              
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s1_commit: COMMIT;
+step s2_commit: COMMIT;
+step s1_vacuum: VACUUM FREEZE tab_freeze;
+
+starting permutation: s2_key_share s1_update s2_commit s1_commit s1_vacuum
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id             
+
+3              
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s2_commit: COMMIT;
+step s1_commit: COMMIT;
+step s1_vacuum: VACUUM FREEZE tab_freeze;
+
+starting permutation: s2_key_share s2_commit s1_update s1_commit s1_vacuum
+step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE;
+id             
+
+3              
+step s2_commit: COMMIT;
+step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3;
+step s1_commit: COMMIT;
+step s1_vacuum: VACUUM FREEZE tab_freeze;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 32c965b2a0..98208d2fdc 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -36,6 +36,7 @@ test: insert-conflict-toast
 test: delete-abort-savept
 test: delete-abort-savept-2
 test: aborted-keyrevoke
+test: multixact-freeze
 test: multixact-no-deadlock
 test: multixact-no-forget
 test: lock-committed-update
diff --git a/src/test/isolation/specs/multixact-freeze.spec b/src/test/isolation/specs/multixact-freeze.spec
new file mode 100644
index 0000000000..3cd9965b2f
--- /dev/null
+++ b/src/test/isolation/specs/multixact-freeze.spec
@@ -0,0 +1,27 @@
+# Test for interactions of tuple freezing with dead, as well as recently-dead
+# tuples using multixacts via FOR KEY SHARE.
+setup
+{
+  CREATE TABLE tab_freeze (
+    id int PRIMARY KEY,
+    name char(3),
+    x int);
+  INSERT INTO tab_freeze VALUES (1, '111', 0);
+  INSERT INTO tab_freeze VALUES (3, '333', 0);
+}
+
+teardown
+{
+  DROP TABLE tab_freeze;
+}
+
+session "s1"
+setup				{ BEGIN; }
+step "s1_update"	{ UPDATE tab_freeze SET x = x + 1 WHERE id = 3; }
+step "s1_commit"	{ COMMIT; }
+step "s1_vacuum"	{ VACUUM FREEZE tab_freeze; }
+
+session "s2"
+setup				{ BEGIN; }
+step "s2_key_share"	{ SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; }
+step "s2_commit"	{ COMMIT; }
