diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 520f77971b..d7e5c02f95 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -339,9 +339,11 @@ IsPinnedObject(Oid classId, Oid objectId)
 	 * robustness.
 	 */
 
-	/* template1 is not pinned */
+	/* template1, template0, postgres are not pinned */
 	if (classId == DatabaseRelationId &&
-		objectId == TemplateDbOid)
+		(objectId == TemplateDbOid ||
+		 objectId == Template0ObjectId ||
+		 objectId == PostgresObjectId))
 		return false;
 
 	/* the public namespace is not pinned */
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 1cb4a5b0d2..04454b3d7c 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -59,11 +59,11 @@
 #include "sys/mman.h"
 #endif
 
-#include "access/transam.h"
 #include "access/xlog_internal.h"
 #include "catalog/pg_authid_d.h"
 #include "catalog/pg_class_d.h" /* pgrminclude ignore */
 #include "catalog/pg_collation_d.h"
+#include "catalog/pg_database_d.h"	/* pgrminclude ignore */
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/logging.h"
@@ -1806,14 +1806,24 @@ make_template0(FILE *cmdfd)
 	 * objects in the old cluster, the problem scenario only exists if the OID
 	 * that is in use in the old cluster is also used in the new cluster - and
 	 * the new cluster should be the result of a fresh initdb.)
-	 *
-	 * We use "STRATEGY = file_copy" here because checkpoints during initdb
-	 * are cheap. "STRATEGY = wal_log" would generate more WAL, which would
-	 * be a little bit slower and make the new cluster a little bit bigger.
 	 */
 	static const char *const template0_setup[] = {
-		"CREATE DATABASE template0 IS_TEMPLATE = true ALLOW_CONNECTIONS = false OID = "
-		CppAsString2(Template0ObjectId)
+		/*
+		 * Since pg_database.dat includes a dummy row for template0, we have
+		 * to remove that before creating the database for real.
+		 */
+		"DELETE FROM pg_database WHERE datname = 'template0';\n\n",
+
+		/*
+		 * Clone template1 to make template0.
+		 *
+		 * We use "STRATEGY = file_copy" here because checkpoints during
+		 * initdb are cheap.  "STRATEGY = wal_log" would generate more WAL,
+		 * which would be a little bit slower and make the new cluster a
+		 * little bit bigger.
+		 */
+		"CREATE DATABASE template0 IS_TEMPLATE = true ALLOW_CONNECTIONS = false"
+		" OID = " CppAsString2(Template0ObjectId)
 		" STRATEGY = file_copy;\n\n",
 
 		/*
@@ -1836,12 +1846,11 @@ make_template0(FILE *cmdfd)
 		"REVOKE CREATE,TEMPORARY ON DATABASE template1 FROM public;\n\n",
 		"REVOKE CREATE,TEMPORARY ON DATABASE template0 FROM public;\n\n",
 
-		"COMMENT ON DATABASE template0 IS 'unmodifiable empty database';\n\n",
-
 		/*
-		 * Finally vacuum to clean up dead rows in pg_database
+		 * Note: postgres.bki filled in a comment for template0, so we need
+		 * not do that here.
 		 */
-		"VACUUM pg_database;\n\n",
+
 		NULL
 	};
 
