Skip to content

Cloud & Infrastructure · Databases

PostgreSQL 14 End of Life: How to Upgrade to 18, Not 19

PostgreSQL 14 gets its last release on November 12, 2026. Pick 18 (or 17), choose pg_upgrade or logical replication, and fix what breaks between 14 and 18.

Prathviraj Singh

Prathviraj Singh

11 min read

A paper-cut elephant walking across a stone bridge from a cracked old tower toward a bright new tower at sunrise

Sponsored

Share

PostgreSQL 14 gets its last release on November 12, 2026, and the right place to land is PostgreSQL 18. If a required extension or vendor product hasn’t certified 18, land on 17. The community supports each major version for five years, and 14 is out of runway: after the final minor release there are no more fixes, security or otherwise.

This guide covers the decision (which version, which method) and the part that usually eats the weekend: what changed between 14 and 18 that can break an application or a monitoring dashboard. Everything below comes from the official versioning policy and the release notes for each major version, linked at the end.

Which version to move to

TargetFinal release (EOL)Pick it when
18November 14, 2030Default choice. Longest runway of any released version.
17November 8, 2029An extension, ORM or vendor product isn’t certified for 18 yet.
16November 9, 2028Only if you already plan a second upgrade within two years.
15November 11, 2027Avoid. You would be doing this again in a year.

What about 19? Its release candidate 1 shipped on October 15, 2026, with general availability planned for November 19, 2026 (a second candidate is due November 12). That is a week after 14 goes end of life. A brand-new major is a fine thing to test on a staging copy. It is a poor first production target, because the early minor releases are where the surprises get fixed. The pull-back of six features during the beta, covered in our PostgreSQL 19 Beta 4 notes, is a reasonable argument for patience.

You can skip versions. 14 to 18 is a single hop with any of the methods below. What you can’t skip is reading the incompatibility list for 15, 16, 17 and 18, because they all apply to you.

Pick the upgrade method

MethodDowntimeRollbackBest for
pg_dump / pg_restoreGrows with data sizeOld cluster untouchedSmall databases, or when you want a clean rebuild
pg_upgrade (copy)Minutes to hoursOld cluster untouchedDefault for most self-hosted setups
pg_upgrade --link or --swapMinutesRestore from backup once the new cluster has startedLarge clusters with a tested backup
pg_upgrade --cloneMinutesOld cluster stays usableBtrfs, XFS (reflink) or APFS storage
Logical replicationSeconds at cutoverSwitch back, if you keep reverse replicationLarge databases where minutes of downtime cost real money

Link mode is where people get hurt. The pg_upgrade documentation is blunt about it: once the new cluster has started, “it is unsafe to use the old cluster”, and you restore from backup. Take a verified backup before you start, not after you notice a problem. If you want speed and an escape hatch, --clone gives link-like speed while leaving the old cluster usable, but it needs a filesystem that supports reflinks.

Managed services (RDS, Cloud SQL, Azure) have their own upgrade buttons and their own end-of-support dates for 14. The mechanics below still apply to the application side, so read the sections on breaking changes whichever route you take. Check your provider’s lifecycle page for when they stop supporting 14 or start charging extra for it.

The checksum trap

PostgreSQL 18 changed initdb to enable data checksums by default. pg_upgrade requires the old and new clusters to have matching checksum settings. A typical 14 cluster was created without checksums, so a default initdb for 18 produces a cluster that pg_upgrade will refuse to upgrade into.

You have two ways out. Create the new cluster without checksums, using the option 18 added for exactly this:

/usr/lib/postgresql/18/bin/initdb --no-data-checksums -D /var/lib/postgresql/18/main

Or enable checksums on the old cluster first, with the server stopped (pg_checksums --enable rewrites every data file, so it takes a while on a big database). Most teams take the first route to keep the maintenance window short, then decide about checksums separately. They do catch silent storage corruption, so they are worth having eventually.

A pg_upgrade run, step by step

Install the 18 binaries alongside 14, and install the new-version builds of every extension you use. Don’t run CREATE EXTENSION in the new cluster. The schema definitions are copied over from the old one.

Run the check with the new version’s pg_upgrade, and add the same mode flag (--link, --clone) you plan to use, so you get the mode-specific checks as well:

