Encrypting client traffic with SSL (TLS) protects database credentials and query results from interception when connections traverse shared networks or untrusted infrastructure.
PostgreSQL negotiates TLS during the connection handshake when ssl is enabled and the server is configured with a certificate (server.crt) plus a private key (server.key). Clients opt in with parameters such as sslmode=require, while access rules in pg_hba.conf can explicitly permit only encrypted sessions by using hostssl entries.
Incorrect certificate paths, permissions, or pg_hba.conf rule ordering can prevent PostgreSQL from starting or can lock out remote clients. The private key must be readable by the PostgreSQL service account only, and production deployments should prefer a CA-trusted certificate plus strict client verification (sslmode=verify-full) instead of encryption-only modes.
Related: How to configure pg_hba.conf in PostgreSQL \\
Related: How to secure a PostgreSQL server
$ sudo -u postgres psql -Atc "SHOW config_file; SHOW hba_file; SHOW data_directory;" /etc/postgresql/16/main/postgresql.conf /etc/postgresql/16/main/pg_hba.conf /var/lib/postgresql/16/main
$ sudo install --owner=postgres --group=postgres --mode=0644 server.crt /var/lib/postgresql/16/main/server.crt
Replace /var/lib/postgresql/16/main with the data directory path from the previous step.
$ sudo install --owner=postgres --group=postgres --mode=0600 server.key /var/lib/postgresql/16/main/server.key
PostgreSQL refuses to start if server.key is group/world readable, commonly logging private key file "server.key" has group or world access.
ssl = on ssl_cert_file = 'server.crt' ssl_key_file = 'server.key'
Relative paths are resolved from the cluster data directory when ssl_cert_file and ssl_key_file are not absolute.
$ sudo systemctl restart postgresql
Enabling ssl and changing certificate/key paths require a restart.
$ openssl s_client -starttls postgres -connect 127.0.0.1:5432 Can't use SSL_get_servername depth=0 CN = host.example.net verify error:num=18:self-signed certificate verify return:1 ##### snipped ##### CONNECTED(00000003) ##### snipped ##### New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 ##### snipped ##### Verify return code: 18 (self-signed certificate)
A return code of 0 (ok) indicates a complete trusted certificate chain.
hostssl appdb appuser 192.0.2.0/24 scram-sha-256 hostnossl appdb appuser 192.0.2.0/24 reject
Place the rules above any broader host entries that match the same clients, and validate scope carefully to avoid locking out legitimate access.
$ sudo -u postgres psql -c "SELECT pg_reload_conf();" pg_reload_conf ---------------- t (1 row)
$ psql "host=app.internal.example port=5432 dbname=appdb user=appuser sslmode=require" -c "\conninfo" You are connected to database "appdb" as user "appuser" on host "app.internal.example" (address "127.0.0.1") at port "5432". SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
sslmode=require encrypts traffic but does not validate server identity, so use sslmode=verify-full with a trusted root certificate for protection against man-in-the-middle attacks.