@@ -1858,12 +1867,19 @@ make_postgres(FILE *cmdfd)
 	const char *const *line;
 
 	/*
-	 * Just as we did for template0, and for the same reasons, assign a fixed
-	 * OID to postgres and select the file_copy strategy.
+	 * Comments in make_template0() mostly apply here too.
 	 */
 	static const char *const postgres_setup[] = {
-		"CREATE DATABASE postgres OID = " CppAsString2(PostgresObjectId) " STRATEGY = file_copy;\n\n",
-		"COMMENT ON DATABASE postgres IS 'default administrative connection database';\n\n",
+		"DELETE FROM pg_database WHERE datname = 'postgres';\n\n",
+
+		"CREATE DATABASE postgres OID = " CppAsString2(PostgresObjectId)
+		" STRATEGY = file_copy;\n\n",
+
+		/*
+		 * Finally vacuum to clean up dead rows in pg_database
+		 */
+		"VACUUM pg_database;\n\n",
+
 		NULL
 	};
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 969e2a7a46..bcb81e02c4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2844,10 +2844,11 @@ dumpDatabase(Archive *fout)
 	qdatname = pg_strdup(fmtId(datname));
 
 	/*
-	 * Prepare the CREATE DATABASE command.  We must specify encoding, locale,
-	 * and tablespace since those can't be altered later.  Other DB properties
-	 * are left to the DATABASE PROPERTIES entry, so that they can be applied
-	 * after reconnecting to the target DB.
+	 * Prepare the CREATE DATABASE command.  We must specify OID (if we want
+	 * to preserve that), as well as the encoding, locale, and tablespace
+	 * since those can't be altered later.  Other DB properties are left to
+	 * the DATABASE PROPERTIES entry, so that they can be applied after
+	 * reconnecting to the target DB.
 	 */
 	if (dopt->binary_upgrade)
 	{
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 9a2816de51..338dfca5a0 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -196,10 +196,6 @@ FullTransactionIdAdvance(FullTransactionId *dest)
 #define FirstUnpinnedObjectId	12000
 #define FirstNormalObjectId		16384
 
-/* OIDs of Template0 and Postgres database are fixed */
-#define Template0ObjectId		4
-#define PostgresObjectId		5
-
 /*
  * VariableCache is a data structure in shared memory that is used to track
  * OID and XID assignment state.  For largely historical reasons, there is
diff --git a/src/include/catalog/pg_database.dat b/src/include/catalog/pg_database.dat
index 5feedff7bf..6c2221a4e9 100644
--- a/src/include/catalog/pg_database.dat
+++ b/src/include/catalog/pg_database.dat
@@ -19,4 +19,24 @@
   datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE',
   datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE', datacl => '_null_' },
 
+# The template0 and postgres entries are included here to reserve their
+# associated OIDs.  We show their correct properties for reference, but
+# note that these pg_database rows are removed and replaced by initdb.
+# Unlike the row for template1, their datfrozenxids will not be overwritten
+# during initdb's first VACUUM, so we have to provide normal-looking XIDs.
+
+{ oid => '4', oid_symbol => 'Template0ObjectId',
+  descr => 'unmodifiable empty database',
+  datname => 'template0', encoding => 'ENCODING', datlocprovider => 'LOCALE_PROVIDER', datistemplate => 't',
+  datallowconn => 'f', datconnlimit => '-1', datfrozenxid => '4',
+  datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE',
+  datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE', datacl => '_null_' },
+
+{ oid => '5', oid_symbol => 'PostgresObjectId',
+  descr => 'default administrative connection database',
+  datname => 'postgres', encoding => 'ENCODING', datlocprovider => 'LOCALE_PROVIDER', datistemplate => 'f',
+  datallowconn => 't', datconnlimit => '-1', datfrozenxid => '4',
+  datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE',
+  datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE', datacl => '_null_' },
+
 ]
diff --git a/src/include/catalog/unused_oids b/src/include/catalog/unused_oids
index 61d41e7561..e55bc6fa3c 100755
--- a/src/include/catalog/unused_oids
+++ b/src/include/catalog/unused_oids
@@ -32,15 +32,6 @@ my @input_files = glob("pg_*.h");
 
 my $oids = Catalog::FindAllOidsFromHeaders(@input_files);
 
-# Push the template0 and postgres database OIDs.
-my $Template0ObjectId =
-  Catalog::FindDefinedSymbol('access/transam.h', '..', 'Template0ObjectId');
-push @{$oids}, $Template0ObjectId;
-
-my $PostgresObjectId =
-  Catalog::FindDefinedSymbol('access/transam.h', '..', 'PostgresObjectId');
-push @{$oids}, $PostgresObjectId;
-
 # Also push FirstGenbkiObjectId to serve as a terminator for the last gap.
 my $FirstGenbkiObjectId =
   Catalog::FindDefinedSymbol('access/transam.h', '..', 'FirstGenbkiObjectId');