/usr/lib/postgresql/18/bin/pg_upgrade \
  --old-bindir=/usr/lib/postgresql/14/bin \
  --new-bindir=/usr/lib/postgresql/18/bin \
  --old-datadir=/var/lib/postgresql/14/main \
  --new-datadir=/var/lib/postgresql/18/main \
  --jobs=8 --check

The check runs against a live old server too, as long as you use different ports. It lists manual fixes you’ll need. Do this on a restored copy of production first, not on an empty dev database, because the blockers live in your real data and your real extensions.

When the check is clean, stop both servers and run the same command without --check. Then start the new cluster and do the part people forget: statistics. Version 18 carries most optimizer statistics across in pg_upgrade, but not extended statistics (CREATE STATISTICS objects), extension-defined statistics or cumulative statistics. The documentation’s recommended fix:

vacuumdb --all --analyze-in-stages --missing-stats-only
vacuumdb --all --analyze-only

The first command quickly builds minimal statistics for anything missing so queries get sane plans. The second brings cumulative statistics up to date so autovacuum triggers correctly. If your vacuum_cost_delay is non-zero, override it for this run with PGOPTIONS='-c vacuum_cost_delay=0'.

Also run any scripts pg_upgrade generated (they may rebuild indexes), and apply extension updates it reports. Keep the old cluster until you’re confident, and delete it last.

The logical replication route

When the maintenance window has to be seconds, replicate into a new 18 cluster and switch over. The outline:

-- On the old 14 primary
CREATE PUBLICATION upgrade_pub FOR ALL TABLES;

-- On the new 18 cluster, after loading the schema with pg_dump --schema-only
CREATE SUBSCRIPTION upgrade_sub
  CONNECTION 'host=old-db dbname=app user=replicator'
  PUBLICATION upgrade_pub;

Logical replication has gaps you must cover by hand. Sequences aren’t replicated, so you reset them to match before cutover. DDL isn’t replicated, so freeze schema changes during the migration. Tables without a primary key need a replica identity (ALTER TABLE ... REPLICA IDENTITY FULL works, at a performance cost). Large objects aren’t replicated either. And physical standbys can’t span major versions, so plan to rebuild them from the new primary. This route is more moving parts than pg_upgrade, and it earns its keep only when downtime is genuinely expensive. For the application-side discipline around schema changes under load, our zero-downtime migrations guide covers the patterns.

What breaks between 14 and 18

Four major versions of incompatibilities stack up. These are the ones most likely to bite a normal web application, taken from the migration sections of each release’s notes.

Security defaults (15)

PostgreSQL 15 removed CREATE permission on the public schema from the PUBLIC role, and made pg_database_owner the owner of public. The release notes say both changes apply to new clusters and newly created databases. Upgrading a cluster or restoring a dump preserves the existing permissions. So your upgraded database behaves as before, and any new database you create afterward does not. If a deploy script creates a database and expects the app role to create tables in public, it will fail on a fresh database. Grant it explicitly, or better, give the app role its own schema:

REVOKE CREATE ON SCHEMA public FROM PUBLIC;  -- adopt the new default in old databases

Version 15 also removed exclusive backup mode and renamed pg_start_backup() and pg_stop_backup() to pg_backup_start() and pg_backup_stop(). If an old backup script calls the old names, it breaks. Grep your tooling for them before the upgrade, because you find out the hard way otherwise: on the night the backup job fails.

Roles and parameters (16)

PostgreSQL 16 restricted what CREATEROLE can do. A role with CREATEROLE used to be able to change many aspects of any non-superuser role. Now it needs ADMIN OPTION on the target role, and can only change properties like CREATEDB, REPLICATION and BYPASSRLS if it holds them too. Provisioning scripts that use a CREATEROLE role to manage other roles are the likely casualty.

Several settings were also removed or renamed in 16: force_parallel_mode became debug_parallel_query, and vacuum_defer_cleanup_age, promote_trigger_file and the read-only lc_collate and lc_ctype variables are gone. If your configuration management still sets any of them, the server won’t start with that config, so search your Ansible or Terraform for them.

Monitoring queries (17)

This one trips up dashboards rather than applications. PostgreSQL 17 removed buffers_backend and buffers_backend_fsync from pg_stat_bgwriter (use pg_stat_io), and renamed the pg_stat_statements columns blk_read_time and blk_write_time to shared_blk_read_time and shared_blk_write_time. It also renamed columns in pg_stat_progress_vacuum and pg_stat_slru. Exporters and Grafana panels that select these by name fail quietly or show gaps.

