TLS and certificate verification
What your endpoint presents, which trust store each client checks it against, why verify-full is the only acceptable mode, and what renewal asks of you.
Database connections are TLS-only. There is no plaintext port and no downgrade path.
There is no bundle to download
Your endpoint presents a publicly trusted certificate. Your client verifies it against the trust store it already has, so there is no file to fetch, no path to deploy and nothing to redeploy when a certificate is replaced.
This page said the opposite until 22 September 2026. The earlier design issued from a certificate authority of ours. That meant every client needed a copy of our roots before it could verify anything, and no such copy was ever downloadable. Public issuance removes the step rather than improving it.
Which store your client reads
Three stores, four clients. One connection string for all of them is right for libpq and for pgx, and wrong for the other two.
| Client | What you set | Store |
|---|---|---|
psql and anything on libpq | sslmode=verify-full sslrootcert=system | The operating system's |
Go, pgx | The same two keywords | The operating system's |
Java, pgjdbc | sslmode=verify-full&sslfactory=org.postgresql.ssl.DefaultJavaSSLFactory | The JVM's cacerts |
Node, pg | ssl: { rejectUnauthorized: true, servername: HOST } | Node's own roots, or the operating system's |
sslrootcert=system needs libpq 16 or newer; on an older libpq give it the path to the operating
system's bundle instead: /etc/ssl/certs/ca-certificates.crt on Debian and Ubuntu,
/etc/pki/tls/certs/ca-bundle.crt on RHEL and its family, and
$(brew --prefix)/etc/openssl@3/cert.pem with Homebrew's libpq on macOS.
Java is the one that fails in a confusing way. pgjdbc's default SSL factory imitates libpq: it
reads a root certificate from ~/.postgresql/root.crt and throws a FileNotFoundException naming
that path when nothing is there. sslmode=verify-full on its own is not enough for this driver.
Node is the one that fails silently. rejectUnauthorized: true verifies the chain and stops there;
servername is what makes it check the hostname. Without it you have verify-ca under another
name. NODE_EXTRA_CA_CERTS is how you add a root to whichever list your build reads.
Client examples has each of these as a working snippet.
Use verify-full, and nothing weaker
| Mode | What it checks | Use it |
|---|---|---|
disable | Nothing. No encryption | No |
require | Encryption only. Any certificate is accepted | No |
verify-ca | The chain, but not the hostname | No |
verify-full | The chain and that the hostname matches | Yes |
require is the mode that catches people out. It encrypts the connection and accepts a certificate
from anybody, which means it protects against passive interception and not against an active one.
Connect to the hostname, never to an IP address. verify-full checks the hostname against the
certificate, and an IP address will not match it.
Renewal
Certificates are short-lived and are replaced before they expire. Nothing on your side changes when one is: your client is checking a public trust store, not a file we handed you, and that store is maintained by your operating system, your JVM or your Node build rather than by us.
That is the whole of what this design asks of you, and it is the reason it replaced the previous one.