Skip to main content

PostgreSQL 19: Security Changes and Preparing with Atlas

· 13 min read
Noa Rogoszinski
Noa Rogoszinski
DevRel Engineer

PostgreSQL 19 is scheduled for general availability on November 19, 2026, one week after PostgreSQL 14 reaches end of life on November 12. From then on, servers on PostgreSQL 14 get no more security fixes. Versions 15 through 18 are still supported, but version 19 will be supported the longest, into late 2031.

This post covers what version 19 changes for security and the schema objects that block an upgrade to it, and how to use the week before GA to prepare with Atlas: scan your servers for their versions and CVEs, keep upgrade blockers out with schema rules, and apply the same schema, roles, and grants to the new instance from code.

Support Windows​

The PostgreSQL project supports each major version for five years. Once version 19 is released, the windows are:

VersionFinal release
14November 12, 2026
15November 11, 2027
16November 9, 2028
17November 8, 2029
18November 14, 2030
19Late 2031

A server on PostgreSQL 14 is the urgent case: CVEs published from now on will not be fixed for it, and standards such as PCI DSS and SOC 2 expect software that receives security updates. Servers on versions 15 through 18 keep getting fixes, so for them, the case for upgrading to version 19 is the security hardening below and a longer runway before the next forced upgrade.

What PostgreSQL 19 Changes for Security​

  • RADIUS authentication is removed. PostgreSQL only supported RADIUS over UDP, which the release notes call "unfixably insecure". Move radius entries in pg_hba.conf to another method before upgrading.
  • MD5 passwords, deprecated in PostgreSQL 18, now log a warning after each successful MD5 login. The md5_password_warnings setting turns it off, but the fix is to move roles to SCRAM-SHA-256.
  • The new password_expiration_warning_threshold setting warns a user whose password is about to expire. The default is seven days.
  • Server-side SNI lets one server present a different certificate per hostname, configured in the new pg_hosts.conf file.
  • Database, role, and tablespace names can no longer contain carriage returns or line feeds, a change made to avoid security problems.
  • OAuth, added in PostgreSQL 18, gains custom pg_hba.conf options for validators, error details on failed logins, and the oauth_ca_file connection setting in libpq for a custom certificate authority.
  • log_lock_waits is on by default.

Outside security, PostgreSQL 19 adds REPACK, which replaces VACUUM FULL and CLUSTER and can rebuild a table without blocking reads and writes with REPACK (CONCURRENTLY). Logical replication can be enabled without a restart when wal_level is replica, and now replicates sequence values. The new WAIT command gives read-your-writes consistency on standbys. ALTER TABLE ... ALTER CONSTRAINT ... [NOT] ENFORCED now works on CHECK constraints, not only foreign keys.

Where Atlas Fits​

Atlas manages database schemas as code. You define your target schema in HCL, SQL, or your ORM, and Atlas plans the migration from the database's current state. It applies the change declaratively, like Terraform, or generates versioned migration files for you to review and commit. One definition serves every environment, from local development through staging and production, and every database you configure.

On PostgreSQL, that schema spans tables, views, functions, triggers, row-level security policies, and more (see supported features for the full list). It also covers who can reach them: with security as code, roles, users, and grants live in the same files and go through the same review as the tables they protect. With the whole schema in one place, the lineage graph traces each column through the tables and views that depend on it.

The same tooling checks changes before and after they ship. atlas migrate lint reports destructive and table-locking changes in CI, atlas schema test and atlas migrate test run tests against the schema and its migrations, drift detection catches changes made outside the pipeline, and atlas security scan checks the running server for CVEs.

For a PostgreSQL upgrade, this means the schema the new version has to receive is already written down, and the steps below use it.

Check Your Servers' Versions and CVEs​

Checking one server takes a query:

SELECT version();

Across staging, production, and every region, that is one query per database. atlas security scan checks them in one run. Pass --url once per database:

atlas security scan \
--url "postgres://postgres:pass@staging.example.com:5432/app?sslmode=require" \
--url "postgres://postgres:pass@prod.example.com:5432/app?sslmode=require" \
--fail-on HIGH

The report lists every database with its server version, so a server still on PostgreSQL 14 stands out. For each one, Atlas matches the server version and installed extensions against the CVEs in the Atlas Security Graph, and reports every CVE that affects it along with the version that fixes it. Run it with a read-only user.

