Skip to content

Proxy Config Reference

QHx Proxy forwards HTTP, TCP, and MQTT traffic and uses SPIFFE workload identities for mTLS. This page describes configuration fields, defaults, and deployment requirements. For complete configurations, see Proxy Configuration Examples.

Before starting a proxy, make the SPIFFE Workload API socket accessible to it, register the workload so it receives an X.509-SVID, and configure the trust bundles needed to authenticate its peers.

Supply a YAML file with -config:

Terminal window
qhx-proxy -config /etc/qhx/proxy.yaml

Alternatively, supply the configuration as the value of QHX_PROXY_CONFIG:

Terminal window
export QHX_PROXY_CONFIG="$(cat /etc/qhx/proxy.yaml)"
qhx-proxy

The environment variable contains YAML or JSON text, not a filename. A nonempty -config path takes precedence; a file read error does not fall back to the environment. The proxy exits if neither source is supplied or validation fails. Restart the process to apply configuration changes.

Run qhx-proxy -help for logging options and defaults. Common options are -zap-log-level=debug, -zap-encoder=json or -zap-encoder=console, and -zap-devel.

FieldRequired / defaultMeaning
spiffe.workload_socket_pathRequiredWorkload API address visible inside the proxy, for example unix:///spiffe-workload-api/agent.sock. The socket must exist and be accessible to the proxy user.
listenersRequired, at least oneList of independently configured listeners.
metricsAddr":7600"Bind address for the HTTP /metrics endpoint. Omitted or empty selects this default. Use "127.0.0.1:7600" to bind to loopback or "0" to disable it.
keylogEmpty; disabledFile for TLS session keys, created or appended to by the proxy. Use only for controlled debugging: anyone with this file and captured traffic can decrypt the sessions. The parent directory must exist and be writable.
notary.enablefalseEnable the notary for this proxy’s listeners; see Notary for prerequisites.
notary.database.pathNo defaultWritable database file, required when the notary is enabled. The file is created if absent; its parent directory must already exist.
Listener fieldRequired / defaultMeaning
nameRequiredNonempty name, unique within this configuration. Identifies the listener in logs.
addressRequiredBind address in host:port form, such as 127.0.0.1:8081 or :8443. An omitted host binds on all interfaces.
modeRequiredclient, server, or central; see the table below.
protocolhttphttp, tcp, or mqtt.
target.urlRequiredDestination URL with a scheme and host. Include an explicit port for TCP and MQTT. Use the scheme table below.
source.spiffe_idsEmpty listIncoming peer identity patterns, used in server and central modes.
target.spiffe_idsEmpty listOutgoing peer identity patterns, used for the mTLS hop in client and central modes.
timeoutsSee Timeouts and connection reuseOptional read, write, and idle durations.
connection.disable_keepalivefalseDisable outgoing HTTP connection reuse. TCP and MQTT do not use this setting.
middlewaresEmpty listProtocol-specific middleware entries with a type and inline options.

The caller connects to this listener; the target is the next service or proxy that the listener connects to.

ModeCaller → listenerListener → targetTypical placement
clientPlaintextSPIFFE mTLSBeside the calling application
serverSPIFFE mTLSPlaintextBeside the receiving application
centralSPIFFE mTLSSPIFFE mTLSBetween a client proxy and a server proxy

Use these target schemes for that topology:

Protocolclient / central targetserver target
httphttps://peer.example.org:8443http://127.0.0.1:8080
tcptls://peer.example.org:9443tcp://127.0.0.1:5432
mqttmqtts://peer.example.org:8883mqtt://127.0.0.1:1883

For HTTP, set an https:// target for SPIFFE mTLS; mode: client alone does not upgrade an http:// target. An HTTPS target in server mode uses ordinary HTTPS certificate verification, without the proxy’s outgoing SPIFFE identity or target.spiffe_ids restriction.

source.spiffe_ids and target.spiffe_ids are lists of Go regular expressions matching the peer’s SPIFFE URI. A nonempty list allows a peer when at least one expression matches. Use ^ and $ to match the entire URI, escape literal dots, and use YAML single quotes to preserve backslashes. For example, '^spiffe://example\.org/ns/app/sa/api-server/.*$' permits workloads under that namespace and service account.

An empty or omitted list allows any SPIFFE identity authenticated using the available trust bundles. Certificate and trust validation still apply. Setting source.spiffe_ids on a plaintext client listener does not authenticate its application caller; bind that listener to loopback or restrict network access. In a central topology, the server proxy authenticates the central proxy, not the original client.

Set durations under each listener’s timeouts, using Go duration strings such as 500ms, 30s, or 2m. Omission or 0s selects the defaults below; zero does not disable a timeout.

FieldDefaultHTTP behavior
timeouts.read15sTime allowed to read the incoming request, including its body.
timeouts.write60sTime allowed to write the response to the caller.
timeouts.idle120sWait for the next request on an incoming keep-alive connection.

For long-running HTTP requests, adjust the read and write timeouts to suit the application. These fields do not set outgoing dial or TLS handshake timeouts. connection.disable_keepalive: true opens new outgoing HTTP connections instead of reusing them.

