In November 2024, PostgreSQL shipped a patch for CVE-2024-10978, a privilege escalation bug affecting all supported major versions. A user with SET ROLE permission could execute queries as a more privileged role than intended. It was patched in 17.1, 16.5, 15.9, 14.14, and 13.17. If your app is still on an older minor version, anyone with limited database access may have already escalated their privileges.
That's the kind of vulnerability PostgreSQL takes seriously. The project has a strong security track record and a dedicated security team. The real danger with Postgres isn't the database engine itself. It's the five lines in pg_hba.conf that your cloud provider set four years ago and no one has looked at since.
TL;DR
PostgreSQL is safe when configured correctly. The engine is mature, actively patched, and used by companies of every size. The risks are almost always configuration: trust auth in pg_hba.conf, port 5432 open to the internet, superuser with no password, outdated minor versions, and no data-at-rest encryption check. Fix those five and you've closed the gap.
Our Verdict
What PostgreSQL Gets Right
- 30+ years of active development and security patches
- Dedicated security team with responsible disclosure process
- Fine-grained role-based access control built in
- SSL/TLS support for connections out of the box
- Row-level security (RLS) for multi-tenant data isolation
- Extensive audit logging via pgaudit extension
- Major cloud managed services (RDS, Cloud SQL, Supabase, Neon) handle patching
Where Deployments Go Wrong
- Default pg_hba.conf uses
trustauth on some distros - Port 5432 gets left open to 0.0.0.0 during provisioning
- The
postgressuperuser often has no password on fresh installs - Data at rest encryption is not built in (relies on OS/storage layer)
- Connection string with credentials ends up in source control
- No automatic patching on self-managed installs
The pg_hba.conf Problem
pg_hba.conf (host-based authentication) is the file that decides who can connect to your database and whether they need a password. The dangerous entry looks like this:
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
host all all 127.0.0.1 trust
trust means: connect with no password, as any user, including postgres (the superuser). Some distributions ship this config by default. If your server has any other services running (a web app, a background worker, a compromised dependency), any of them can connect to Postgres as superuser with zero authentication.
The fix is two lines:
# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
local all all scram-sha-256
host all all 127.0.0.1 scram-sha-256
host all all ::1/128 scram-sha-256
scram-sha-256 is the correct method for password authentication, and it has been available since PostgreSQL 10. Do not reach for md5 as a fallback. PostgreSQL's own documentation carries a deprecation warning on it: "Support for MD5-encrypted passwords is deprecated and will be removed in a future release of PostgreSQL." The docs are blunt about why, calling md5 "a custom less secure challenge-response mechanism" that "provides no protection if an attacker manages to steal the password hash from the server."
If you inherited an md5 installation, the migration is three steps: set password_encryption = 'scram-sha-256' in postgresql.conf, have every user set a new password (the old hashes cannot be converted), then switch the pg_hba.conf methods. Check what you are on right now:
SELECT rolname,
CASE WHEN rolpassword LIKE 'SCRAM-SHA-256%' THEN 'scram'
WHEN rolpassword LIKE 'md5%' THEN 'md5'
ELSE 'other' END AS hash_type
FROM pg_authid
WHERE rolcanlogin;
And never use trust on any connection type in production.
Port 5432 on Shodan
Hunters' security research team ran a Postgres honeypot in 2024 and, while scoping it, counted what was already sitting in the open. Their number: "we stumbled upon 1,310,544 instances of Postgres databases with their default ports open to the internet." That is not 1.3 million deliberate decisions. It is databases provisioned in a hurry, where someone opened the port for a debug session and never closed it, or where a cloud security group defaulted to 0.0.0.0/0 and nobody read it back.
PostgreSQL is not designed to be a public-facing service. It has no built-in rate limiting, no brute-force protection on connection attempts, and no web application firewall layer. The correct architecture:
- Cloud managed (RDS, Cloud SQL, Supabase, Neon): Keep the database in a private subnet. Connect through your app server or a VPN. Never add a public endpoint unless you have an explicit reason.
- Self-hosted: Bind PostgreSQL to
127.0.0.1inpostgresql.conf(listen_addresses = 'localhost'). Use SSH tunnels or a bastion host for remote DBA access.
Check right now: grep listen_addresses /etc/postgresql/*/main/postgresql.conf. If it shows '*' or your server's public IP, PostgreSQL is accepting connections from anywhere.
The Minor Version You're On Matters More Than Any Single CVE
PostgreSQL ships minor releases at least once every three months, and they contain only bug fixes and security patches. Upgrading a minor version is the cheapest security work available to you: no migration, no schema change, usually a package upgrade and a restart.
Check what you're on:
SELECT version();
-- Returns something like:
-- PostgreSQL 18.6 on x86_64-pc-linux-gnu...
Here is the state of play as of September 2026, straight from PostgreSQL's versioning policy:
| Major Version | Current Minor | Supported Until |
|---|---|---|
| PostgreSQL 18 | 18.6 | November 14, 2030 |
| PostgreSQL 17 | 17.11 | November 8, 2029 |
| PostgreSQL 16 | 16.15 | November 9, 2028 |
| PostgreSQL 15 | 15.19 | November 11, 2027 |
| PostgreSQL 14 | 14.24 | November 12, 2026 |
Two rows on that table deserve a second look.
PostgreSQL 13 is end-of-life. It stopped receiving patches on November 13, 2025, and its final release was 13.23. If SELECT version() returns anything starting with 13, you are not "a few patches behind", you are on a branch that will never be fixed again. Every vulnerability disclosed since that date is permanent on your server. The same goes for 12 (EOL November 21, 2024) and everything older.
PostgreSQL 14 has about six weeks left. Its final release lands November 12, 2026. If you're on 14, a major-version upgrade belongs on this quarter's plan, not next year's.
Why the CVE you've heard of is the wrong thing to chase
A lot of Postgres security writing, including an earlier version of this page, pins itself to one CVE. That ages badly, and it also gets the risk wrong. Take CVE-2024-10978, which got a round of attention in late 2024. The accurate description is narrower than the summaries suggested: it's an incorrect privilege assignment, scored CVSS 4.2 (medium), where an application using SET ROLE or SET SESSION AUTHORIZATION could have a query react to the wrong user ID and so return or change different rows than intended. PostgreSQL's advisory is explicit that "the attacker cannot control which incorrect user ID applies." It's a real bug. It is not remote code execution, and it was never the reason to patch.
The reasons to patch look like this instead:
- CVE-2025-1094 (CVSS 8.1, fixed 2025-02-13 in 17.3, 16.7, 15.11, 14.16 and 13.19): libpq's quoting functions, the ones you rely on to make input safe, failed to neutralize quoting syntax during failed encoding validation. An input provider could achieve SQL injection through a function whose entire job is preventing that.
- The 2026 advisories, fixed across the current minors, include more than a dozen rated CVSS 8.8: mostly heap buffer overflows and type confusion bugs that end in arbitrary code execution.
to_char,regexp,pg_dump,plperland therefintcontrib module are all on that list. A few of them are fixed only in 18.6.
You don't need to track any of them. That's the point. Run the current minor for your major version and you get all of it at once. Fall behind by a year and you're carrying a dozen code-execution bugs in a database that holds every row your product has.
On managed Postgres (RDS, Cloud SQL, Supabase, Neon), minor patching is handled for you, but the timing is usually yours to configure. Go and read your maintenance window setting rather than assuming an upgrade already happened. "Managed" means someone else runs the upgrade, not that it ran today.
Role-Based Access and the Least Privilege Rule
Most apps connect to PostgreSQL with a superuser. This is the equivalent of running your web server as root. If your app's database credentials leak or get injected, the attacker has full access to every database on the server, the ability to read server files, and the ability to create new users.
Create a dedicated application user with only the permissions your app needs:
-- Create app user with a strong password
CREATE USER myapp_user WITH PASSWORD 'use-a-real-random-password-here';
-- Grant only what the app needs
GRANT CONNECT ON DATABASE myapp TO myapp_user;
GRANT USAGE ON SCHEMA public TO myapp_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO myapp_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO myapp_user;
-- For future tables
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO myapp_user;
Your app should never connect as postgres. The superuser should only be used for schema migrations and DBA tasks.
Data at Rest and Connection Security
PostgreSQL does not encrypt data at rest. Your data sits in plaintext on disk. This matters if:
- Someone gets physical or filesystem access to the server
- You're on a cloud provider and the storage volume isn't encrypted
- You export backups to S3 without server-side encryption
Most major cloud providers encrypt EBS/storage volumes by default, but verify this explicitly. For sensitive fields (SSNs, health data, payment tokens), use pgcrypto for column-level encryption as a defense-in-depth layer.
For connections, make sure SSL is required:
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
And in your connection string, add ?sslmode=require or ?sslmode=verify-full if you have a CA certificate.
Hardening Checklist
PostgreSQL Security Hardening
Is PostgreSQL safe to use in production?
Yes, PostgreSQL's core engine is mature and well-audited. The risk comes from misconfiguration: default trust authentication in pg_hba.conf, port 5432 exposed to the internet, superuser passwords left blank, and outdated versions missing security patches. Address those four and you've removed 90% of the real-world risk.
What PostgreSQL version should I be running in 2026?
PostgreSQL 18 is the current major version, and the supported minor releases as of September 2026 are 18.6, 17.11, 16.15, 15.19 and 14.24. PostgreSQL 13 reached end-of-life on November 13, 2025 and PostgreSQL 14 follows on November 12, 2026, so anything on 13 or below receives no security patches at all. Run the current minor for whatever major version you are on. Minor releases contain only bug fixes and security patches, so the upgrade is low risk and it is the cheapest security work available to you.
Is port 5432 safe to expose to the internet?
No. PostgreSQL is not designed to sit directly on the public internet. Use a private VPC, a bastion host, or an SSH tunnel for remote access. Hunters' security research team counted 1,310,544 Postgres instances with their default ports open to the internet in 2024, most of them unintentionally exposed.
What is pg_hba.conf and why does it matter?
pg_hba.conf (host-based authentication) controls who can connect to PostgreSQL and how they authenticate. The dangerous setting is 'trust', which allows connections without a password. Default installations on some Linux distros ship with 'trust' for local socket connections, meaning any process on the same server can connect as any database user including the superuser.
Does PostgreSQL encrypt data at rest?
PostgreSQL itself does not encrypt data at rest. You need full-disk encryption at the OS or storage level (most cloud providers enable this by default) or a column-level encryption extension like pgcrypto for specific sensitive fields. Verify your cloud provider's encryption settings rather than assuming it's active.
Using PostgreSQL?
Scan your app for exposed credentials, missing encryption headers, and common database misconfigurations.