note

The scan reports published CVEs, not support status. A server on the final PostgreSQL 14 release with no open CVEs reports no issues, so compare the versions in the report against the support windows above.

The CVE check is what keeps the scan useful after the upgrade. New CVEs are published for supported versions too, and a server is reported until it runs a release that carries the fix. To catch them as they are published, run the scan on a schedule from CI or the Kubernetes Operator.

atlas security scan is available to Atlas Pro users with the Security Graph enabled in their plan. Run atlas login to get started with a free trial.

Check for Upgrade Blockers​

Upgrading to PostgreSQL 19 from any earlier major version takes pg_upgrade, a dump and restore, or logical replication. On a self-managed server, pg_upgrade --check reports every blocker without changing the cluster. On a managed service, the provider runs its own pre-checks. Three changes in version 19 live in the schema, and the queries below run on the version you have today, so they can be cleared before version 19 ships.

btree_gist indexes on inet and cidr​

The btree_gist operator classes for inet and cidr can exclude rows that a query should return. In PostgreSQL 19, the default GiST operator class for these types is the built-in inet_ops, and pg_upgrade rejects a cluster that has a btree_gist index on them. These indexes are common in exclusion constraints on IP ranges. To find them:

SELECT n.nspname AS schema, c.relname AS index, opc.opcname AS opclass
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_opclass opc ON opc.oid = ANY (i.indclass::oid[])
WHERE opc.opcname IN ('gist_inet_ops', 'gist_cidr_ops');

Rebuild each one with inet_ops before upgrading. If Atlas manages the schema, set the operator class on the index and let Atlas plan the rebuild:

schema.pg.hcl
table "sessions" {
schema = schema.public
column "client_ip" {
type = inet
}
index "sessions_client_ip_idx" {
type = GIST
on {
column = column.client_ip
ops = inet_ops
}
}
}

Rebuilding an index on a large table blocks writes while it runs. Set concurrent_index in the diff policy and Atlas creates and drops the index with CONCURRENTLY. In versioned migrations, PG101 reports an index created without it.

Names with line breaks​

Database, role, and tablespace names that contain a carriage return or a line feed block pg_upgrade. Rename them first:

SELECT 'role' AS kind, rolname AS name FROM pg_roles WHERE rolname ~ '[\r\n]'
UNION ALL
SELECT 'database', datname FROM pg_database WHERE datname ~ '[\r\n]'
UNION ALL
SELECT 'tablespace', spcname FROM pg_tablespace WHERE spcname ~ '[\r\n]';

Rename a role with ALTER ROLE ... RENAME TO, and update its name in the schema file. To keep new ones out, add a schema rule that checks the names of roles and users:

schema.rule.hcl
predicate "role" "no_line_breaks" {
name {
match = "^[^\r\n]+$"
}
}

predicate "user" "no_line_breaks" {
name {
match = "^[^\r\n]+$"
}
}

rule "schema" "role-names" {
description = "Role names must not contain line breaks"
role {
assert {
predicate = predicate.role.no_line_breaks
message = "role ${self.name} must not contain a carriage return or line feed"
}
}
}

rule "schema" "user-names" {
description = "User names must not contain line breaks"
user {
assert {
predicate = predicate.user.no_line_breaks
message = "user ${self.name} must not contain a carriage return or line feed"
}
}
}

Include the file in the lint block of atlas.hcl, and atlas schema lint and atlas migrate lint report any role or user that breaks it, so the name is caught in review instead of at the next upgrade:

atlas.hcl
lint {
rule "hcl" "custom-rules" {
src = ["schema.rule.hcl"]
}
}

The rule covers role and user names only. Atlas does not manage databases or tablespaces, so the query above is the only check for their names.

Schema rules are available to Atlas Pro users. Run atlas login to get started with a free trial.

Dumps and encodings​

standard_conforming_strings is always on in PostgreSQL 19. A dump taken by a pg_dump older than version 19 with the setting off does not load correctly, so take dumps with the version 19 client tools. The MULE_INTERNAL encoding is removed, and a database that uses it has to be dumped and restored with another encoding.

When Atlas applies the schema from code, as shown in the next section, the schema does not go through pg_dump at all. The new instance gets its tables, roles, and grants from the same files as every other environment, and only the data needs a dump or replication.

