TLS


https://www.postgresql.org/docs/current/ssl-tcp.html
https://www.postgresql.org/docs/current/libpq-ssl.html
https://www.postgresql.org/docs/current/auth-pg-hba-conf.html
https://jdbc.postgresql.org/documentation/ssl/
https://www.psycopg.org/psycopg3/docs/
https://pkg.go.dev/github.com/jackc/pgx/v5
https://node-postgres.com/features/ssl

1. Important Points#

PostgreSQL TLS protects client <-> server traffic:
    encrypt query / result / password exchange
    verify server certificate with CA
    optionally use client certificate auth

recommended sslmode:
    verify-full:
        encrypt
        verify CA
        verify hostname

avoid in production:
    sslmode=disable
    sslmode=require without CA verification for sensitive systems
    rejectUnauthorized=false

2. Server Configuration#

files#

/etc/postgresql/tls/ca.crt
/etc/postgresql/tls/server.crt
/etc/postgresql/tls/server.key
sudo chown postgres:postgres /etc/postgresql/tls/server.key
sudo chmod 0600 /etc/postgresql/tls/server.key
sudo chmod 0644 /etc/postgresql/tls/server.crt /etc/postgresql/tls/ca.crt

postgresql.conf#

ssl = on
ssl_cert_file = '/etc/postgresql/tls/server.crt'
ssl_key_file = '/etc/postgresql/tls/server.key'
ssl_ca_file = '/etc/postgresql/tls/ca.crt'
ssl_min_protocol_version = 'TLSv1.2'

pg_hba.conf#

# TYPE    DATABASE  USER       ADDRESS        METHOD
hostssl   orders    order_app  10.0.0.0/16    scram-sha-256
hostssl   orders    dba_order  10.0.10.0/24   scram-sha-256
hostssl:
    only matches SSL/TLS connections

host:
    can match non-TLS connections

production:
    use hostssl for remote app connections

reload#

SELECT pg_reload_conf();

3. Verify#

psql "host=db.example.com port=5432 dbname=orders user=order_app sslmode=verify-full sslrootcert=/etc/ssl/company-ca.pem"
SELECT ssl, version, cipher
FROM pg_stat_ssl
WHERE pid = pg_backend_pid();

4. Java#

<dependency>
  <groupId>org.postgresql</groupId>
  <artifactId>postgresql</artifactId>
  <version>42.7.4</version>
</dependency>
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;

public class PgTlsExample {
  public static void main(String[] args) throws Exception {
    Properties props = new Properties();
    props.setProperty("user", "order_app");
    props.setProperty("password", System.getenv("DB_PASSWORD"));
    props.setProperty("sslmode", "verify-full");
    props.setProperty("sslrootcert", "/etc/ssl/company-ca.pem");

    try (Connection conn = DriverManager.getConnection(
        "jdbc:postgresql://db.example.com:5432/orders", props)) {
      System.out.println(conn.isValid(2));
    }
  }
}

5. Python#

pip install "psycopg[binary]"
import os
import psycopg

conninfo = (
    "host=db.example.com port=5432 dbname=orders user=order_app "
    "sslmode=verify-full sslrootcert=/etc/ssl/company-ca.pem"
)

with psycopg.connect(conninfo, password=os.environ["DB_PASSWORD"]) as conn:
    with conn.cursor() as cur:
        cur.execute("select current_database(), current_user")
        print(cur.fetchone())

6. Go#

go get github.com/jackc/pgx/v5
package main

import (
	"context"
	"fmt"
	"os"

	"github.com/jackc/pgx/v5"
)

func main() {
	url := "postgres://order_app:" + os.Getenv("DB_PASSWORD") +
		"@db.example.com:5432/orders?sslmode=verify-full&sslrootcert=/etc/ssl/company-ca.pem"

	conn, err := pgx.Connect(context.Background(), url)
	if err != nil {
		panic(err)
	}
	defer conn.Close(context.Background())

	var db string
	if err := conn.QueryRow(context.Background(), "select current_database()").Scan(&db); err != nil {
		panic(err)
	}
	fmt.Println(db)
}

7. Node.js#

npm install pg
import fs from "node:fs";
import { Pool } from "pg";

const pool = new Pool({
  host: "db.example.com",
  port: 5432,
  database: "orders",
  user: "order_app",
  password: process.env.DB_PASSWORD,
  ssl: {
    ca: fs.readFileSync("/etc/ssl/company-ca.pem", "utf8"),
    rejectUnauthorized: true
  }
});

const result = await pool.query("select current_database() as db");
console.log(result.rows[0]);
await pool.end();

8. Production Checklist#

server:
    ssl=on
    hostssl used for remote connections
    server.key permission is 0600
    certificate hostname matches connection host
    TLS 1.2+ enforced

client:
    sslmode=verify-full
    CA file mounted read-only
    no rejectUnauthorized=false
    password comes from secret manager

operations:
    cert expiry alert exists
    cert rotation tested
    pg_stat_ssl checked in runbook