PostgreSQL releases one major version about once a year and supports each for five years, with minor releases at least every three months. On 25 October 2026, versions 14 to 18 are supported, 14 ends on 12 November 2026, and 19 is a release candidate. Security milestones: row-level security in 9.5, SCRAM in 10, OAuth in 18.
The rest of this post goes version by version: history, the versioning policy, a dated table of every major release the project lists, the security changes in each, authentication methods, how row-level security works and where it stops applying, and a step by step audit you can run today. Every date and support window below comes from postgresql.org pages fetched on 25 October 2026.
Key takeaways
- Five years, then done. The PostgreSQL Global Development Group supports a major version for five years after its first release, then ships one final minor release and stops. The policy describes no community support beyond that.
- Quarterly minors are the security channel. Security fixes ship inside minor releases, on the second Thursday of February, May, August and November, plus out-of-cycle releases when something cannot wait. The next scheduled one is 12 November 2026, which is also the final release of PostgreSQL 14.
- Version 10 changed the numbering. Before 10, a major version was the first two numbers (9.5, 9.6). From 10 on, it is the first number only (10, 11, 12), and the second number is the minor.
- SCRAM-SHA-256 arrived in 10 and became the default password hashing in 14. MD5 passwords are deprecated as of 18 and emit warnings; the 19 release notes add a warning after every successful MD5 login.
- Row-level security arrived in 9.5, restrictive policies in 10, and security invoker views in 15. Superusers, roles with BYPASSRLS and (by default) table owners skip policies entirely.
- Version 15 removed PUBLIC's CREATE on the public schema for new databases. Upgraded databases keep whatever grants they had, so check them.
- OAuth 2.0 bearer token authentication arrived in 18. PostgreSQL does not provide the authorization server; you bring one and a validator library.
- initdb still defaults to trust authentication "for ease of installation." If nobody changed pg_hba.conf after setup, local users can log in as anyone.
How PostgreSQL grew from POSTGRES at Berkeley and Postgres95
PostgreSQL's own documentation tells the history in three steps, and each one left fingerprints on the system you run today.
The Berkeley POSTGRES project (1986 to 1993)
The POSTGRES project was led by Professor Michael Stonebraker at the University of California at Berkeley and sponsored by DARPA, the Army Research Office, the National Science Foundation and ESL, Inc. Implementation began in 1986. A first "demoware" system was running in 1987 and was shown at the 1988 ACM SIGMOD conference. Version 1 went to a few external users in June 1989, Version 2 followed in June 1990 with a redesigned rule system, and Version 3 appeared in 1991 with multiple storage managers and an improved executor.
By 1993 the external user community had nearly doubled in a year, and maintaining a prototype for outside users was eating research time. The Berkeley project officially ended with Version 4.2. The query language at this point was PostQUEL, not SQL. One trace survives: the client library is still called libpq, named after PostQUEL.
Postgres95 (1994 to 1996)
In 1994, Andrew Yu and Jolly Chen added an SQL interpreter to POSTGRES and released the result to the web as Postgres95, an open source descendant of the Berkeley code. The docs say the code was converted to ANSI C, trimmed by 25%, and that release 1.0.x ran roughly 30 to 50% faster than POSTGRES 4.2 on the Wisconsin benchmark. Postgres95 introduced psql, rewrote aggregates, added GROUP BY, and removed the inversion file system and the instance-level rule system.
PostgreSQL (1996 onward)
By 1996 the project decided "Postgres95" would not age well and renamed it PostgreSQL. Version numbering restarted at 6.0 to continue the sequence the Berkeley project had begun. The documentation notes that "Postgres" remains an accepted official name. From there, the release notes at postgresql.org/docs/release record every version, and the versioning policy page lists every major release from 6.3 onward with its first and final release dates.
How the PostgreSQL versioning policy works
The versioning policy page is short and specific. Here is what it says, in plain terms.
Major releases: about once a year, five years of support
The project "releases a new major version containing new features about once a year." Each major version gets bug fixes and security fixes "at least once every three months" in minor releases. The project "supports a major version for 5 years after its initial release. After this, a final minor version will be released and the software will then be unsupported (end-of-life)."
In practice, majors land in September or October. Of the last eight majors on the policy page, 11 (18 October 2018), 12 (3 October 2019) and 15 (13 October 2022) shipped in October; 13, 14, 16, 17 and 18 shipped in September. End of life falls on the November quarterly minor five years later. The roadmap page says 19 "is planned for October 2026," and the project announced 19 RC 1 on 15 October 2026. Until 19's final release is announced, treat its date as unconfirmed.
Minor releases: second Thursday of February, May, August and November
The developer roadmap page says the project "aims to make at least one minor release every quarter, on a predefined schedule," normally on the second Thursday of February, May, August and November. Each scheduled date ships a new minor release for every supported version at once. The current published schedule is:
| Scheduled minor release | Notes |
|---|---|
| 12 November 2026 | Final scheduled release for PostgreSQL 14 (policy page lists 14's final release as 12 November 2026) |
| 11 February 2027 | First quarterly release with 15 to 19 (assuming 19 is final by then) |
| 13 May 2027 | Regular quarterly |
| 12 August 2027 | Regular quarterly |
The roadmap calls this list "a minimum." When a bug or security issue is important enough, the release team ships out of cycle. The versioning policy says the same: a critical fix "may" be released outside the roadmap.
How numbering changed at version 10
Before PostgreSQL 10, a major version was identified by the first two numbers: 9.5 and 9.6 are different majors, and 9.6.3 to 9.6.4 is a minor update. Starting with 10, the first number alone is the major: 10, 11, 12. The minor is the second number, so 16.4 to 16.5 is a minor update.
This trips people up in two ways. First, scripts that parse 9.6.24 and 16.15 with the same logic will get one of them wrong. Use server_version_num instead (for example 90624 versus 160015). Second, "PostgreSQL 9" is not a version. 9.0 through 9.6 are seven separate majors with seven separate end of life dates.
Minor versus major upgrades
The policy page draws a hard line between the two:
- Minor upgrades need no dump and restore. Stop the server, install the new binaries, restart. The project says it "considers performing minor upgrades to be less risky than continuing to run an old minor version," and recommends always running the current minor for your major. Read the release notes anyway, because some minors require an extra step such as reindexing a specific index type.
- Major upgrades change the on-disk format, so you need a dump and reload, pg_upgrade, or logical replication. You can skip intervening majors (say 13 straight to 18), but the project recommends reading the release notes of every major in between.
Every PostgreSQL major version: release and end of life dates
The table below reproduces every major version on the postgresql.org versioning policy page as fetched on 25 October 2026, with the current or final minor release for each. Versions 6.0 to 6.2 are not listed on the policy page, so they are not listed here.
| Major | First release | Final release (end of life) | Current or final minor | Supported on 25 Oct 2026 |
|---|---|---|---|---|
| 19 | Not yet released (RC 1 on 15 Oct 2026) | Not yet published | n/a | Release candidate |
| 18 | 25 September 2025 | 14 November 2030 | 18.6 | Yes |
| 17 | 26 September 2024 | 8 November 2029 | 17.11 | Yes |
| 16 | 14 September 2023 | 9 November 2028 | 16.15 | Yes |
| 15 | 13 October 2022 | 11 November 2027 | 15.19 | Yes |
| 14 | 30 September 2021 | 12 November 2026 | 14.24 | Yes, ends in weeks |
| 13 | 24 September 2020 | 13 November 2025 | 13.23 | No |
| 12 | 3 October 2019 | 21 November 2024 | 12.22 | No |
| 11 | 18 October 2018 | 9 November 2023 | 11.22 | No |
| 10 | 5 October 2017 | 10 November 2022 | 10.23 | No |
| 9.6 | 29 September 2016 | 11 November 2021 | 9.6.24 | No |
| 9.5 | 7 January 2016 | 11 February 2021 | 9.5.25 | No |
| 9.4 | 18 December 2014 | 13 February 2020 | 9.4.26 | No |
| 9.3 | 9 September 2013 | 8 November 2018 | 9.3.25 | No |
| 9.2 | 10 September 2012 | 9 November 2017 | 9.2.24 | No |
| 9.1 | 12 September 2011 | 27 October 2016 | 9.1.24 | No |
| 9.0 | 20 September 2010 | 8 October 2015 | 9.0.23 | No |
| 8.4 | 1 July 2009 | 24 July 2014 | 8.4.22 | No |
| 8.3 | 4 February 2008 | 7 February 2013 | 8.3.23 | No |
| 8.2 | 5 December 2006 | 5 December 2011 | 8.2.23 | No |
| 8.1 | 8 November 2005 | 8 November 2010 | 8.1.23 | No |
| 8.0 | 19 January 2005 | 1 October 2010 | 8.0.26 | No |
| 7.4 | 17 November 2003 | 1 October 2010 | 7.4.30 | No |
| 7.3 | 27 November 2002 | 27 November 2007 | 7.3.21 | No |
| 7.2 | 4 February 2002 | 4 February 2007 | 7.2.8 | No |
| 7.1 | 13 April 2001 | 13 April 2006 | 7.1.3 | No |
| 7.0 | 8 May 2000 | 8 May 2005 | 7.0.3 | No |
| 6.5 | 9 June 1999 | 9 June 2004 | 6.5.3 | No |
| 6.4 | 30 October 1998 | 30 October 2003 | 6.4.2 | No |
| 6.3 | 1 March 1998 | 1 March 2003 | 6.3.2 | No |
A few things stand out. The five year rule has held cleanly since 9.2, but the end of life date is always the November (or February, for majors that shipped in winter) quarterly release after the five year mark, not the exact anniversary. 9.5 is the odd one out: it shipped in January 2016, so it ended in February 2021. And 8.0 and 7.4 were both retired on 1 October 2010, a reminder that the policy was less regular before the 9.x era.
The "current minor" column moves every quarter. If your server reports 16.9 and the policy page says 16.15, you are six quarterly security releases behind, whatever your major version's end of life date says.
Security features by version: the full timeline
This is the part most release history pages skip. The table below lists the security relevant change in each major, taken from that version's release notes. "Security relevant" here means authentication, encryption in transit, privileges, roles, row-level security and secure defaults, not individual vulnerability fixes (those ship in minors and are listed on the security page).
| Version | Year | Security relevant changes (from the release notes) |
|---|---|---|
| 7.0 | 2000 | Password authentication enabled and required for new users; unprivileged users can change their own passwords; initdb can prompt for a superuser password |
| 7.1 | 2001 | New OpenSSL configuration option; Kerberos V fixes |
| 7.3 | 2002 | Schemas and the public schema; search_path; EXECUTE and USAGE privileges; functions that run with their creator's privileges; encrypted stored passwords become the default; ~/.pgpass |
| 8.1 | 2005 | Roles replace users and groups; roles can be members of roles; SET ROLE |
| 8.4 | 2009 | SSL client certificates as a user authentication method; certificate chains in the server certificate file; column-level privileges |
| 9.0 | 2010 | RADIUS authentication; LDAP improvements; passwordcheck module; GRANT/REVOKE IN SCHEMA; ALTER DEFAULT PRIVILEGES |
| 9.1 | 2011 | SECURITY LABEL command and the sepgsql module for SELinux integration |
| 9.2 | 2012 | security_barrier views; LEAKPROOF function attribute; privileges on data types |
| 9.4 | 2014 | Native Kerberos removed in favour of GSSAPI; ECDH key exchange; better default ssl_ciphers; server cipher order preferred by default |
| 9.5 | 2016 | Row-level security (CREATE POLICY, ENABLE ROW LEVEL SECURITY); certificate Subject Alternative Name checks; pg_stat_ssl view; SSL renegotiation removed |
| 9.6 | 2016 | Role names starting with pg_ reserved; first built-in role pg_signal_backend; privilege system replaces hard-wired superuser checks on many functions; CREATEUSER option removed |
| 10 | 2017 | SCRAM-SHA-256 authentication and storage; password_encryption becomes an enum; plain text stored passwords removed; restrictive RLS policies; pg_monitor and related roles; pg_hba_file_rules view |
| 11 | 2018 | SCRAM channel binding (server side); LDAPS; pg_read_server_files, pg_write_server_files, pg_execute_server_program roles |
| 12 | 2019 | GSSAPI encryption of TCP connections (hostgssenc); clientcert=verify-full for certificate plus password; ssl_min_protocol_version and ssl_max_protocol_version |
| 13 | 2020 | Default minimum TLS version raised from 1.0 to 1.2; libpq channel_binding parameter; trusted extensions installable by non-superusers |
| 14 | 2021 | password_encryption defaults to scram-sha-256; pg_read_all_data, pg_write_all_data, pg_database_owner; "default roles" renamed "predefined roles"; clientcert overhaul |
| 15 | 2022 | PUBLIC CREATE on the public schema removed; public schema owned by pg_database_owner; views can run with the caller's privileges (security_invoker); pg_checkpoint role |
| 16 | 2023 | CREATEROLE restricted (ADMIN OPTION required); pg_create_subscription; scram_iterations; libpq require_auth; pg_use_reserved_connections |
| 17 | 2024 | MAINTAIN privilege and pg_maintain role; safe search_path during maintenance operations; direct TLS negotiation (sslnegotiation=direct) |
| 18 | 2025 | OAuth authentication; MD5 passwords deprecated with warnings; pg_signal_autovacuum_worker; ssl_tls13_ciphers; SCRAM pass-through for postgres_fdw and dblink; pg_dump --no-policies |
| 19 | 2026 (pending) | Draft notes as of 9 Oct 2026: warning after successful MD5 login; OAuth validators can add pg_hba.conf options; libpq oauth_ca_file |
The table is the list. What follows is the context behind it, because the order of these changes explains why so many installations still carry old defaults.
7.x and 8.x: the privilege model most people still use
7.3 introduced schemas, the public schema, search_path, and functions that run with their creator's privileges (SECURITY DEFINER today). That last feature is the root of a whole category of later advice about pinning search_path inside privileged functions, advice the project itself acted on in 17 when it made maintenance operations use a safe search_path. 8.1 then replaced separate users and groups with one role system, where any role can log in, own objects and be a member of other roles. Every modern design, from application roles to predefined roles, rests on that. 8.4 made a TLS client certificate a way to authenticate a user, not just to prove the client holds some certificate.
9.x: building blocks, then row-level security
9.0's ALTER DEFAULT PRIVILEGES let new objects get the right grants without a manual step. 9.2's security_barrier views and LEAKPROOF functions stopped the planner from running user-supplied functions on rows a view should have filtered out, which is the plumbing row-level security later depends on. 9.5 (7 January 2016) shipped row-level security itself, which the notes describe as control over "which users can add, modify, or even see rows in a table," together with the BYPASSRLS attribute. 9.6 reserved pg_ role names and moved many superuser-only functions onto the normal privilege system, which made later predefined roles possible: instead of handing a monitoring agent superuser, you grant a narrow role.
10 to 14: the long road to SCRAM by default
PostgreSQL 10 (5 October 2017) added SCRAM-SHA-256, which the notes say "provides better security than the existing md5 negotiation and storage method," and removed unencrypted password storage entirely. But SCRAM was opt-in for four more years. 11 added channel binding on the server with an honest caveat: it only stops man-in-the-middle attacks if the client can insist on it, and libpq could not yet. 13 fixed that with the libpq channel_binding parameter and raised the server's default minimum TLS version from 1.0 to 1.2. Only in 14 did password_encryption default to scram-sha-256. Every password set on a default configuration before 14 was stored as MD5, which is why clusters upgraded from 13 or earlier still hold MD5 hashes years later.
15 to 19: safer defaults and the end of MD5
15 removed PUBLIC's CREATE on the public schema, but only for new databases. The note says that for existing databases, "especially those having multiple users, consider revoking CREATE permission on the public schema." 16 narrowed CREATEROLE so changes such as adding members need ADMIN OPTION on the target role, and added libpq require_auth so a client can refuse weaker methods than it expects. 17 added the MAINTAIN privilege for delegating VACUUM, ANALYZE, REINDEX and similar work without ownership. 18 (25 September 2025) added OAuth and deprecated MD5: "Support for MD5 passwords will be removed in a future major version release." The 19 notes on postgresql.org were marked "AS OF 2026-10-09" with no release date when fetched, so treat 19 details as draft until the final release.
How PostgreSQL authentication methods compare
pg_hba.conf decides, line by line, which method applies to which connection. The first matching record wins. The current docs list these methods; the table adds when each became notable and what it protects against.
| Method | What it does | Since (notable change) | Password on the wire | Use it for |
|---|---|---|---|---|
| trust | Allows anyone who can connect, as any role, with no check | Original; still the initdb default | n/a | Nothing reachable by untrusted users |
| reject | Refuses the connection | Original | n/a | Explicit deny rules near the top of the file |
| password | Sends the password in clear text | Original | Clear text | Only inside TLS, and better avoided |
| md5 | Custom challenge-response over MD5 hashes | Old; deprecated in 18 | Not sent in clear | Legacy only; switches to SCRAM automatically if the stored hash is SCRAM |
| scram-sha-256 | RFC 7677 challenge-response, salted and iterated storage | 10; default hashing since 14 | Not sent; resists sniffing | The default choice for password logins |
| cert | TLS client certificate identifies the user | 8.4 | No password | Service to service, strong machine identity |
| gss | GSSAPI (Kerberos) | GSSAPI encryption in 12 | No password | Kerberos estates |
| sspi | Windows single sign-on | Long standing | No password | Windows domains |
| ldap | Checks the password against a directory | Improved in 9.0; LDAPS in 11 | Password reaches the server | Directory-backed logins, always over TLS |
| radius | Checks the password against RADIUS | 9.0 | Password reaches the server | Existing RADIUS estates |
| pam | Delegates to PAM | Long standing | Password reaches the server | OS-integrated setups |
| peer, ident | Uses the OS user name (local) or ident server | Long standing | n/a | Local admin access via Unix sockets |
| oauth | Validates an OAuth 2.0 bearer token via a validator library | 18 | Token, not password | Central identity provider, short-lived tokens |
Defaults per version, in one place
- initdb authentication: the initdb docs say "trust is the default for ease of installation." That is still true in the current docs. Use
--auth-localand--auth-host(or-A) at initdb time, or edit pg_hba.conf before the server listens on anything but a trusted socket. Packaged builds may write their own defaults, so read the file you actually have. - listen_addresses: by default the server listens only on localhost, per the pg_hba.conf chapter. Trust on loopback is still a privilege escalation for every local OS user.
- password_encryption:
md5through 13,scram-sha-256from 14. Setting the parameter only affects passwords set afterwards. - md5 in pg_hba.conf with a SCRAM hash: the docs say SCRAM is chosen automatically. This is what makes a gradual migration possible.
- Minimum TLS version: 1.0 through 12, 1.2 from 13.
- MD5 passwords: deprecated in 18, with warnings when set; 19 draft notes add warnings when used.
Where OAuth fits
The OAuth chapter is explicit about roles. libpq clients such as psql are the OAuth clients. PostgreSQL is the resource server. "PostgreSQL does not provide an authorization server; it is the responsibility of the OAuth provider." The server validates bearer tokens (RFC 6750) through a validator module named in oauth_validator_libraries, and the issuer option in pg_hba.conf must exactly match the provider's discovery document: "No variations in case or formatting are permitted."
The practical consequence is that OAuth moves the hard part of authentication out of PostgreSQL. Token lifetime, revocation and multi-factor rules live in your identity provider, and the quality of the validator library you load becomes part of your database's trust boundary. Treat that library like any other privileged dependency: know which version you run and where it came from.
How row-level security works and where it fails
Row-level security (RLS) adds a per-row filter to every normal query against a table. It is powerful and it is easy to deploy in a way that looks enforced but is not.
The mechanics
ALTER TABLE t ENABLE ROW LEVEL SECURITY;turns it on. From that moment, normal access must be allowed by a policy.- If RLS is enabled and no policy applies, a default deny is used: no rows are visible or modifiable.
CREATE POLICYdefines aUSINGexpression (which existing rows are visible or affected) and aWITH CHECKexpression (which new or updated rows are allowed). If you give onlyUSING, it is reused asWITH CHECK.- Policies can target specific commands (SELECT, INSERT, UPDATE, DELETE, or ALL) and specific roles.
- Multiple permissive policies (the default) combine with OR. Restrictive policies (since 10) combine with AND, so a restrictive policy must pass in addition to at least one permissive one.
A minimal tenant isolation example, adapted from the patterns in the docs:
-- Each row belongs to a tenant; the application sets app.tenant_id per session.
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices FORCE ROW LEVEL SECURITY; -- apply to the owner too
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
-- An extra restrictive policy: never show soft-deleted rows to app_reader.
CREATE POLICY hide_deleted ON invoices AS RESTRICTIVE
FOR SELECT TO app_reader
USING (deleted_at IS NULL);
Who bypasses RLS
The docs are direct about this, and it is where most RLS failures start:
- Superusers and roles with BYPASSRLS "always bypass the row security system." If your application connects as a superuser, every policy is decoration.
- Table owners "normally bypass row security as well," unless you run
ALTER TABLE ... FORCE ROW LEVEL SECURITY. Many applications connect as the role that ran the migrations, which owns the tables. - Only the table owner can enable or disable RLS and add policies. Anyone who can become the owner (through membership) controls the policies.
Where else RLS does not apply
- Whole-table operations. TRUNCATE and REFERENCES are not subject to row security.
- Referential integrity checks. Unique, primary key and foreign key checks "always bypass row security." The docs warn about "covert channel" leaks: a user can learn that a row exists because an insert fails on a unique constraint, even though they cannot see it.
- Views run as their owner by default. Before 15, a view always accessed its base tables with the view owner's privileges and the owner's RLS policies. From 15 you can create the view
WITH (security_invoker = true)so the caller's policies apply. A view owned by a superuser or table owner over an RLS table will show every row to anyone allowed to read the view. - SECURITY DEFINER functions run as their owner, so a function owned by a privileged role can read past policies on the caller's behalf.
- Policy expressions that read other tables can race. The docs walk through a READ COMMITTED case where a concurrent
SELECT ... FOR UPDATElets a user see a row updated to hide it from them. Prefer policies that only look at the row being accessed, or lock the referenced rows. - Leakproof functions may be evaluated before the policy check. Only mark functions LEAKPROOF if they truly cannot reveal their input through errors or side effects.
- Logical replication. The 15 notes say that because RLS policies are not checked during apply, only superusers, roles with BYPASSRLS and table owners can replicate into tables with policies.
Backups and RLS
The docs warn that RLS silently omitting rows from a backup "could be disastrous." Setting row_security = off does not bypass policies; it makes any query that would be filtered throw an error so you notice. The pg_dump docs say it sets row_security to off by default and throws an error if the user cannot bypass row security, which is why backups run as a role that can. Since 18, --no-policies lets you dump data without carrying policies to a system with a different model.
How the PostgreSQL project publishes security information
The project's security page lays out a shared responsibility model and a single place for vulnerability data. At concept level:
- Scope. A PostgreSQL security vulnerability is an issue that lets a user "gain access to privileges or data that they do not have permission to use," or execute arbitrary code through a PostgreSQL process. Actions a superuser takes are not vulnerabilities. A denial of service from an authenticated, valid SQL statement is typically treated as a bug, not a security issue.
- Shared responsibility. The project covers PostgreSQL itself; the hardware, operating system, client libraries, extensions and frameworks around it are part of the deployment's responsibility. Vulnerabilities "can exist both in PostgreSQL and software within the PostgreSQL ecosystem."
- CVE Numbering Authority. The PostgreSQL Project is a CNA (with Red Hat as its root) and assigns CVEs for PostgreSQL, its RPM and DEB packaging, the Windows and macOS installers, pgJDBC, psqlODBC, pgAdmin, PgBouncer, PostgreSQL Anonymizer and pgvector, on request.
- Reporting. Issues go to security@postgresql.org; pgJDBC, pgAdmin and pgvector have their own addresses. The project does not run a bug bounty.
- Fix delivery. Fixes ship in the next scheduled minor release where possible, later if the fix is complex, or in an out-of-cycle release in exceptional cases. A new major release contains every earlier security fix.
- Notifications. Subscribe to the pgsql-announce mailing list, filtered to the Security tag if you only want security releases.
- Known vulnerabilities table. One page lists each vulnerability with the affected majors, the minor that fixes it, whether an exploit needs a valid login, and a CVSS score, filterable by supported version.
The "requires a valid login" column is worth using in triage. A vulnerability that needs an authenticated session is much more urgent on a database exposed to many application roles or tenants than on one reachable only by a single service account.
Supported versions: release date, end of life and key security change
For planning, this is the table to pin. It covers the majors still supported on 25 October 2026 plus the two most recent to fall out of support.
| Version | Released | End of life | Key security change to plan around |
|---|---|---|---|
| 18 | 25 Sep 2025 | 14 Nov 2030 | OAuth authentication; MD5 passwords deprecated |
| 17 | 26 Sep 2024 | 8 Nov 2029 | MAINTAIN privilege and pg_maintain; safe search_path in maintenance; direct TLS |
| 16 | 14 Sep 2023 | 9 Nov 2028 | CREATEROLE narrowed; require_auth in libpq; scram_iterations |
| 15 | 13 Oct 2022 | 11 Nov 2027 | PUBLIC CREATE removed from public schema; security_invoker views |
| 14 | 30 Sep 2021 | 12 Nov 2026 | SCRAM becomes default password hashing; pg_read_all_data and pg_write_all_data |
| 13 | 24 Sep 2020 | 13 Nov 2025 (ended) | TLS 1.2 minimum by default; libpq channel_binding |
| 12 | 3 Oct 2019 | 21 Nov 2024 (ended) | GSSAPI encryption; clientcert=verify-full |
If you are on 14, the 12 November 2026 minor is your last community fix. Our PostgreSQL 14 end of life guide covers 14 specifics and target choice. If you are on 13 or older, you have already gone at least one quarter without community security fixes.
Step by step: audit versions, roles and passwords, then plan the upgrade
Run these as a superuser or a role with the predefined roles noted. Nothing here changes data until step 5, and each changing step says so.
Step 1: check server and client versions
# Client tools and libpq on this host
psql --version
pg_config --version # version the server binaries were built as, if installed
# Server version, from SQL
psql -X -A -t -c "SELECT version();"
psql -X -A -t -c "SHOW server_version_num;" # e.g. 160015 means 16.15
Compare the result with the "current minor" column on the versioning policy page. Then check the client side: application drivers that are not built on libpq (for example a JDBC driver or a pure language driver) have their own version and their own SCRAM support history, so list them from the application's lockfile, not from the database host.
-- Who is connecting, with what, and is it encrypted?
SELECT s.usename, s.application_name, s.client_addr, l.ssl, l.version AS tls_version
FROM pg_stat_activity s
LEFT JOIN pg_stat_ssl l ON l.pid = s.pid
WHERE s.backend_type = 'client backend';
Step 2: list roles that skip privileges or RLS
SELECT rolname, rolsuper, rolbypassrls, rolcreaterole, rolcreatedb, rolcanlogin
FROM pg_roles
WHERE rolsuper OR rolbypassrls OR rolcreaterole
ORDER BY rolsuper DESC, rolbypassrls DESC, rolname;
Any login role with rolsuper or rolbypassrls that an application uses is a finding. Then list who owns RLS tables, and whether FORCE is set:
SELECT n.nspname AS schema, c.relname AS table_name,
pg_get_userbyid(c.relowner) AS owner,
c.relrowsecurity AS rls_enabled,
c.relforcerowsecurity AS rls_forced
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','p') AND c.relrowsecurity
ORDER BY 1, 2;
If the application connects as the owner and rls_forced is false, the policies do not apply to it. Also check views over RLS tables, since they run as their owner unless created with security_invoker on 15 or later:
SELECT schemaname, viewname, viewowner FROM pg_views
WHERE schemaname NOT IN ('pg_catalog','information_schema');
Step 3: review pg_hba.conf from SQL
-- Available since PostgreSQL 10
SELECT line_number, type, database, user_name, address, auth_method, error
FROM pg_hba_file_rules
ORDER BY line_number;
Look for trust on anything other than a socket you fully control, password on non-TLS lines, md5 lines you plan to retire, and any error values (a rule that failed to parse). Remember the first matching line wins, so a broad trust rule above a strict one makes the strict one dead.
Step 4: find MD5 password hashes
-- Needs superuser: rolpassword is only readable from pg_authid
SELECT rolname,
CASE WHEN rolpassword LIKE 'SCRAM-SHA-256$%' THEN 'scram-sha-256'
WHEN rolpassword LIKE 'md5%' THEN 'md5'
WHEN rolpassword IS NULL THEN 'none'
ELSE 'other' END AS hash_type
FROM pg_authid
WHERE rolcanlogin
ORDER BY hash_type, rolname;
Step 5: move from MD5 to SCRAM (changes configuration)
The docs give the order: first make sure every client library in use supports SCRAM, then set password_encryption = 'scram-sha-256', have every user set a new password, and finally change pg_hba.conf methods to scram-sha-256.
-- 1. Confirm the setting (default is scram-sha-256 from 14 onward)
SHOW password_encryption;
ALTER SYSTEM SET password_encryption = 'scram-sha-256';
SELECT pg_reload_conf();
-- 2. Re-set each password. In psql, \password prompts and sends a pre-hashed value,
-- so the clear text never appears in server logs or history.
\password app_user
Because an md5 line in pg_hba.conf automatically uses SCRAM when the stored hash is SCRAM, you can rotate passwords role by role while the file still says md5, then flip the lines to scram-sha-256 once the Step 4 query shows no MD5 hashes left. On the client side, set libpq's require_auth=scram-sha-256 (16 and later) and channel_binding=require (13 and later, over TLS) for connections that must never fall back. Our MD5 to SCRAM migration guide covers driver compatibility in depth.
Step 6: check extensions against the server
SELECT e.extname, e.extversion AS installed, a.default_version AS available
FROM pg_extension e
JOIN pg_available_extensions a ON a.name = e.extname
WHERE e.extversion IS DISTINCT FROM a.default_version;
Rows here mean new extension packages are installed on disk but the SQL objects in the database were not updated. ALTER EXTENSION name UPDATE; updates them, after you have read that extension's own release notes.
Step 7: plan the major upgrade
You have two community supported routes besides a full dump and restore.
pg_upgrade (in place, fast). Install the new major's binaries, plus every extension's shared objects built for the new major, then initialize an empty new cluster. Run the check first; the docs say --check can run "even if the old server is still running" and will "outline any manual adjustments."
# Run as the postgres OS user. Paths are examples; adjust to your packaging.
/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 \
--check
Warning: the next step modifies data directories. Take and test a backup first, and confirm both --old-datadir and --new-datadir point where you think they do (ls them and check PG_VERSION in each) before you continue. Then repeat without --check, choosing a transfer mode: copy (the default, slowest, leaves the old cluster usable), --link (fast, but once the new server starts the old cluster must not be used), --clone, or in 18 --swap. In 18, pg_upgrade retains optimizer statistics, and its docs then suggest vacuumdb --all --analyze-in-stages --missing-stats-only followed by vacuumdb --all --analyze-only; on older targets, follow the statistics step in that version's pg_upgrade page. 18 also changed initdb to enable data checksums by default, and the notes say pg_upgrade requires matching checksum settings, so initialize with --no-data-checksums if the old cluster has none.
Logical replication (near zero downtime). Stand up the new major as a subscriber, let it catch up, then switch applications over. The docs note that pg_upgrade migrates logical slots and that "migration of logical replication clusters is possible only when all the members of the old logical replication clusters are version 17.0 or later." For older sources, the usual approach is to build fresh publications and subscriptions to the new major. Our logical versus physical backups guide explains the trade-offs behind both paths.
After either route: re-run Steps 2 to 6 on the new cluster. Upgrades preserve roles, grants and pg_hba.conf choices, including the bad ones.
Common mistakes
- trust left in pg_hba.conf. initdb defaults to trust for ease of installation. A cluster set up by hand and never revisited lets any local OS user connect as the superuser. Replace it with
peerfor local admin sockets andscram-sha-256elsewhere. - Extensions not upgraded with the server. Packages get updated, the SQL objects inside each database do not. After a minor or major upgrade, compare
pg_extensionwithpg_available_extensionsand runALTER EXTENSION ... UPDATEwhere the extension's notes allow it. For pg_upgrade, the new major's shared objects must be installed before you run it. - Minor releases skipped for months. Security fixes ship in minors. The project says running an old minor is riskier than upgrading. A server on 16.9 when 16.15 is current is six quarterly security releases behind.
- Old client libraries without SCRAM support. libpq gained SCRAM in 10, and drivers that do not use libpq added it on their own schedules. Switching pg_hba.conf before checking every client causes an outage, and the usual "fix" is reverting to md5. Inventory clients first.
- Assuming 15's public schema change applies to old databases. It applies to new databases. Upgraded ones keep
CREATEfor PUBLIC until you runREVOKE CREATE ON SCHEMA public FROM PUBLIC;. - Applications connecting as the table owner. RLS does not apply to owners unless FORCE is set. Use a separate, non-owner login role for the application.
- BYPASSRLS on the app role "for the migration." It never gets removed. Use a dedicated migration role instead.
- Views over RLS tables owned by privileged roles. On 15 and later, use
security_invoker; on older versions, design views so their owner is subject to the same policies. - Parsing version strings.
9.6.24and16.15mean different things. Useserver_version_num. - Treating 19 dates as final. As of 25 October 2026, 19 is at RC. Plan on the announced release, not the roadmap's target month.
Where Safeguard fits, honestly
Most of this post is database administration, and much of it sits outside any software composition tool. Safeguard does not audit pg_hba.conf, does not read role attributes or RLS policies, and does not patch a managed database service; your provider controls that service's minor versions and upgrade windows.
Where it does help is the software around the database. PostgreSQL usually reaches production inside a container image or as an OS package in an image, and applications talk to it through client libraries pulled in by npm, pip, Maven or Go modules. Safeguard scans repositories, container images and SBOM files (safeguard scan --dir ., --image <name>, --sbom <file>), matches components against vulnerability databases, and shows which projects contain a given component and version. That answers questions like "which of our images still ship a PostgreSQL 14 server package?" and "which services pin a driver release too old for SCRAM?" Policies and gates can then act on those findings in CI with Block, Warn or Notify actions, with exceptions and approvals for the cases you accept on purpose.
The database server is only part of the exposure. The client drivers, connection poolers, extensions and base images around PostgreSQL are open source dependencies you build and ship yourself, and they need the same quarterly discipline as the server.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo is a real Safeguard CLI session. It asks the CLI to fix a vulnerable dependency, and you watch it read the finding and advisory, edit the manifest, and run the install and tests, asking before any command that changes something. The same flow applies when a service's PostgreSQL driver is too old for SCRAM or carries a known vulnerability: you ask for the upgrade, review the change, and nothing is pushed for you.
Click through it at your own pace, or open it full screen.
The second demo sets up the Safeguard runner on your own machine, approves it with a device code, and scans from the terminal. For a database team, that is the quick way to scan the container images and Compose files that bring up PostgreSQL and its poolers, and see which images still carry an end of life server package.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and chooses which tools Claude can use. Once connected, you can ask in plain language which projects include a given PostgreSQL client library version, and get the answer from your own scan data rather than from memory.
Click through it at your own pace, or open it full screen.
To try it on your own repositories and images, download Safeguard and follow the CLI docs. For a broader server checklist, see our database hardening checklist for PostgreSQL, MySQL, MongoDB and Redis.
FAQ
How long is a PostgreSQL version supported?
Five years from its first release. The versioning policy says that after five years "a final minor version will be released and the software will then be unsupported." In practice the final release is the quarterly minor in November (or February for majors released in winter) after the five year mark. For example, 15 was released on 13 October 2022 and its final release is listed as 11 November 2027.
What is the latest PostgreSQL version?
On 25 October 2026 the latest released major is 18, released on 25 September 2025, with 18.6 as its current minor. PostgreSQL 19 RC 1 was announced on 15 October 2026 and the roadmap targets October 2026 for the final release, so check postgresql.org for 19's actual release date.
When does PostgreSQL 14 reach end of life?
The versioning policy lists 12 November 2026 as 14's final release. That matches the November quarterly minor on the roadmap. After that date, 14 receives no community bug or security fixes.
Which PostgreSQL version added SCRAM-SHA-256?
PostgreSQL 10, released on 5 October 2017, added SCRAM-SHA-256 authentication and password storage. PostgreSQL 14 made scram-sha-256 the default for password_encryption, so new passwords are hashed with SCRAM unless you change the setting.
Is MD5 password authentication deprecated in PostgreSQL?
Yes. PostgreSQL 18 deprecated MD5 passwords and says support "will be removed in a future major version release." CREATE ROLE and ALTER ROLE warn when you set an MD5 password, controlled by md5_password_warnings. The draft 19 notes add a warning after each successful MD5 login.
Which PostgreSQL version introduced row-level security?
9.5, released on 7 January 2016, introduced row-level security with CREATE POLICY and ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgreSQL 10 added restrictive policies, and 15 added security invoker views so views over RLS tables can apply the caller's policies.
Does row-level security apply to the table owner and superusers?
Not by default. Superusers and roles with BYPASSRLS always bypass it. Table owners bypass it unless you run ALTER TABLE ... FORCE ROW LEVEL SECURITY. Connect applications with a separate role that is neither.
Can I skip versions when upgrading PostgreSQL?
Yes. The policy says you can go from one major straight to another "without upgrading to intervening versions," but recommends reading the release notes of every major in between. pg_upgrade and logical replication both support direct jumps, within the limits in their docs.
Does PostgreSQL support OAuth or single sign-on?
PostgreSQL 18 added an oauth method that validates OAuth 2.0 bearer tokens through a validator library you configure. PostgreSQL does not include an authorization server, so you use your identity provider. GSSAPI, SSPI, LDAP, RADIUS and certificate authentication remain available for other single sign-on setups.
Where does PostgreSQL publish security vulnerabilities?
On its security page at postgresql.org/support/security, which lists each known vulnerability with affected majors, the fixing minor, whether a login is required, and a CVSS score. Security releases are also announced on the pgsql-announce mailing list, which you can filter to the Security tag.
Quick check
Your PostgreSQL 13 cluster was upgraded to 16 with pg_upgrade. Which passwords are hashed with SCRAM?
Only passwords set after the upgrade (or set on 13 while password_encryption was changed to SCRAM). pg_upgrade copies existing MD5 hashes as they are. Run the pg_authid query in Step 4 and re-set every MD5 password.
An application connects as the role that owns its tables. RLS is enabled with a tenant policy. Is tenant isolation enforced?
No, unless the table also has FORCE ROW LEVEL SECURITY. Table owners normally bypass row security. The safer fix is a separate application role that does not own the tables and lacks BYPASSRLS.
Sources
- PostgreSQL: Versioning Policy
- PostgreSQL: Developer roadmap and minor release schedule
- PostgreSQL: Security Information
- PostgreSQL release notes index
- PostgreSQL docs: A Brief History of PostgreSQL
- PostgreSQL docs: Password Authentication
- PostgreSQL docs: The pg_hba.conf File
- PostgreSQL docs: Authentication Methods
- PostgreSQL docs: Trust Authentication
- PostgreSQL docs: OAuth Authorization/Authentication
- PostgreSQL docs: initdb
- PostgreSQL docs: Row Security Policies
- PostgreSQL docs: CREATE POLICY
- PostgreSQL docs: CREATE VIEW
- PostgreSQL docs: Predefined Roles
- PostgreSQL docs: Schemas
- PostgreSQL docs: pg_upgrade
- PostgreSQL docs: pg_dump
- PostgreSQL docs: psql
- PostgreSQL docs: Upgrading a PostgreSQL Cluster
- PostgreSQL docs: Logical replication upgrade
- Release notes: PostgreSQL 6.5, 7.0, 7.1, 7.3, 8.1, 8.4
- Release notes: PostgreSQL 9.0, 9.1, 9.2, 9.4, 9.5, 9.6
- Release notes: PostgreSQL 10, 11, 12, 13, 14, 15
- Release notes: PostgreSQL 16, 17, 18, 19 (draft)
Last reviewed: 25 October 2026. Dates and support windows are from postgresql.org on that day; check the versioning policy page for changes, and the 19 release announcement for its final date.