Prepare the Schema Move with Atlas​

An in-place upgrade keeps the schema where it is. Many upgrades instead build a new instance on PostgreSQL 19, copy the data over, and switch traffic: a blue-green deployment, a logical replication cutover, or a managed service that does not upgrade major versions in place. Databricks Lakebase, for example, upgrades to a new major version only through a new project.

On those paths, the schema has to exist on the new instance before any data arrives. Logical replication copies rows, not DDL, so every table must already be on the subscriber, and roles and grants are not copied either. When the schema is defined as code, one config describes both instances:

atlas.hcl
env "pg18" {
url = getenv("PG18_URL")
dev = "docker://postgres/18/dev"
schema {
src = "file://schema.pg.hcl"
mode {
roles = true
permissions = true
}
}
}

env "pg19" {
url = getenv("PG19_URL")
dev = "docker://postgres/19/dev"
schema {
src = "file://schema.pg.hcl"
mode {
roles = true
permissions = true
}
}
}

PG18_URL and PG19_URL hold each instance's connection URL, such as postgres://user:pass@host:5432/app?sslmode=require. Leave out search_path so the URL covers every schema in the database, the same scope as the dev databases.

The dev database runs the same major version as its target, so Atlas plans and validates each change on the version it will run on. Until version 19 is released, point the pg19 environment at a staging server running RC 2, and set its dev to an empty database on an RC 2 server, to rehearse the move this week and repeat it on the GA release. Apply the schema to the new instance:

atlas schema apply --env pg19

With roles and permissions on, Atlas creates the roles, memberships, and grants along with the tables, indexes, functions, and row-level security policies. Declare roles that Atlas must leave alone, such as the provider's admin roles, with external = true, as described in Managing Roles and Permissions. On a managed service, the dev database also needs the provider's roles, created with a baseline script as shown in Dev Database for Cloud Environments. If the schema sets user passwords, add sensitive = ALLOW to the mode block.

Before switching traffic, check that both instances match the code:

atlas schema diff --env pg18 --from env://url --to env://schema.src
atlas schema diff --env pg19 --from env://url --to env://schema.src

Each prints Schemas are synced, no changes to be made. when the instance matches. A schema change merged while replication runs is applied to both environments from the same file, so the two stay in step until cutover. Teams using versioned migrations run atlas migrate apply against the new instance instead, and can run atlas migrate validate with a version 19 dev database, which executes every migration file on PostgreSQL 19 before the upgrade.

PostgreSQL Inside the Data Platforms​

PostgreSQL is no longer only a standalone service. The data platforms now run it next to their analytical engines:

  • Snowflake Postgres became generally available on February 24, 2026, with PostgreSQL instances created and managed from Snowflake and mirrored into Snowflake tables.
  • Databricks Lakebase is a managed PostgreSQL service in a Databricks workspace, supporting PostgreSQL 16, 17, and 18.
  • ClickHouse Managed Postgres became generally available on AWS on November 9, 2026, with change data capture into ClickHouse.

Each platform decides when it offers PostgreSQL 19, but every one of them exposes a standard PostgreSQL endpoint, so the upgrade steps above apply. A team on one of these platforms now runs two schemas side by side: the PostgreSQL schema that the application writes to, and the warehouse schema that the data lands in. A change to one often needs a change to the other.

Atlas manages both from the same project. It connects to the PostgreSQL service with a postgres:// URL, and to Snowflake, Databricks, and ClickHouse with their own drivers. Both sides get the same workflow: schema as code, migration linting in CI, drift detection, and security scanning. For Lakebase specifics such as OAuth tokens and its managed functions, see the Lakebase guide. For other managed services, see Managed PostgreSQL.

Wrapping Up​

PostgreSQL 19 is a week away, and its security changes make it worth the move, especially for servers left on PostgreSQL 14. An upgrade goes smoothly when it holds no surprises: you know which servers run which version, the blockers are cleared before the cutover window, and the new instance gets exactly the schema the old one had.

Atlas covers that ground. With the schema, roles, and grants defined as code, it scans your servers for CVEs, catches problem changes in review, and applies the same schema to every instance and version you run, whether that is a standalone PostgreSQL server or one inside Snowflake, Databricks, or ClickHouse.

To manage your PostgreSQL schemas with Atlas, follow the PostgreSQL guide, or schedule a demo.