Skip to content

PostgreSQL TLS

Encrypt the broker's connection to PostgreSQL, and understand exactly what the encrypt-only mode does not protect against.

Updated View as Markdown

Every message Queen stores travels over the connection between the broker and PostgreSQL. That connection is plaintext by default. Two variables control it, and the difference between them is the difference between encryption and authenticated encryption.

Variable Default Effect
PG_USE_SSL false Wrap the connection in TLS at all
PG_SSL_REJECT_UNAUTHORIZED true Validate the server’s certificate chain

With PG_USE_SSL=false the broker connects with no TLS layer whatsoever: the connector is built only when the flag is on. There is no “prefer” or opportunistic mode: it is off, or it is on with one of the two verification behaviours below.

Verified TLS: the default when TLS is on

PG_USE_SSL=true

The broker builds a rustls client on the ring provider and trusts the Mozilla root set bundled into the binary at compile time (webpki-roots). The server’s chain must validate against those roots and its name must match.

This is the mode to want. It is also the mode that fails against most managed PostgreSQL offerings, because they present a chain rooted in their own private CA rather than a public one. The broker has no variable for adding a custom root certificate: the trust store is the compiled-in Mozilla set and nothing else. If your provider’s chain is private, your options are the system trust of a sidecar or tunnel in front of PostgreSQL, or the encrypt-only mode below.

Encrypt-only: what managed PostgreSQL usually needs

PG_USE_SSL=true
PG_SSL_REJECT_UNAUTHORIZED=false

This installs a certificate verifier that accepts any certificate the server presents. Signature verification for the TLS handshake itself still runs (the handshake is cryptographically sound against the key in whatever certificate arrives), but nothing checks whose key it is. It is the equivalent of libpq’s sslmode=require.

What you get:

  • The bytes on the wire are encrypted. A passive tap on the network path sees ciphertext, not payloads, and not the credentials in the startup message.

What you do not get:

  • No proof of identity. Any endpoint that completes a TLS handshake is accepted as your database.
  • No protection against an active man in the middle. An attacker who can redirect the broker’s TCP connection (a poisoned DNS answer, a hijacked service record, an ARP-level redirect, a compromised sidecar) terminates TLS with its own self-signed certificate, and the broker connects happily. It then holds the database password from the startup message and every message the broker writes.
  • No detection of a certificate swap. There is nothing to compare against, so a substituted certificate produces no error and no log line.

In other words, encrypt-only defends against someone reading the wire and not against someone becoming the other end of it.

Choosing

  1. Same host or private socket, nothing else on the segment. Leave PG_USE_SSL=false. TLS to localhost buys ciphertext on a loopback nobody else can read, at the cost of a handshake per pooled connection.

  2. Managed PostgreSQL with a public CA chain. PG_USE_SSL=true and leave PG_SSL_REJECT_UNAUTHORIZED at its default. Verify by breaking it on purpose once: point the host at something with a wrong name and confirm the broker fails to connect.

  3. Managed PostgreSQL with a private CA chain. PG_USE_SSL=true plus PG_SSL_REJECT_UNAUTHORIZED=false, and treat the network path as part of your trust boundary. Document that it is, because the certificate no longer proves anything about it.

  4. Across an untrusted network. Do not rely on either mode. Terminate the trust decision somewhere that can validate a private root: a TLS-terminating proxy next to the broker, or a VPN.

How it applies

The setting affects the broker’s whole PostgreSQL surface, not just the request path: the deadpool connection pool, the boot-time connection that applies the embedded schema, and the maintenance connections all go through the same connector. There is no separate knob for the migration connection.

The flags are read once at boot, like every other broker variable. A failure to establish TLS surfaces as a connection error at boot or as pool checkout failures at runtime. The broker does not silently fall back to plaintext.

  • Trust boundaries: where this boundary sits among the others, including the mesh port and the plaintext HTTP listener.
  • Payload encryption at rest: the only mechanism that protects payloads once they are inside the database.
  • The full variable list, with defaults, is in the reference.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close