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

  1. Read the release notes for every major version you skip — look for incompatibilities that affect your queries or extensions.
  2. Check extensions (PostGIS, TimescaleDB, pgvector): they must be available for the new version.
  3. Back up: a fresh pg_dumpall (or a verified physical backup). See pg_dump and pg_restore.
  4. Check disk space: copy-mode upgrades need roughly double the data size.
  5. Test the upgrade on a copy (a staging server or a restored backup) and run your application’s tests against the new version.
  6. 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 upgrade uses pg_upgrade under the hood — fast, converts the catalog and copies (or links) data files.
  • Add --link to hard-link files instead of copying: very fast and no extra disk, but the old cluster can’t be started afterwards.
  • Default -m dump uses 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

  1. Update planner statistics — pg_upgrade doesn’t carry them over:
sudo -u postgres vacuumdb --all --analyze-in-stages
  1. Update extensions: ALTER EXTENSION name UPDATE; in each database.
  2. Re-apply any configuration changes you’d made manually that weren’t copied.
  3. Check application logs and slow queries.
  4. Update monitoring, backup scripts and client tools to the new version (pg_dump must be at least as new as the server).
  5. When satisfied, remove the old cluster: sudo pg_dropcluster 16 main and sudo 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.

  • PostgreSQL
  • Upgrades
  • Databases