Skip to content

Enable TLS for PostgreSQL

Severity: highApplies to: PostgreSQL 14+Applies to: PostgreSQL 18
The fixpostgresql.conf
Terminal window
ssl = on
ssl_cert_file = '/etc/postgresql/ssl/server.crt'
ssl_key_file = '/etc/postgresql/ssl/server.key'
ssl_min_protocol_version = 'TLSv1.2'

Then require it in pg_hba.conf, which is the half that matters:

Terminal window
# TYPE DATABASE USER ADDRESS METHOD
hostssl appdb appuser 10.0.1.0/24 scram-sha-256
hostnossl all all 0.0.0.0/0 reject

Turning ssl on for the first time needs a restart; certificate changes afterwards only need a reload.

ssl defaults to off. That is one of the few PostgreSQL defaults that is not the safe choice, and it means a source-built or minimally-configured cluster sends every query, every result row and every parameter in cleartext.

SCRAM does not save you here. It protects the stored hash and resists replay of the credential — but the data itself, and everything you select, crosses the wire in the clear. Anyone on the path reads your table contents.

Debian and Ubuntu packages enable ssl = on with a snakeoil certificate at install time, so a packaged cluster is usually encrypting already. A container or a compiled-from-source cluster usually is not. Check rather than assume.

This is the trap, and it mirrors the same one in Redis and SSH: enabling a secure option is not the same as disabling the insecure one.

With ssl = on and a host rule, TLS is available. A client that asks for it gets it; a client that doesn’t, connects in plaintext and is accepted. Since libpq’s default sslmode is prefer, most clients will negotiate TLS — and any client that doesn’t silently won’t, with no error and no log entry that stands out.

Three rule types decide this:

Rule Matches
host TLS and plaintext — offers encryption, requires nothing
hostssl TLS only
hostnossl Plaintext only — useful as an explicit reject

Use hostssl for anything crossing a network. The hostnossl ... reject line is belt and braces: it turns a plaintext attempt into a loud refusal instead of a quiet success.

A server can require TLS; only the client can require that it’s the right server. sslmode=require encrypts but does not verify the certificate — it stops passive eavesdropping and does nothing against an active machine-in-the-middle.

For that you need the client to verify:

Terminal window
psql "host=db.internal dbname=appdb sslmode=verify-full sslrootcert=/etc/ssl/ca.crt"

verify-full checks the chain and the hostname. It is the only mode that means what people usually think require means.