Routing strategies
A connection can be mapped to a backend in two ways. Which ways are allowed for a route is decided by the Kubernetes administrator with a Service label.
| Strategy | Key comes from | Example |
|---|---|---|
| username | The login in the first client message | alice.db1.pool selects deployment db1, pooled. The backend sees alice |
| sni | The TLS server name the client connected to | db1.pool.example.com selects deployment db1, pooled |
Supported per database
| DATABASE_TYPE | username | sni | Default for Services without the label |
|---|---|---|---|
| postgresql (and cockroachdb, yugabytedb, timescaledb, citus, greenplum, questdb, cratedb) | yes | only if the label enables it; the proxy terminates TLS and uses the server name as the key | username |
| redis | yes (first command AUTH or HELLO with AUTH) | yes, passthrough | username and sni |
| clickhouse (native protocol) | yes (Hello packet) | yes, passthrough | username and sni |
| mongodb | no, the username arrives only after the server answered hello | yes, passthrough | sni |
| tls (any TLS-first protocol) | no | yes, passthrough | sni |
| mysql, mariadb | not supported | not supported | none |
One proxy instance serves one database type. Run one instance per type.
The label
metadata:
labels:
xdatabase-proxy-enabled: "true"
xdatabase-proxy-database-type: redis
xdatabase-proxy-deployment-id: cache1
xdatabase-proxy-routing: username # username | sni | all
username: only login based routing may reach this Service.sni: only host name based routing may reach it.all: both.- Missing label: the default of the database type, see the table above.
If several Services share a deployment, only those allowing the strategy used by the client are considered. With STATIC_BACKENDS every strategy is allowed.
TLS: terminate or pass through
For SNI routes there are two ways to handle TLS. Choose per proxy instance with SNI_TLS_MODE.
| SNI_TLS_MODE | What happens | Backend | Client sees |
|---|---|---|---|
| passthrough (default) | The proxy forwards the encrypted bytes untouched. It needs no certificate | Must speak TLS | The backend’s certificate |
| terminate | The proxy ends TLS with its own certificate (TLS_MODE, TLS_SECRET_NAME, auto self-signed, cert-manager) and talks plain TCP to the backend | Plain TCP is fine | The proxy’s certificate |
Username routing (PostgreSQL, Redis, ClickHouse) always terminates TLS at the proxy, because the login is inside the encrypted stream. PostgreSQL always terminates TLS.
Summary per database:
| Database | TLS terminated by proxy | TLS passthrough | Route by username | Route by SNI |
|---|---|---|---|---|
| PostgreSQL | always | no | yes | only with the label |
| Redis | for username routing, or SNI_TLS_MODE=terminate | default for SNI | yes | yes |
| ClickHouse | for username routing, or SNI_TLS_MODE=terminate | default for SNI | yes | yes |
| MongoDB, tls | SNI_TLS_MODE=terminate | default | no | yes |
How a connection is decided
- The proxy looks at the first bytes. A TLS handshake starts with 0x16 0x03.
- TLS with a server name: if a route exists for that name and the label allows sni, the connection is passed through (default, the backend terminates TLS and the client must trust the backend certificate) or, with
SNI_TLS_MODE=terminate, terminated at the proxy and sent to the backend as plain TCP. - Otherwise, if the protocol can carry a login and the proxy has a certificate, the proxy terminates TLS itself, reads the login and routes by username. The hop to the backend is then plain TCP.
- Plaintext connections are routed by username. For protocols without username routing (MongoDB, tls) plaintext is refused.
PostgreSQL always reads the username from its startup message. It negotiates TLS with SSLRequest, or direct TLS (PostgreSQL 17). The server name is used only when the login has no deployment and the label enables sni.
Redis
The first command must carry the credentials:
AUTH alice.db1.pool secret
HELLO 3 AUTH alice.db1.pool secret
The proxy replaces the user with alice before forwarding and passes everything else through. Clients that send something else first (HELLO without AUTH, PING, a password-only AUTH, inline commands) get -ERR and are disconnected. Use the sni strategy for those clients.
With the default user and requirepass, log in as default.db1.
ClickHouse
The native protocol Hello packet carries database, user and password. The user is rewritten like in Redis. The HTTP interface is not supported.
MongoDB and other TLS databases
What works and what does not:
| Client connection | Routed? |
|---|---|
| TLS with SNI (host name, not an IP address) | yes |
| TLS without SNI (connecting by IP) | no, closed |
| Plaintext, no TLS | no, closed |
| Username in the login | not possible: MongoDB sends the user only after the server answered hello |
With passthrough the proxy does not decrypt the traffic, so the client talks TLS with the backend and sees the backend’s certificate. For a self-signed or private-CA certificate the client must trust it or relax verification (for example tlsAllowInvalidCertificates or tlsCAFile). Nothing has to be installed on the proxy.
With SNI_TLS_MODE=terminate the proxy presents its own certificate and the backend can run without TLS. Clients still need TLS with SNI; plaintext clients cannot be routed, because MongoDB has no username before authentication.
Use SNI. Enable TLS on the backend and point a wildcard DNS name at the proxy:
DATABASE_TYPE=mongodb
STATIC_BACKENDS="db1=10.0.0.9:27017,db1.pool=10.0.0.9:27018"
Clients connect to db1.mongo.example.com or db1.pool.mongo.example.com with TLS enabled. The proxy needs no certificate.
Errors
| Situation | Redis | Others |
|---|---|---|
| Unroutable first message | -ERR unsupported first message: ... | connection closed |
| No deployment in login | -ERR no backend for this user; expected user.deployment[.pool] | PostgreSQL sends an ErrorResponse, others close |
| Route not allowed by label | same as no backend | same |
| Unknown or missing server name | connection closed | connection closed |