PostgreSQL minor releases (17.1 → 17.2) are drop-in updates — install the package and restart. Major versions (16 → 17) change the on-disk format, so the data directory must be converted or reloaded. Each major version is supported for five years; plan upgrades before your version reaches end of life.
Before you start
- Read the release notes for every major version you skip — look for incompatibilities that affect your queries or extensions.
- Check extensions (PostGIS, TimescaleDB, pgvector): they must be available for the new version.
- Back up: a fresh
pg_dumpall(or a verified physical backup). See pg_dump and pg_restore. - Check disk space: copy-mode upgrades need roughly double the data size.
- Test the upgrade on a copy (a staging server or a restored backup) and run your application’s tests against the new version.
- Plan a maintenance window.
Debian/Ubuntu: the clusters approach
Debian’s postgresql-common lets several versions and clusters coexist. Install the new version from your distribution or the PGDG repository:
sudo apt install -y postgresql-17
pg_lsclusters
Ver Cluster Port Status Owner Data directory
16 main 5432 online postgres /var/lib/postgresql/16/main
17 main 5433 online postgres /var/lib/postgresql/17/main
The package creates an empty 17/main cluster. Remove it so the upgrade can create it from your data:
sudo pg_dropcluster 17 main --stop
Option A: pg_upgradecluster (simplest)
sudo pg_upgradecluster -m upgrade 16 main
-m upgradeuses pg_upgrade under the hood — fast, converts the catalog and copies (or links) data files.- Add
--linkto hard-link files instead of copying: very fast and no extra disk, but the old cluster can’t be started afterwards. - Default
-m dumpuses dump and restore (slower, more forgiving).
The tool stops the old cluster, creates 17/main with the same configuration and port, upgrades, and starts it. Check:
pg_lsclusters
sudo -u postgres psql -c "SELECT version();"
The old cluster remains (stopped, on another port) until you remove it.
Option B: dump and restore
Works across any versions and also defragments everything:
sudo -u postgres pg_dumpall -p 5432 > /var/backups/all.sql
sudo -u postgres psql -p 5433 -f /var/backups/all.sql
Then swap ports in /etc/postgresql/<ver>/main/postgresql.conf so the new cluster listens on 5432, and restart both. Slower for large databases, and requires downtime for the duration of the dump and restore.
Other distributions: pg_upgrade directly
sudo systemctl stop postgresql-16
sudo -u postgres /usr/pgsql-17/bin/pg_upgrade \
-b /usr/pgsql-16/bin -B /usr/pgsql-17/bin \
-d /var/lib/pgsql/16/data -D /var/lib/pgsql/17/data --check
Run with --check first (no changes), then without it. Paths shown are RHEL-family PGDG defaults.
Docker
The official image won’t start on a data directory from an older major version. Either dump and restore into a new container/volume, or use a helper image that bundles both versions to run pg_upgrade. See PostgreSQL in Docker Compose.
After the upgrade
- Update planner statistics — pg_upgrade doesn’t carry them over:
sudo -u postgres vacuumdb --all --analyze-in-stages
- Update extensions:
ALTER EXTENSION name UPDATE;in each database. - Re-apply any configuration changes you’d made manually that weren’t copied.
- Check application logs and slow queries.
- Update monitoring, backup scripts and client tools to the new version (
pg_dumpmust be at least as new as the server). - When satisfied, remove the old cluster:
sudo pg_dropcluster 16 mainandsudo apt remove postgresql-16.
Replicas
With streaming replicas, upgrade the primary and then rebuild the standbys from it (or use rsync-based procedures from the pg_upgrade docs). Logical replication can enable near-zero-downtime upgrades by replicating to a new-version server and switching over.
Related: PostgreSQL replication and installing PostgreSQL.