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

> Documentation Index
> Fetch the complete documentation index at: https://queenmq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# PostgreSQL TLS

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

```bash
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

```bash
PG_USE_SSL=true
```

```bash
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.

> **Caution**
>
> `PG_SSL_REJECT_UNAUTHORIZED=false` is a deliberate mode, never a default, and the
> code says so. Use it when the network path between broker and database is itself
> trusted (a private VPC subnet, a service mesh, a host-local socket path), and the
> only thing you actually need TLS for is the provider's requirement that connections
> be encrypted. Do not use it across the public internet.

## 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.

## Related

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

Source: https://queenmq.com/selfhost/security/postgres-tls/index.mdx
