One major version per year, five years of fixes
PostgreSQL ships a major version roughly once a year and maintains it for five years. After that, a final minor release comes out and the branch reaches end of life, always on the November minor release: PostgreSQL 13 stopped on 13 November 2025, and PostgreSQL 14 will stop on 12 November 2026. The branches still followed run from 14 to 18; 18 has been available since 25 September 2025, and 19 is announced for September 2026.
What the end of support actually changes
- No more fixes — a vulnerability published after end of life will not be patched on your branch: the code stays as it is, indefinitely.
- Extensions fall behind —
PostGIS,pgvectororTimescaleDBstop publishing packages for a dead branch, and the next requirement becomes a blocker. - Distribution packages disappear — no more updates through
apt, and reinstalling the VPS will not necessarily bring back the same version. - Compliance notices it — an audit, a customer questionnaire or an insurance contract will flag an unmaintained component long before an incident happens.
- Clients and drivers move on — recent tooling (
psql,pg_dump, application drivers) assumes supported server versions; the gap eventually produces errors that are hard to read.
Finding out which version you actually run
The server is authoritative, not your deployment file. Connect and run SELECT version();, or SHOW server_version; for the bare value. On Debian and Ubuntu, pg_lsclusters lists every cluster with its version, its port and its state, including the ones forgotten during an earlier upgrade. In containers, the image tag is misleading: replacing postgres:16 with a newer tag does not convert the files already written in the volume.
Planning the major upgrade
Back up, then verify the backup
Export roles and global settings with pg_dumpall --globals-only, then each database with pg_dump -Fc. A backup only counts once it has been restored: replay it into a throwaway cluster and compare row counts on your main tables.
Choose between logical export and in-place conversion
pg_dump followed by pg_restore rebuilds everything in a fresh cluster: simple, reversible, but the downtime follows the volume. pg_upgrade converts the catalog of the existing cluster in minutes, provided you install the binaries of both versions side by side.
Rehearse the migration on a copy
Do not test on the machine serving traffic. Spin up a second VPS, restore the backup there, then run pg_upgrade --check: it validates compatibility without writing anything and lists the manual adjustments to expect. Time the operation: that measurement sets your window.
Cut over with writes stopped
Stop the application, then shut the server down cleanly. Run the conversion or the restore, restart on the new cluster, and let traffic back in after a first check. Choose the mode knowingly: --link and --swap speed up the cutover but leave the old cluster unusable.
Check after the cutover
Confirm the version with SELECT version();, update your extensions with ALTER EXTENSION ... UPDATE, then regenerate optimizer statistics with vacuumdb --all --analyze-in-stages. Only delete the old data directory after several days of real production use, using the script pg_upgrade points to.
Run a full backup cycle again
Your archives from before the cutover describe a cluster that no longer exists. Take a full backup on the new version and test its restore: our guide on encrypted backups with restic covers rotation and integrity checking. On a Cloud VPS, that schedule stays yours.
pg_dump/restore or pg_upgrade: how to decide
| Criterion | pg_dump / pg_restore | pg_upgrade |
|---|---|---|
| Principle | Logical export, then reimport into a fresh cluster | Conversion of the existing cluster's catalog |
| Downtime | Proportional to the data volume | A few minutes, largely volume-independent in `--link` or `--swap` mode |
| Disk space | Room for the archive, then for both clusters | `--link` does not copy the files, but requires the same file system |
| Rollback | The old cluster stays intact and the archive stays replayable | After `--link` or `--swap`, the old cluster can no longer be started |
| Pre-flight check | Failure shows up late, during the import | `pg_upgrade --check` validates before any write |
| Optimizer statistics | Fully rebuilt after the import | Mostly carried over since PostgreSQL 18, rebuilt before that |
| Parallelism | `pg_dump -j` and `pg_restore -j` | `--jobs` to process several databases in parallel |
| Typical use case | Modest volumes, a change of machine, a reorganization | Large volumes, in-place upgrades, a short window |
The real check is the restore
A branch at end of life raises no alert, and neither does a backup that has never been restored. Before the cutover, list the archive with pg_restore --list, restore it fully on a separate machine, and compare row counts on your sensitive tables. Then write your branch's end-of-life date into the calendar that already holds your renewals: a silent deadline becomes a planned task.