---
title: "Security"
description: "One plaintext listener and five trust boundaries: what the broker enforces, what you put around it, and where the detail for each mechanism lives."
---

> Queen MQ documentation, for AI agents
> Complete self-contained summary of Queen MQ: https://queenmq.com/llms-brief.txt
> Fetch that first when the question is about the product rather than about this page.
> Index of all pages: https://queenmq.com/llms.txt

# Security

The broker is one process with one plaintext listener. It binds `PORT` on `QUEEN_BIND_ADDR`, which
defaults to `0.0.0.0` and so answers on every interface, serves the HTTP API and the dashboard from
that single port, and authenticates nobody until you turn JWT on. There is no TLS listener and no
CORS layer in the binary, so most of a deployment's security is what you put around it.

Because there is no TLS and no CORS, the bind address is one of the few controls the binary itself
gives you. `QUEEN_BIND_ADDR=127.0.0.1` makes the broker reachable only through something on the
same host, which is the right setting when a TLS terminator or `queen_proxy` runs beside it. It is
the wrong setting in Kubernetes, where the kubelet's probes and the Service both arrive on the pod
IP. Narrowing it moves the mesh listener with it unless `QUEEN_MESH_BIND_ADDR` says otherwise, and
neither one changes `QUEEN_MESH_ADVERTISE_HOST`, which is the address peers are told to dial.

Generate the two secrets, keep them, and boot with everything the broker does implement turned on:

```bash
JWT_SECRET="$(openssl rand -hex 32)"
QUEEN_ENCRYPTION_KEY="$(openssl rand -hex 32)"
echo "$JWT_SECRET" "$QUEEN_ENCRYPTION_KEY"   # both are needed again later
```

```bash
JWT_ENABLED=true JWT_ALGORITHM=HS256 JWT_SECRET="$JWT_SECRET" \
  PG_USE_SSL=true \
  QUEEN_ENCRYPTION_KEY="$QUEEN_ENCRYPTION_KEY" \
  ./bin/queen
```

The boundaries, and who defends each one:

- HTTP API and dashboard, on `PORT`: optional JWT per route, with TLS terminated by [`queen_proxy`](/deploy/proxy) or by whatever else you put in front, and network policy yours.
- Broker to PostgreSQL: optional TLS, off by default.
- The mesh between brokers, on `QUEEN_MESH_PORT`: an HMAC handshake and then nothing, so the firewall is yours and not optional.
- Payloads at rest: optional AES-256-GCM on flagged queues, with key management yours.
- `x-queen-tenant`, where native tenancy is on: checked for shape and never for authority, so the gateway that sets it is yours.

> **Danger**
>
> The last of those is the one boundary the broker refuses to let you leave undeclared.
> **`QUEEN_TENANCY_HEADER=1` exits 1 unless `QUEEN_KV_TRUSTED_PROXY=1` is set with it**, with no
> exception and nothing to switch off instead.
>
> The reason is that [key/value state](/use/kv) is addressable purely by name. For queues a forged
> header is bad and bounded: a caller still has to name a queue, and an ack carries a lease id the
> broker minted and a caller cannot guess. In the KV there is no such id, so the only gate is the
> tenant in the `WHERE` clause and the tenant is the header, which makes a forged one a read and a
> write of another tenant's state knowing only its name. The KV is part of the engine on every cell,
> so there is no longer any flag that could take it away to make an unvalidated header safe: choosing
> the header mode is itself the assertion that something in front sets it and strips the client's, and
> `QUEEN_KV_TRUSTED_PROXY=1` is where you write that assertion down. If you cannot make it, do not run
> with the header. An operator who needs to stop KV traffic on a live cell wants the runtime kill
> switch instead, which is a different instrument for a different problem
> ([KV and timers](/deploy/state)).

One more surface has no switch at all: `/metrics/prometheus` is public, broker authentication
cannot protect it, and its per-queue series carry no tenant label, so keep it off any network a
customer can reach ([Prometheus metrics](/reference/prometheus)).

## The front door is in this repository