TCP does not use these timeout fields. Buffered MQTT uses timeouts.idle to close an idle upstream connection; the next publish reconnects. MQTT does not use read or write, and unbuffered MQTT does not use idle. Use positive durations for normal operation.

Configure middleware in the middlewares array. Each entry has a type, with its options at the same level; do not add a config wrapper. For example, allowUnknown belongs alongside type: openai. Middleware is protocol-specific; TCP does not support it.

The first middleware entry processes incoming requests first.

TypeOptions and defaultsBehavior
notary-queryNo optionsServes local GET /.qhx/workload/{id}, /.qhx/certificate/{id}, and /.qhx/receipt/{id} queries. Requires notary.enable: true.
openaiallowUnknown: falseProcesses POST /v1/chat/completions. Other methods and paths receive HTTP 403 unless allowUnknown: true, which forwards them without OpenAI processing.
dummymessage: ""Diagnostic middleware that logs requests and adds a nonempty message to the Qhx-Dummy request and response headers.

When combining notary-query and openai, put notary-query first so notary queries are not blocked by OpenAI endpoint filtering. The listener’s incoming identity restrictions also control access to its notary query routes.

The openai middleware removes JSON fields outside its supported model. It reads at most 1 MiB of each request body and buffers the complete response; streaming responses are not supported. Without the notary, it produces no receipts. With the notary enabled, only successfully parsed HTTP 200 JSON responses are notarized; empty, error, and non-JSON responses receive no receipts. For notarization, use a client or central listener with an authenticated upstream workload; see Notary.

The MQTT buffer middleware queues outgoing publishes in memory and retries sending them after connection failures. Put storage, overflow-policy, and reconnect-backoff alongside type: buffer.

OptionDefaultMeaning and limits
storage.typein-memoryThe only supported backend.
storage.max-bytes33554432 (32 MiB)Maximum queued payload bytes; excludes packet metadata and other process memory. Zero selects the default.
storage.max-messages0Maximum queued messages; zero means no count limit.
storage.max-ttl0sMaximum queued-message age from enqueue; zero disables expiry. Enforcement depends on the deployed build; see below.
overflow-policyrejectreject, drop-oldest, or drop-newest; see below.
reconnect-backoff.initial1sInitial retry delay; zero selects the default.
reconnect-backoff.max30sMaximum retry delay and time budget for each replay attempt; zero selects the default. Must be at least initial.

storage.max-ttl is enforced only in builds containing the queued-message expiry fix. Without it, expired messages may still be sent. With the fix, message age is checked before each upstream send or retry, including after reconnection and retry backoff. An in-progress send may finish after expiry: max-ttl is not a deadline for broker receipt. Check your deployed build before relying on it.

Sizes, counts, and durations must not be negative. Retry delays double after failures, up to the configured maximum, and reset after a successful replay.

Use buffering only for publishing connections. Buffered MQTT supports QoS 0 and 1, but not subscriptions or QoS 2. A successful CONNECT acknowledgement is local to the proxy and does not confirm broker acceptance.

For QoS 0, failed sends are retried without broker acknowledgement. For QoS 1, the proxy normally acknowledges a publish after enqueueing it, then waits for the target’s PUBACK when forwarding it. A local PUBACK does not confirm broker delivery. Duplicate delivery is possible, including after reconnection.

Buffers are per client session and held in memory; queued messages do not survive process restarts. With reject, a full queue causes the publish to fail and the connection to close. drop-oldest evicts queued messages to make room; an oversized message still fails. drop-newest discards the new message and can still return a QoS 1 PUBACK. Buffered MQTT does not support notarization.

Set notary.enable: true to enable a shared notary and database for this proxy’s listeners. The proxy must run in Kubernetes with in-cluster API credentials, permission to get the referenced Pods, a usable JWT-SVID source, and a writable database directory. Use persistent storage to retain records across pod replacement.

For workload statements, the authenticated upstream SPIFFE identity must use the QHx pod form: spiffe://trust-domain/ns/namespace/sa/service-account/pod/pod-name/pod-uid. The referenced Pod must be accessible through the notary’s Kubernetes API, with a matching UID and service account.

HTTP notarization requires openai middleware. Requests select workload (the default), logRequest, or signRequest with the Qhx-Notarization-Level header. Add notary-query to retrieve statements, certificates, and receipts over HTTP.

Unbuffered MQTT supports notarization when an authenticated upstream pod identity is available. MQTT 5 clients can select the same levels with the qhx.notarization user property on CONNECT or PUBLISH and receive the resulting notary identifiers. TCP and buffered MQTT do not support notarization. Enabling the notary does not automatically add HTTP middleware.

Configuration validation checks required fields, unique listener names, supported modes and protocols, TCP/MQTT target schemes, and MQTT buffer limits. Unknown fields can be silently ignored: use source.spiffe_ids, target.spiffe_ids, and middlewares exactly as shown.

Valid configuration does not establish connectivity. Also check socket access, identity registration, peer trust, listening ports, and notary permissions and storage. Check the proxy logs and test application traffic after deployment.

See Proxy Configuration Examples for HTTP, TCP, and MQTT configurations. For Kubernetes manifests, see the manual sidecar deployment guide and the TCP deployment example.