Safeguard
Supply Chain Security•

PostgreSQL Release History: SCRAM, Row-Level Security and Support Dates

PostgreSQL ships a major version yearly with five years of fixes. Every version's dates, when SCRAM, row-level security and OAuth landed, what to check.

Safeguard Security Team
42 min read

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 releaseNotes
12 November 2026Final scheduled release for PostgreSQL 14 (policy page lists 14's final release as 12 November 2026)
11 February 2027First quarterly release with 15 to 19 (assuming 19 is final by then)
13 May 2027Regular quarterly
12 August 2027Regular 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.

MajorFirst releaseFinal release (end of life)Current or final minorSupported on 25 Oct 2026
19Not yet released (RC 1 on 15 Oct 2026)Not yet publishedn/aRelease candidate
1825 September 202514 November 203018.6Yes
1726 September 20248 November 202917.11Yes
1614 September 20239 November 202816.15Yes
1513 October 202211 November 202715.19Yes
1430 September 202112 November 202614.24Yes, ends in weeks
1324 September 202013 November 202513.23No
123 October 201921 November 202412.22No
1118 October 20189 November 202311.22No
105 October 201710 November 202210.23No
9.629 September 201611 November 20219.6.24No
9.57 January 201611 February 20219.5.25No
9.418 December 201413 February 20209.4.26No
9.39 September 20138 November 20189.3.25No
9.210 September 20129 November 20179.2.24No
9.112 September 201127 October 20169.1.24No
9.020 September 20108 October 20159.0.23No
8.41 July 200924 July 20148.4.22No
8.34 February 20087 February 20138.3.23No
8.25 December 20065 December 20118.2.23No
8.18 November 20058 November 20108.1.23No
8.019 January 20051 October 20108.0.26No
7.417 November 20031 October 20107.4.30No
7.327 November 200227 November 20077.3.21No
7.24 February 20024 February 20077.2.8No
7.113 April 200113 April 20067.1.3No
7.08 May 20008 May 20057.0.3No
6.59 June 19999 June 20046.5.3No
6.430 October 199830 October 20036.4.2No
6.31 March 19981 March 20036.3.2No

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).

VersionYearSecurity relevant changes (from the release notes)
7.02000Password authentication enabled and required for new users; unprivileged users can change their own passwords; initdb can prompt for a superuser password
7.12001New OpenSSL configuration option; Kerberos V fixes
7.32002Schemas 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.12005Roles replace users and groups; roles can be members of roles; SET ROLE
8.42009SSL client certificates as a user authentication method; certificate chains in the server certificate file; column-level privileges
9.02010RADIUS authentication; LDAP improvements; passwordcheck module; GRANT/REVOKE IN SCHEMA; ALTER DEFAULT PRIVILEGES
9.12011SECURITY LABEL command and the sepgsql module for SELinux integration
9.22012security_barrier views; LEAKPROOF function attribute; privileges on data types
9.42014Native Kerberos removed in favour of GSSAPI; ECDH key exchange; better default ssl_ciphers; server cipher order preferred by default
9.52016Row-level security (CREATE POLICY, ENABLE ROW LEVEL SECURITY); certificate Subject Alternative Name checks; pg_stat_ssl view; SSL renegotiation removed
9.62016Role 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
102017SCRAM-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
112018SCRAM channel binding (server side); LDAPS; pg_read_server_files, pg_write_server_files, pg_execute_server_program roles
122019GSSAPI encryption of TCP connections (hostgssenc); clientcert=verify-full for certificate plus password; ssl_min_protocol_version and ssl_max_protocol_version
132020Default minimum TLS version raised from 1.0 to 1.2; libpq channel_binding parameter; trusted extensions installable by non-superusers
142021password_encryption defaults to scram-sha-256; pg_read_all_data, pg_write_all_data, pg_database_owner; "default roles" renamed "predefined roles"; clientcert overhaul
152022PUBLIC 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
162023CREATEROLE restricted (ADMIN OPTION required); pg_create_subscription; scram_iterations; libpq require_auth; pg_use_reserved_connections
172024MAINTAIN privilege and pg_maintain role; safe search_path during maintenance operations; direct TLS negotiation (sslnegotiation=direct)
182025OAuth 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
192026 (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.

MethodWhat it doesSince (notable change)Password on the wireUse it for
trustAllows anyone who can connect, as any role, with no checkOriginal; still the initdb defaultn/aNothing reachable by untrusted users
rejectRefuses the connectionOriginaln/aExplicit deny rules near the top of the file
passwordSends the password in clear textOriginalClear textOnly inside TLS, and better avoided
md5Custom challenge-response over MD5 hashesOld; deprecated in 18Not sent in clearLegacy only; switches to SCRAM automatically if the stored hash is SCRAM
scram-sha-256RFC 7677 challenge-response, salted and iterated storage10; default hashing since 14Not sent; resists sniffingThe default choice for password logins
certTLS client certificate identifies the user8.4No passwordService to service, strong machine identity
gssGSSAPI (Kerberos)GSSAPI encryption in 12No passwordKerberos estates
sspiWindows single sign-onLong standingNo passwordWindows domains
ldapChecks the password against a directoryImproved in 9.0; LDAPS in 11Password reaches the serverDirectory-backed logins, always over TLS
radiusChecks the password against RADIUS9.0Password reaches the serverExisting RADIUS estates
pamDelegates to PAMLong standingPassword reaches the serverOS-integrated setups
peer, identUses the OS user name (local) or ident serverLong standingn/aLocal admin access via Unix sockets
oauthValidates an OAuth 2.0 bearer token via a validator library18Token, not passwordCentral 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-local and --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: md5 through 13, scram-sha-256 from 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

  1. ALTER TABLE t ENABLE ROW LEVEL SECURITY; turns it on. From that moment, normal access must be allowed by a policy.
  2. If RLS is enabled and no policy applies, a default deny is used: no rows are visible or modifiable.
  3. CREATE POLICY defines a USING expression (which existing rows are visible or affected) and a WITH CHECK expression (which new or updated rows are allowed). If you give only USING, it is reused as WITH CHECK.
  4. Policies can target specific commands (SELECT, INSERT, UPDATE, DELETE, or ALL) and specific roles.
  5. 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 UPDATE lets 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.

VersionReleasedEnd of lifeKey security change to plan around
1825 Sep 202514 Nov 2030OAuth authentication; MD5 passwords deprecated
1726 Sep 20248 Nov 2029MAINTAIN privilege and pg_maintain; safe search_path in maintenance; direct TLS
1614 Sep 20239 Nov 2028CREATEROLE narrowed; require_auth in libpq; scram_iterations
1513 Oct 202211 Nov 2027PUBLIC CREATE removed from public schema; security_invoker views
1430 Sep 202112 Nov 2026SCRAM becomes default password hashing; pg_read_all_data and pg_write_all_data
1324 Sep 202013 Nov 2025 (ended)TLS 1.2 minimum by default; libpq channel_binding
123 Oct 201921 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 peer for local admin sockets and scram-sha-256 elsewhere.
  • 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_extension with pg_available_extensions and run ALTER EXTENSION ... UPDATE where 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 CREATE for PUBLIC until you run REVOKE 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.24 and 16.15 mean different things. Use server_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 situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull 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

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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.