-- Before (16 and earlier)
SELECT blk_read_time, blk_write_time FROM pg_stat_statements;
-- After (17+)
SELECT shared_blk_read_time, shared_blk_write_time FROM pg_stat_statements;

Version 17 also made maintenance operations (ANALYZE, CLUSTER, CREATE INDEX, REFRESH MATERIALIZED VIEW, REINDEX, VACUUM) run with a safe search_path. Functions used by expression indexes and materialized views that reference non-default schemas must set a search path at creation. A function that quietly relied on your session’s search_path is the usual offender, and it shows up as an error during autovacuum or a reindex, which is a bad time to find it. And old_snapshot_threshold is gone, so remove it from your configuration.

Smaller behavior changes (18)

  • VACUUM and ANALYZE now process inheritance children by default. Use VACUUM (ONLY) parent_table for the old behavior. Partitioned tables are the common case.
  • MD5 passwords are deprecated. CREATE ROLE and ALTER ROLE now warn when setting an MD5 password, and support will be removed in a future major version. Move roles to SCRAM now. You can silence the warnings with md5_password_warnings = off, but that only postpones the work.
  • COPY FROM no longer treats \. as end-of-file in CSV. Older psql clients connecting to an 18 server may have \copy problems, so update client tooling.
  • Full-text search and pg_trgm may behave differently on clusters that use a non-libc collation provider. The release notes recommend reindexing those indexes after a pg_upgrade.
  • AFTER triggers run as the role that was active when the event was queued, not the role at commit time. Review triggers in multi-role setups.
  • Unlogged partitioned tables are disallowed, and rule privileges are gone from GRANT and REVOKE.

Version 15’s long list of small tightenings (stricter numeric literals like 123abc, interval rounding, removal of plpythonu) is worth a skim too. Most apps never hit them, but a report query with an odd literal can.

Test plan that fits in a day

  1. Restore last night’s production backup to a staging server. Don’t test on an empty database.
  2. Run pg_upgrade --check with your chosen mode. Fix every blocker it reports.
  3. Do a full rehearsal, including vacuumdb, and time it. That number is your downtime estimate.
  4. Replay your top queries from pg_stat_statements before and after, and compare plans. A slow query is better found on staging. Our query optimization guide for application developers covers reading the plans.
  5. Point the application’s test suite and migrations at the new server. ORMs and drivers are where a stale client library shows up.
  6. Check monitoring and backup tooling against the renamed views and functions above.
  7. Write the rollback step down, with the point of no return marked (the first start of the new cluster in link or swap mode).

If you also carry big tables you’ve been meaning to partition, the upgrade window is the wrong time to start. Do it separately, with the approach in partitioning an existing table without downtime. And if you’re deciding whether 18’s features justify the move beyond the support dates, our PostgreSQL 18 upgrade guide covers async I/O, UUIDv7 and the rest.

The takeaway: you have until November 12. Book a rehearsal on a production copy this week, target 18 (or 17 if something blocks it), and leave 19 for a second, calmer upgrade next year.

Frequently asked questions

What happens when PostgreSQL 14 reaches end of life?
The PostgreSQL project stops shipping bug fixes and security fixes for version 14 after the final minor release on November 12, 2026. Your database keeps running, but any vulnerability found later stays unpatched on 14. Managed services such as RDS or Cloud SQL publish their own support timelines, so check your provider's lifecycle page for the date they stop or start charging for extended support.
Should I upgrade from PostgreSQL 14 to 17 or 18?
Choose 18 unless something blocks it. It is supported until November 14, 2030, against November 8, 2029 for 17. Pick 17 when a required extension, ORM or vendor product hasn't been certified against 18 yet. Both need the same upgrade method, so the choice is about support length and compatibility, not effort.
Can I skip PostgreSQL versions when upgrading?
Yes. pg_upgrade, pg_dump and logical replication all work from any supported older major version straight to a newer one, so 14 to 18 is one hop. You still need to read the incompatibility notes for every version in between (15, 16, 17 and 18), because their changes stack.
Do I need to wait for PostgreSQL 19?
No. Version 14 reaches end of life a week before 19 is scheduled to ship, and a new major release is usually a better production target after its first minor releases. Move to 18 now and treat 19 as a separate, later upgrade.

Sources

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored