One C++ binary. PostgreSQL underneath. Stateless on top.
The Queen server is a single statically-linked C++17 binary built on uWebSockets and the libpq async API. It serves the HTTP API and the Vue 3 dashboard from the same process, on the same port.
Docker
Multi-arch image (linux/amd64 + linux/arm64), pushed to Docker Hub on every release. Current stable: 0.14.1 — pin with smartnessai/queen-mq:0.14.1 for reproducible deployments.
PostgreSQL requirements
- PostgreSQL 14+ (15+ recommended for the JIT/parallel-plan paths).
- A dedicated database. Queen owns its schema and stored procedures.
- The schema is applied automatically on first start. No manual migration step.
- Pool size: keep
max_connections≥DB_POOL_SIZE + SIDECAR_POOL_SIZE + 20.
Environment variables (essentials)
Full list lives in server/ENV_VARIABLES.md.
These are the ones you almost always touch.
| Variable | Default | What it controls |
|---|---|---|
| PORT | 6632 | HTTP listener port. |
| HOST | 0.0.0.0 | HTTP bind address. |
| NUM_WORKERS | 10 | uWebSockets event-loop threads. |
| PG_HOST | localhost | PostgreSQL hostname. |
| PG_USER | postgres | PostgreSQL username. |
| PG_PASSWORD | postgres | PostgreSQL password. |
| PG_DB | postgres | Database name. |
| PG_USE_SSL | false | TLS to PostgreSQL. |
| DB_POOL_SIZE | 150 | Async connection pool for the hot path. |
| SIDECAR_POOL_SIZE | 50 | Sidecar pool for batch/transaction work. |
| DEFAULT_SUBSCRIPTION_MODE | "" (all) | Default for new consumer groups: "" or "new". |
| LOG_LEVEL | info | trace · debug · info · warn · error. |
| FILE_BUFFER_DIR | /var/lib/queen/buffers (Linux) | Disk failover buffer location. |
| QUEEN_ENCRYPTION_KEY | , | 64 hex chars (AES-256-GCM at-rest payload encryption). |
JWT authentication
Queen supports HS256 (shared secret), RS256 (RSA), and EdDSA (Ed25519), including external IDPs via JWKS auto-discovery. Routes are gated by role:
| Level | Routes | Required role |
|---|---|---|
| PUBLIC | /health, /metrics, /metrics/prometheus, / (dashboard) | none |
| READ_ONLY | GET /api/v1/status/*, /api/v1/resources/* | any valid token |
| READ_WRITE | POST /api/v1/push, GET /api/v1/pop/* | read-write or admin |
| ADMIN | /api/v1/system/*, DELETE operations | admin |
With JWT enabled the server stamps the validated sub claim onto every
pushed message as producerSub. Clients cannot set this field
, the server always derives it from the verified JWT. It's exposed on pop responses
and admin message APIs for downstream attribution.
Auth proxy — login over the dashboard
The Queen server speaks Bearer JWT only, there is no built-in username/password login form. For machine traffic that's exactly what you want. But the Vue 3 dashboard is served from the same port, and you usually want a human-friendly login page in front of it. That's what queen-mq-proxy is for.
It's a small Node.js (Express) reverse proxy that sits in front of Queen, gives
you a login page, validates credentials, mints a short-lived HS256 JWT into an
HTTP-only cookie, and forwards every authenticated request to Queen with
Authorization: Bearer <jwt>. It also supports
SSO passthrough: point it at an external IDP's JWKS URL and it
will accept your existing RS256 / EdDSA tokens and pass them through to Queen
unchanged. Users live in a queen_proxy schema on the same
PostgreSQL Queen already uses, so there is no extra database to operate.
Topology: browser → queen-mq-proxy (:3000) → queen-mq (:6632).
The proxy also strips oversized cookie / referer headers
before forwarding, which avoids “Request Header Fields Too Large”
errors from Queen on long-running browser sessions.
Run with Docker
Open http://localhost:3000/login,
log in, and you'll land on the dashboard at /. The
queen_proxy.users and queen_proxy.sessions tables are
created automatically on first start.
Roles & method gating
The proxy enforces role-based method access on top of whatever Queen itself does:
| Role | Allowed HTTP methods | Use case |
|---|---|---|
admin |
All methods | Operators. Full read/write/destroy. |
read-write |
GET, POST, PUT, DELETE |
Standard developer access. |
read-only |
GET only |
Monitoring / read-only dashboard users. |
The CLI (src/create-user.js) can also mint a long-lived bearer token
at user creation time (24h / 7d / 30d /
1y / never), useful for service accounts that hit the
proxy from CI or microservices.
SSO passthrough (optional)
Set EXTERNAL_JWKS_URL and the proxy will verify incoming bearer
tokens against your IDP's JWKS (RS256, EdDSA, etc.), normalize the claims
(sub, preferred_username, roles), and pass
the original token through to Queen unchanged. This way a single token works
across the proxy and the API, and you don't run a parallel user database for
humans who already authenticate against your IDP.
Internal-token verification still works in parallel; the proxy tries the external token first, falls back to the internal HS256 cookie if the external verification fails. Useful while migrating users.
Sign in with Google (OAuth 2.0)
In addition to local username/password and JWKS passthrough, the proxy can
drive a full OAuth 2.0 Authorization Code flow with Google
itself, so the login page exposes a Sign in with Google button. The
proxy verifies Google's id_token against
oauth2/v3/certs,
resolves (or auto-provisions) a row in queen_proxy.users, and
then mints the same internal HS256 cookie as the password flow, so RBAC and
token-forwarding to the Queen server work unchanged.
1. Create the OAuth client
In Google Cloud Console → APIs & Services → Credentials, create an OAuth 2.0 Client ID of type Web application:
- Authorized JavaScript origins — not required (we don't use the browser-side flow), but harmless to fill in.
- Authorized redirect URIs — required, must match
character-for-character. One per environment, all ending in
/api/auth/google/callback:https://queen.example.com/api/auth/google/callbackhttps://queen-stage.example.com/api/auth/google/callbackhttp://localhost:3000/api/auth/google/callback(onlyhttpURL Google accepts — useful for local dev).
Save and copy the Client ID and Client secret. Best practice is one OAuth client per environment, so a leaked stage secret can't be replayed against prod.
2. Provide the env vars
The proxy reads six GOOGLE_* variables. The recommended pattern
is a single Kubernetes Secret consumed via envFrom:
Then enable the Google block in the chart values; the StatefulSet adds the
secret to envFrom automatically.
3. Egress — let the pod reach Google
The callback exchanges the authorization code by calling
https://oauth2.googleapis.com/token from the pod. If your cluster
uses NetworkPolicies that block outbound traffic by default, that call will
time out with ConnectTimeoutError. Add an egress label that
matches a policy permitting public-internet egress (or at least the
googleapis.com hosts), e.g. np.egress.internet: 'true'
on the proxy pod template.
4. User resolution
After verifying the id_token the proxy looks up the local user
in this order:
- by
google_subif previously linked — sign in; - else by verified email (links the Google identity to the existing local user) — sign in;
- else if
GOOGLE_AUTO_PROVISION=true— create a new user with roleGOOGLE_DEFAULT_ROLE; - else — deny with
?error=not_provisioned.
Linking by email is gated on Google reporting email_verified=true
to prevent takeover by a third party who registers a Google account at
someone else's address. Promotion to read-write /
admin stays a manual UPDATE on
queen_proxy.users.
Common pitfalls
Error 400: redirect_uri_mismatch— the URI we send doesn't exactly match a registered one. Compare theredirect_uriquery param in the redirect toaccounts.google.comwith the entries in Google Cloud Console; they must be byte-identical (scheme, host, path, no trailing slash).- Lands back on
/loginwith no error — fixed in0.0.14; the session cookie usedSameSite=Strict, which the browser refuses to send on the first navigation after a cross-site redirect. The proxy now sets it toLax. - “Verify it's you” without an SMS option — rendered by Google, not the proxy. Use the Google app on a phone, an offline security code, or enroll an authenticator/security key in Google Account → Security.
- Auto-provisioned users only get
read-onlyby design. Promote via SQL once you've reviewed the row.
Helm chart
Deploy as a sibling release to queen-mq in the same namespace. The
chart we run lives at
proxy/helm/;
it ships a StatefulSet, a ClusterIP Service, an HTTPRoute (Gateway API), and a
GKE HealthCheckPolicy hitting /health/ready.
An HTTPRoute (Gateway API) maps {{ .Values.proxyHostName }}
to the proxy's port 3000, and a HealthCheckPolicy probes
/health/ready. Apply, then bootstrap the first admin user from
inside a running pod:
Environment variables
| Variable | Default | Notes |
|---|---|---|
PG_HOST / PG_PORT / PG_DB |
localhost / 5432 / postgres |
Same convention as the Queen server. Schema queen_proxy is created on first start. |
PG_USER / PG_PASSWORD |
postgres / postgres |
Use envFrom + a Kubernetes Secret in production. |
PG_USE_SSL |
off | Set to any non-empty value to enable TLS to PostgreSQL. |
QUEEN_SERVER_URL |
http://localhost:8080 |
Where authenticated traffic is forwarded. |
JWT_SECRET |
— | Required. HS256 secret for internally-issued tokens. Generate with openssl rand -hex 32. |
JWT_EXPIRES_IN |
24h |
Use 'never' for service-account tokens minted via the CLI. |
EXTERNAL_JWKS_URL |
— | Optional. Enables SSO passthrough against this IDP. |
EXTERNAL_ISSUER / EXTERNAL_AUDIENCE |
— | Optional. Validate iss / aud claims on external tokens. |
GOOGLE_CLIENT_ID |
— | Optional. Sets together with GOOGLE_CLIENT_SECRET and GOOGLE_REDIRECT_URI to enable the Sign in with Google button. |
GOOGLE_CLIENT_SECRET |
— | OAuth 2.0 client secret from Google Cloud Console. |
GOOGLE_REDIRECT_URI |
— | Must exactly match an authorized redirect URI on the OAuth client, e.g. https://queen.example.com/api/auth/google/callback. |
GOOGLE_ALLOWED_DOMAINS |
— | Comma-separated allowlist matched against the Google hd claim or the email domain. Empty = allow any verified email. |
GOOGLE_AUTO_PROVISION |
false |
If true, create a local user on first Google login. If false, the user must already exist in queen_proxy.users (matched by verified email). |
GOOGLE_DEFAULT_ROLE |
read-only |
Role assigned to auto-provisioned Google users (admin, read-write, or read-only). |
PORT |
3000 |
Listen port. |
NODE_ENV |
development |
Set to production to enable the Secure cookie flag. |
Multi-instance cluster (UDP sync)
Queen servers are stateless, you can run multiple instances pointing at the same PostgreSQL. Two optional UDP layers make horizontal scaling actually fast:
- Peer notifications, when one instance receives a push, it UDP-broadcasts a wake-up to peers, so long-polling consumers on other instances return immediately.
- Distributed cache (UDPSYNC), heartbeats + shared state for queue config caching, partition-id LRU, and server-health tracking.
Self-detection is automatic: each server excludes itself from the peer list. The cluster works fine without UDP, long-poll consumers just fall back to a slightly longer wake-up interval.
Kubernetes
We run Queen MQ on Kubernetes as a StatefulSet + headless
Service. The chart we run in production lives at
helm/; a flat
manifest is at
server/k8s-example.yaml.
The headless service gives each pod a stable DNS name, which is what the UDP sync
layer needs to find its peers, and what client-side affinity routing keys off.
values.yaml — production
The actual values file we ship in production: 3 replicas, peer DNS via the headless service, libqueen knobs raised for a 16+ core PostgreSQL.
StatefulSet template
One container per pod. Probes hit /health on 6632 (HTTP); the pod also
listens on UDP {{ .Values.udpNotifyPort }} for peer wake-ups. A PVC
mounted at /var/lib/queen/buffers holds the disk push-buffer if
PostgreSQL goes away. Postgres credentials and JWT/encryption secrets are pulled
via envFrom, never templated into the manifest.
Service: ClusterIP + headless
Two services. The ClusterIP is what your apps hit;
sessionAffinity: ClientIP keeps related calls (push then pop, ack
chains) on the same pod, which plays nicely with the per-pod partition-id LRU. The
headless service gives each pod the stable
queen-mq-N.queen-mq-headless.<ns>.svc.cluster.local name the
UDP peer list points at.
We render and apply with vanilla helm template, no Tiller, no chart
hub:
High-throughput tuning
Defaults are tuned for ~1 KB messages on a 4-core node. For a 32-core box pushing ~50k msg/s, raise:
See server/README.md for the full tuning guide and the rationale behind these defaults.
Failover & durability
- Disk push buffer, if PostgreSQL is unreachable, pushes spill to
FILE_BUFFER_DIRand replay automatically on recovery. - Multi-instance, run ≥2 servers behind a load balancer; they're stateless. Affinity routing in the clients keeps related operations on the same node when possible.
- PostgreSQL HA, point
PG_HOSTat your HA endpoint (Patroni VIP, Cloud SQL/RDS endpoint, pgbouncer, etc.). Queen reconnects automatically when the upstream changes. - Encryption at rest, set
QUEEN_ENCRYPTION_KEY(32 bytes hex) and configure queues withencryptionEnabled: truefor AES-256-GCM payload encryption inside the database.