`queen_proxy` (Apache-2.0, in `proxy/`) is the component the boundaries above assume. It terminates
TLS itself: set `QUEEN_PROXY_TLS_CERT` and `QUEEN_PROXY_TLS_KEY` and it serves rustls/ring directly,
failing at boot rather than serving plaintext if only one of the two is set. It authenticates two
credential kinds, an opaque cluster API key and a human session token, classifies every broker path
against a fail-closed table that forwards nothing it cannot name, strips the caller's
`Authorization` header and substitutes the cell's own token where the cell has one, and appends a
`queen_proxy.operations` row on every mutating control-plane function, a table nothing deletes
from. It is worth running in front of a single-tenant broker for those properties alone.

Neither TLS variable is set by default, and unset means the proxy serves its own plaintext listener
on `QUEEN_PROXY_PORT`, so a proxy in front of the broker is not by itself a TLS deployment. The
variables, the timeouts and the pxdb settings are in [proxy configuration](/deploy/proxy); the
classification table is in [endpoints a tenant can reach](/reference/multi-tenant/endpoints).

## Authentication

`JWT_ENABLED` is `false` by default, and off means open: every route answers anything that can open
a TCP connection, `DELETE /api/v1/resources/queues/:queue` and `POST /api/v1/system/maintenance`
included, and the dashboard boots as a full admin. The boot log states the mode in one line, so
read it instead of assuming.

Turned on, each route carries an access level and each token carries roles. `/health`, the metrics
endpoints, the dashboard shell and the identity routes stay public whatever you do. The algorithms,
JWKS rotation, boot-time validation and the five access levels, which are a set and not a ladder,
are in [broker authentication](/reference/security/jwt).

## Payload encryption at rest

Encryption needs two things true at once: `QUEEN_ENCRYPTION_KEY` on the process and
`encryptionEnabled` on the queue. With only one of them the broker stores plaintext and raises no
error, so confirm the `encryption service initialized (AES-256-GCM)` line at boot before trusting
the flag. It covers the payload on every write path, the disk spool included, and it is the one
control that still holds when someone walks off with a database dump
([payload encryption at rest](/reference/security/encryption)).

## PostgreSQL TLS

`PG_USE_SSL` is `false` by default, which means every message you store crosses a plaintext
connection. Turn it on whenever the database is not on localhost. With it on, the broker validates
the server's chain against the Mozilla roots compiled into the binary, and most managed PostgreSQL
fails that check because its chain is rooted in a private CA. The answer is `PG_SSL_ROOT_CERT`,
holding that provider's CA certificate as PEM content: verification stays on, against the CA you
named and only that one. `PG_SSL_REJECT_UNAUTHORIZED=false` remains as a last resort for a provider
that will not publish its CA, and it encrypts the connection without proving whose key answered
([PostgreSQL TLS](/reference/security/postgres-tls)).

Inside the database, `002_streams_schema.sql` grants `queen_streams.queries`,
`queen_streams.state` and `queen_streams.quota` to `PUBLIC`, so any role that can connect reads
and rewrites stream definitions, stream state and the per-tenant streams grants, whatever the
HTTP layer allows ([the schema](/internals/schema)).

## The mesh port

Notifications between brokers travel over framed TCP on `QUEEN_MESH_PORT`. The `HELLO` handshake is
HMAC'd but replayable, and every frame after it is unauthenticated JSON, maintenance-mode flips
included: whoever can connect can divert a replica's pushes to its disk spool, or make its pops
answer empty. Bind the port to a private network and allow it only from the other brokers'
addresses. `QUEEN_SYNC_SECRET` raises the bar from connecting to capturing one handshake, and does
not replace the firewall ([the mesh](/internals/mesh)).

TLS in front, JWT on, `PG_USE_SSL` true with a CA to verify it against, a key on the queues that
need one, and a mesh port only the brokers can reach.

- [High availability](/deploy/ha) — More than one broker on one database, and what the mesh between them is for.
- [Broker-native tenancy](/reference/multi-tenant/broker-tenancy) — What the x-queen-tenant header scopes, and why it is an operator feature.

Source: https://queenmq.com/deploy/security/index.mdx
