Skip to content

Last updated: 2026-06-25

PostgreSQL TLS/SSL Configuration

PostgreSQL uses OpenSSL for TLS, and the configuration splits across two files: postgresql.conf (TLS settings) and pg_hba.conf (which connection types require TLS). PostgreSQL 16 or later is recommended; ssl_min_protocol_version is available in PostgreSQL 12 and later.

Certificate Setup

Put your server certificate, private key, and CA certificate somewhere the postgres user can read, then lock down the private key:

chmod 600 /etc/postgresql/ssl/server.key
chown postgres:postgres /etc/postgresql/ssl/server.key

PostgreSQL refuses to start if the private key has group or world read permissions.

postgresql.conf

The config file location varies by distribution:

  • Debian/Ubuntu: /etc/postgresql/<version>/main/postgresql.conf
  • RHEL: /var/lib/pgsql/<version>/data/postgresql.conf
  • SLES: /var/lib/pgsql/data/postgresql.conf

Add these settings:

# TLS
ssl = on
ssl_min_protocol_version = 'TLSv1.2'
ssl_ciphers = 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305'
ssl_prefer_server_ciphers = on
ssl_ecdh_curve = 'prime256v1'

# Certificate files
ssl_cert_file = '/etc/postgresql/ssl/server.crt'
ssl_key_file  = '/etc/postgresql/ssl/server.key'
ssl_ca_file   = '/etc/postgresql/ssl/ca.crt'

Two version-specific notes:

  • PostgreSQL 15+: ssl_ecdh_curve accepts a colon-separated list ('prime256v1:secp384r1'). Earlier versions accept only a single curve.
  • PostgreSQL 16+: ssl_prefer_server_ciphers has no effect on TLS 1.3 connections; TLS 1.3 always uses client preference. It still applies to TLS 1.2, so leaving it on is harmless.

pg_hba.conf

Use hostssl instead of host for remote connections. host accepts both encrypted and unencrypted connections; hostssl requires TLS:

# Require TLS for all remote connections
hostssl all all 0.0.0.0/0 scram-sha-256
hostssl all all ::/0      scram-sha-256

# Local Unix socket connections don't use TLS
local   all all           peer

To actively reject non-TLS remote attempts rather than just not advertising plaintext:

hostnossl all all 0.0.0.0/0 reject
hostnossl all all ::/0      reject

Client Connections

Use sslmode=verify-full. It encrypts the connection, verifies the server certificate against your CA, and checks that the hostname matches the certificate CN/SAN. The weaker modes (require skips cert verification entirely, verify-ca skips the hostname check) leave you open to MITM.

psql "host=db.example.com dbname=mydb user=appuser sslmode=verify-full sslrootcert=/path/to/ca.crt"

Connection string form:

postgresql://appuser@db.example.com/mydb?sslmode=verify-full&sslrootcert=/path/to/ca.crt

Mutual TLS (mTLS)

Standard TLS encrypts the connection and lets the client verify the server's certificate. Mutual TLS goes further: the server also verifies a certificate presented by the client. PostgreSQL can use client certificates as the sole authentication mechanism, replacing passwords entirely for automated service accounts. This is optional; it is most useful for internal infrastructure where you manage client certificates centrally.

Server Configuration

Ensure ssl_ca_file in postgresql.conf points to the CA that signed the client certificates:

ssl_ca_file = '/etc/postgresql/ssl/client-ca.crt'

pg_hba.conf

Use the cert auth method to require client certificate authentication. PostgreSQL maps the certificate's CN to a database username:

# Require a client certificate; CN must match the database username
hostssl mydb appuser 0.0.0.0/0 cert
hostssl mydb appuser ::/0      cert

To require a client certificate alongside password authentication, add clientcert=verify-full to the existing auth method:

hostssl mydb appuser 0.0.0.0/0 scram-sha-256 clientcert=verify-full
hostssl mydb appuser ::/0      scram-sha-256 clientcert=verify-full

Client Connection

The client must supply its certificate and key alongside the CA cert:

psql "host=db.example.com dbname=mydb user=appuser \
      sslmode=verify-full \
      sslrootcert=/path/to/ca.crt \
      sslcert=/path/to/client.crt \
      sslkey=/path/to/client.key"

Connection string form:

postgresql://appuser@db.example.com/mydb?sslmode=verify-full&sslrootcert=/path/to/ca.crt&sslcert=/path/to/client.crt&sslkey=/path/to/client.key

See RFC 8446 ยง4.3.2 for the TLS Certificate Request specification, and Wikipedia: Mutual authentication for a general overview.

Security Notes

The cipher suite and protocol configuration in this guide addresses the following known TLS vulnerabilities:

  • POODLE (CVE-2014-3566, 2014): SSL 3.0 is disabled. TLS_FALLBACK_SCSV was added in OpenSSL 1.0.1j / 1.0.2 (October 2014); SSL 3.0 disabled by default in OpenSSL 1.1.0 (August 2016).
  • BEAST (CVE-2011-3389, 2011): Mitigated by recommending TLS 1.2 as the minimum; AEAD-only ciphers eliminate the CBC padding oracle.
  • CRIME (CVE-2012-4929, 2012): TLS compression is off by default in OpenSSL 1.1.0+; do not enable it.
  • Lucky13 (2013): AEAD-only cipher list eliminates CBC padding timing side-channels entirely.
  • FREAK (CVE-2015-0204, 2015): EXPORT-grade ciphers are excluded from the cipher string. Removed from OpenSSL 1.1.0 (August 2016).
  • LOGJAM (CVE-2015-4000, 2015): Short-key DHE is excluded; only ECDHE key exchange is recommended.
  • Sweet32 (CVE-2016-2183, 2016): 3DES is excluded from the cipher string.
  • ROBOT (2017): Static RSA key exchange is excluded; only ECDHE is recommended.
  • Downgrade attacks: TLS_FALLBACK_SCSV prevents protocol version rollback.
  • Renegotiation injection (CVE-2009-3555, 2009): Secure renegotiation is enforced by default in OpenSSL 0.9.8m+; TLS 1.3 removes renegotiation entirely.

The following are not addressable through TLS configuration alone:

  • Heartbleed (CVE-2014-0160, 2014): A memory disclosure bug in OpenSSL 1.0.1 through 1.0.1f. Fixed in OpenSSL 1.0.1g (April 7, 2014). Addressed by patching OpenSSL, not by TLS configuration.
  • BREACH (CVE-2013-3587, 2013): Not applicable. BREACH targets HTTP-level response compression; the PostgreSQL wire protocol does not involve HTTP.
  • DROWN (CVE-2016-0800, 2016): Requires SSLv2 to be enabled on any server sharing the same private key. Ensure SSLv2 is disabled on all services that use the same certificate and key pair.

Verification

systemctl restart postgresql

Check TLS is active, and see which protocol version and cipher your connection negotiated:

psql -h localhost -U postgres -c "SHOW ssl;"
psql -h localhost -U postgres -c "SELECT ssl, version, cipher FROM pg_stat_ssl WHERE pid = pg_backend_pid();"

Related Guides

View all Databases guides →

Configured TLS? Now Monitor It.

Generator Labs alerts you before certificates expire, get revoked, or fail chain validation, across HTTPS, SMTPS, IMAPS, LDAPS, and more.

Certificate Monitoring →