Upgrading PostgreSQL changes the database server that applications depend on, so the work needs a backup, a maintenance window, and proof that the upgraded cluster still serves the same data on the expected port. The highest-risk moment is the major-version cutover, when the old cluster stops, the new binaries take over, and rollback remains possible only while the old cluster is still kept intact.
Minor updates within the same PostgreSQL major version are package updates and restarts; they do not need a dump and restore. Major upgrades change the system catalogs and require an upgrade tool such as pg_upgrade. On Debian and Ubuntu package installations, postgresql-common wraps that tool with pg_upgradecluster so the old and new major versions can live side by side as named clusters.
A package-managed PostgreSQL 17 to PostgreSQL 18 upgrade on Ubuntu 26.04 with PGDG packages provides the example path. Use the source and target versions that match the server being upgraded, install matching target-version extension packages before the cutover, and avoid combining the database major upgrade with an operating-system or architecture migration unless that full path has already been rehearsed.
Related: How to create a PostgreSQL database backup \\
Related: How to restore a PostgreSQL database backup
Steps to upgrade PostgreSQL using pg_upgradecluster:
- Schedule a maintenance window for database downtime.
- List existing PostgreSQL clusters and ports.
$ sudo pg_lsclusters Ver Cluster Port Status Owner Data directory Log file 17 main 5432 online postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
- Note the source cluster Ver plus Cluster values for the upgrade (example: 17 / main).
- Check free disk space for the filesystem hosting /var/lib/postgresql.
$ df -h /var/lib/postgresql Filesystem Size Used Avail Use% Mounted on nvme0n1p2 480G 132G 326G 29% /
pg_upgradecluster –method=upgrade copies cluster files by default when it runs pg_upgrade. Plan free space close to the source cluster size unless a tested link or clone mode is intentionally used.
- Create a backup directory owned by postgres.
$ sudo install --directory --owner=postgres --group=postgres --mode=0700 /var/backups/postgresql
No output indicates the directory was created successfully.
- Create a full logical backup of all databases and global objects.
$ sudo -u postgres pg_dumpall --file=/var/backups/postgresql/pg_dumpall-2026-06-07.sql
pg_dumpall output contains database contents plus role definitions and can include password hashes, so the backup file must be protected like a private key.
- Restrict the backup file permissions.
$ sudo chmod 0600 /var/backups/postgresql/pg_dumpall-2026-06-07.sql
- Verify that the backup file exists and is readable only by its owner.
$ sudo ls -l /var/backups/postgresql/pg_dumpall-2026-06-07.sql -rw------- 1 postgres postgres 4266 Jun 7 04:45 /var/backups/postgresql/pg_dumpall-2026-06-07.sql
A file listing is not a restore test. Test the backup on a separate host or disposable cluster before using this workflow on production data. Related: How to restore a PostgreSQL database backup
- Update package indexes from the configured PostgreSQL package source.
$ sudo apt-get update Hit:1 http://archive.ubuntu.com/ubuntu resolute InRelease Hit:2 http://security.ubuntu.com/ubuntu resolute-security InRelease Get:3 https://apt.postgresql.org/pub/repos/apt resolute-pgdg InRelease [181 kB] ##### snipped ##### Reading package lists... Done
- Install the target PostgreSQL major version packages (example: 18).
$ sudo apt-get install -y postgresql-18 postgresql-client-18 Reading package lists... Building dependency tree... Reading state information... Suggested packages: postgresql-doc-18 The following NEW packages will be installed: postgresql-18 postgresql-client-18 ##### snipped ##### Setting up postgresql-18 (18.4-1.pgdg26.04+1) ... ##### snipped #####
Install matching target-version extension packages before the upgrade, such as postgresql-18-postgis-3 for a cluster that uses PostGIS.
- Confirm the source cluster is online before the upgrade.
$ sudo pg_lsclusters Ver Cluster Port Status Owner Data directory Log file 17 main 5432 online postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
pg_upgradecluster creates the target cluster during the upgrade, so it is normal to see only the source cluster here.
- Stop the source cluster.
$ sudo pg_ctlcluster 17 main stop
- Run the major version upgrade in copy mode.
$ sudo pg_upgradecluster --method=upgrade 17 main Upgrading cluster 17/main to 18/main ... Restarting old cluster with restricted connections... Stopping old cluster... Creating new PostgreSQL cluster 18/main ... ##### snipped ##### Success. Please check that the upgraded cluster works. If it does, you can remove the old cluster with pg_dropcluster 17 main - List clusters to confirm the target version is online on the original port.
$ sudo pg_lsclusters Ver Cluster Port Status Owner Data directory Log file 17 main 5433 down postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log 18 main 5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
The source cluster is kept down on another port for rollback until it is removed.
- Check that PostgreSQL accepts connections.
$ sudo -u postgres pg_isready /var/run/postgresql:5432 - accepting connections
- Verify the upgraded server version.
$ sudo -u postgres psql -c "SELECT version();" version ------------------------------------------------------------------------------------------------------------------------------------------- PostgreSQL 18.4 (Ubuntu 18.4-1.pgdg26.04+1) on aarch64-unknown-linux-gnu, compiled by gcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0, 64-bit (1 row) - Run an application-specific smoke query against a database that existed before the upgrade.
$ sudo -u postgres psql -d appdb -c "SELECT * FROM upgrade_check;" id | note ----+---------------- 1 | before upgrade (1 row)
Use a real application table, migration marker, health query, login flow, or read-only transaction that proves the upgraded cluster is serving the expected data.
- Rebuild planner statistics for all databases.
$ sudo -u postgres vacuumdb --all --analyze-in-stages --verbose vacuumdb: processing database "postgres": Generating minimal optimizer statistics (1 target) ##### snipped ##### INFO: finished analyzing table "appdb.public.upgrade_check" ##### snipped #####
Refreshing statistics helps the query planner make good choices immediately after a major upgrade.
- Check the upgraded cluster process status.
$ sudo pg_ctlcluster 18 main status pg_ctl: server is running (PID: 7986) /usr/lib/postgresql/18/bin/postgres "-D" "/var/lib/postgresql/18/main" "-c" "config_file=/etc/postgresql/18/main/postgresql.conf"
- Remove the old cluster when rollback is no longer needed.
$ sudo pg_dropcluster --stop 17 main
pg_dropcluster permanently deletes the old cluster data directory. Keep the old cluster until application verification, monitoring, backup, and rollback decisions are complete.
- Purge old major version packages when no longer needed.
$ sudo apt-get purge --assume-yes postgresql-17 postgresql-client-17 Reading package lists... Building dependency tree... Reading state information... The following packages will be REMOVED: postgresql-17* postgresql-client-17* 0 upgraded, 0 newly installed, 2 to remove and 0 not upgraded. After this operation, 68.1 MB disk space will be freed. ##### snipped #####
- Remove orphaned dependencies.
$ sudo apt-get autoremove --assume-yes Reading package lists... Building dependency tree... Reading state information... 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
- Confirm that only the target cluster remains.
$ sudo pg_lsclusters Ver Cluster Port Status Owner Data directory Log file 18 main 5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.