Upgrade a PostgreSQL 15 Cluster to 16 with pg_upgrade
You will prepare a PostgreSQL 15 cluster, run the PostgreSQL 16.15 pg_upgrade checks, and then perform the major-version upgrade with copy mode. The examples use version-specific directories and placeholders for your actual cluster paths. Allow at least thirty minutes for preparation and checks, plus the time needed to copy your data.
The route
Jump straight to the step you need, or tick off Done means at the end.
This is a service-disrupting operation. Take and test a backup before starting, arrange a maintenance window, and make sure no clients can connect during the upgrade. A major-version upgrade is different from a minor update such as 16.14 to 16.15. pg_upgrade is for changing the major version without a normal dump and restore.
1. Confirm the installed tool
Run these read-only commands as the PostgreSQL installation user or another account that can inspect the binaries. The installed program on this machine is PostgreSQL 16.15 from Ubuntu's postgresql-16 package. Always use the new version's pg_upgrade binary, not the old one.
$ /usr/lib/postgresql/16/bin/pg_upgrade --version
pg_upgrade (PostgreSQL) 16.15 (Ubuntu 16.15-0ubuntu0.24.04.1)
$ dpkg-query -W -f='${Package} ${Version}\n' postgresql-16
postgresql-16 16.15-0ubuntu0.24.04.1
Checkpoint: identify four paths before continuing. OLD_DATA is the old cluster data directory, NEW_DATA is a new cluster already initialised with PostgreSQL 16, and the two bin directories contain the matching old and new executables.
$ OLD_DATA=/var/lib/postgresql/15/main
$ NEW_DATA=/var/lib/postgresql/16/main
$ OLD_BIN=/usr/lib/postgresql/15/bin
$ NEW_BIN=/usr/lib/postgresql/16/bin
$ test -x "$OLD_BIN/postgres" && test -x "$NEW_BIN/postgres" && echo 'binaries found'
binaries found
Replace those values with paths from your host. Do not assume that a directory exists merely because its version appears in a package name.
2. Prepare the new cluster and extensions
Install the PostgreSQL 16 binaries and initialise NEW_DATA with initdb, using settings compatible with the old cluster. The new server does not need to be started. If a package has already created the cluster, verify it rather than initialising over it.
Install the PostgreSQL 16 versions of every extension shared object used by the old cluster, including custom modules. Do not run CREATE EXTENSION again: the extension definitions are carried over, and pg_upgrade will report extension updates that need a later script. Copy custom full-text dictionaries, synonym files, thesauri and stop-word files to the new installation as well.
Save the new cluster's configuration, then arrange authentication that lets the cluster installation user connect to both clusters during the run. Peer authentication or a correctly protected ~/.pgpass file can help. Treat the password file as sensitive and do not put a password in a command line or article script.
3. Stop services and choose safe upgrade mode
Stop both clusters with your normal service manager, or use pg_ctl when you manage them directly. These commands require the PostgreSQL installation user, and may require elevated privileges if that user does not own the service.
$ pg_ctl -D "$OLD_DATA" stop
$ pg_ctl -D "$NEW_DATA" stop
Confirm that both servers really stopped before proceeding. Do not start either cluster between this checkpoint and the upgrade. Replication and log-shipping standbys must have received the final changes before shutdown.
Copy mode is the default and leaves the old data files separate. It uses more time and disk space, but gives the clearest rollback boundary. --link is faster and needs the data directories on the same file system, but starting the new cluster makes the old cluster unusable. --clone can provide similar speed while leaving the old cluster untouched, but is only available on supported file systems. Do not choose either shortcut merely to save time.
4. Run a no-change compatibility check
Create a writable, empty working directory for pg_upgrade. The program writes logs and generated SQL there. Run the check from that directory with the new binary. This command does not change cluster data, and it can check a running old server, although the old and new ports must then differ.
$ mkdir -p /var/tmp/pg-upgrade-16
$ cd /var/tmp/pg-upgrade-16
$ /usr/lib/postgresql/16/bin/pg_upgrade \
--check \
--old-datadir "$OLD_DATA" \
--new-datadir "$NEW_DATA" \
--old-bindir "$OLD_BIN" \
--new-bindir "$NEW_BIN" \
--jobs 4 \
--retain
Performing Consistency Checks
Clusters are compatible
The exact progress lines vary. The useful result is a successful exit status and a final compatibility message. If you intend to use --link or --clone, include that same mode in this check so its file-system requirements are tested.
Stop at the first failed check. Read the generated log and fix the reported issue in the old or new preparation, such as a missing extension library or incompatible build setting. Do not skip the check because the directories look correct.
5. Run the upgrade in copy mode
With both servers stopped and the check successful, run the same command without --check. The command below keeps the old and new ports explicit and retains SQL and log files for review. The default port is 50432, which helps avoid accidental client connections; use different ports if you are checking an old running server.
$ /usr/lib/postgresql/16/bin/pg_upgrade \
--old-datadir "$OLD_DATA" \
--new-datadir "$NEW_DATA" \
--old-bindir "$OLD_BIN" \
--new-bindir "$NEW_BIN" \
--old-port 50432 \
--new-port 50432 \
--jobs 4 \
--retain
Performing Upgrade
Success. Please read the documentation carefully before using the new cluster.
The real output contains progress and may name generated scripts. A non-zero exit status means the upgrade is not complete. If schema restoration fails, read the log, correct the old cluster's cause, and use the rollback rules below before trying again. Do not start the new cluster to see whether it works.
6. Apply configuration and verify the new cluster
Restore the intended pg_hba.conf rules and review postgresql.conf, included files and postgresql.auto.conf. A temporary permissive rule used for the upgrade should not become the production policy. Start PostgreSQL 16 through your normal service mechanism, then connect as the cluster owner and check the server version.
$ psql -X -d postgres -c 'SELECT version();'
version
------------------------------------------------------------------------------------------------------------------
PostgreSQL 16.15 ...
(1 row)
The rest of the verification is application-specific: check representative databases, extensions, jobs, authentication and replication. Run the generated post-upgrade scripts, such as the script that updates extensions, when the log tells you to. Run vacuumdb --all --analyze-in-stages if appropriate for your maintenance plan so the new planner has statistics.
7. Keep the rollback boundary visible
Before the new cluster has started, a failed copy-mode upgrade leaves the old cluster usable. Stop and inspect the logs rather than making ad hoc edits to either data directory. If you used --check only, the old cluster was unmodified. If you used --link, it is safe to reuse the old cluster only when the new cluster has not been started; the manual may require removing the .old suffix from global/pg_control. Follow the exact generated instructions and take a backup before editing that file.
Once the new cluster has started, it has written to shared files in cases covered by link mode, so the old cluster is no longer a safe rollback target. Restore the old cluster from a tested backup instead. Never delete the old data directory or run a generated deletion script until the new service, applications and backups have been checked.
Done means
- The installed new binary reports PostgreSQL 16.15 and the old and new paths were verified.
- A tested backup exists, both clusters were stopped, and clients were kept away during the change.
pg_upgrade --checkcompleted successfully with the intended copy, link or clone mode.- The real upgrade completed and its retained logs and generated scripts were reviewed.
- PostgreSQL 16 starts with the intended authentication and configuration, and representative queries and extensions work.
- The old cluster is retained until the rollback window and backup verification are complete.