Configuration reference¶
Coxswain is configured via environment variables. Each setting maps to an environment variable (the COXSWAIN_* family, plus the Downward-API POD_NAME / POD_NAMESPACE) and an equivalent CLI flag.
Setting configuration¶
Pass values at install or upgrade time:
helm upgrade coxswain oci://ghcr.io/coxswain-labs/charts/coxswain \
--namespace coxswain-system \
--set proxy.ingress.http.port=80 \
--set proxy.ingress.https.port=443 \
--set watchNamespace=my-namespace
Or via a values.yaml file:
helm upgrade coxswain oci://ghcr.io/coxswain-labs/charts/coxswain \
--namespace coxswain-system \
-f my-values.yaml
See the Helm install guide for the full values reference.
Set environment variables directly on the relevant Deployment (controller or shared proxy):
env:
- name: COXSWAIN_INGRESS_HTTP_PORT
value: "80"
- name: COXSWAIN_INGRESS_HTTPS_PORT
value: "443"
- name: COXSWAIN_STATUS_ADDRESS
value: "203.0.113.10"
Settings¶
| Env var | Flag | Default | Description |
|---|---|---|---|
COXSWAIN_ACCESS_LOG |
--access-log |
true |
Emit one structured access-log event per proxied request on the coxswain_proxy::access target; set false to silence. See Observability |
COXSWAIN_ACCESS_LOG_PATH_MODE |
--access-log-path-mode |
full |
What the access-log path field records: full, pattern, or none |
COXSWAIN_ADMIN_PORT |
--admin-port |
8082 |
Port for admin, metrics, and diagnostics endpoints |
COXSWAIN_CONTROLLER_LEASE_RENEW_INTERVAL |
--controller-lease-renew-interval |
5s |
How often the leader renews its lease; must be ≤ 1/3 of the TTL |
COXSWAIN_CONTROLLER_LEASE_TTL |
--controller-lease-ttl |
15s |
How long a lease stays valid without renewal; must be ≥ 3× the renew interval |
COXSWAIN_CONTROLLER_NAME |
--controller-name |
coxswain-labs.dev/gateway-controller |
GatewayClass spec.controllerName to claim |
COXSWAIN_DISCOVERY_BOOTSTRAP_ENDPOINT |
--discovery-bootstrap-endpoint |
(none) | (proxy) https:// URI of the controller bootstrap listener; where the proxy exchanges its SA token + CSR for an SVID. See Control-plane security |
COXSWAIN_DISCOVERY_BOOTSTRAP_PORT |
--discovery-bootstrap-port |
50052 |
(controller) Port for the server-auth-only bootstrap gRPC listener |
COXSWAIN_DISCOVERY_CA_BUNDLE_PATH |
--discovery-ca-bundle-path |
/var/run/secrets/coxswain/trust-bundle/ca.crt |
(proxy) Path to the mounted trust-bundle ConfigMap used to verify the controller |
COXSWAIN_DISCOVERY_CA_MODE |
--discovery-ca-mode |
auto |
(controller) auto self-generates the CA Secret if absent; external requires a pre-existing Secret (fail closed) |
COXSWAIN_DISCOVERY_CA_SECRET |
--discovery-ca-secret |
coxswain-discovery-ca |
(controller) Name of the CA Secret (tls.crt/tls.key) in the controller namespace |
COXSWAIN_DISCOVERY_ENDPOINT |
--discovery-endpoint |
(none; required for proxy) | (proxy) Comma-separated controller discovery (Stream) endpoints; https://host:port for mTLS |
COXSWAIN_DISCOVERY_PORT |
--discovery-port |
50051 |
(controller) Port for the mTLS Stream gRPC listener (routing snapshots) |
COXSWAIN_DISCOVERY_SA_TOKEN_PATH |
--discovery-sa-token-path |
/var/run/secrets/coxswain/discovery-token/token |
(proxy) Path to the projected ServiceAccount token presented at bootstrap |
COXSWAIN_DISCOVERY_SVID_TTL |
--discovery-svid-ttl |
24h |
(controller) Lifetime of SVIDs issued to proxies; proxies refresh at ~50 % |
COXSWAIN_DISCOVERY_TRUST_DOMAIN |
--discovery-trust-domain |
cluster.local |
SPIFFE trust domain; must match across the controller and all proxies |
COXSWAIN_HEALTH_PORT |
--health-port |
8081 |
Port for liveness and readiness health endpoints |
COXSWAIN_DISABLE_GATEWAY_API |
--disable-gateway-api |
false |
Disable the Gateway API surface entirely; no HTTPRoute/GatewayClass reflectors are started and the gateway_api_crds health check is not registered |
COXSWAIN_DISABLE_INGRESS |
--disable-ingress |
false |
Disable the Ingress surface entirely; no Ingress reflectors are started and no Ingress listener ports are bound |
COXSWAIN_INGRESS_DEFAULT_BACKEND |
--ingress-default-backend |
(none) | Cluster-wide fallback backend for Ingress requests that match no rule, expressed as <namespace>/<service>:<port> |
COXSWAIN_INGRESS_HTTP_PORT |
--ingress-http-port |
(none) | Port for inbound HTTP traffic; unset to bind no static Ingress HTTP listener |
COXSWAIN_INGRESS_HTTPS_PORT |
--ingress-https-port |
(none) | Port for inbound HTTPS traffic (SNI TLS); unset to bind no static Ingress HTTPS listener |
COXSWAIN_LOG |
--log |
info |
Log level; supports RUST_LOG directive syntax (e.g. info,coxswain=debug) |
COXSWAIN_LOG_FORMAT |
--log-format |
json |
json (production) or console (human-readable) |
COXSWAIN_MANAGEMENT_BIND_ADDRESS |
--management-bind-address |
0.0.0.0 |
IP the health (/healthz, /readyz) and admin (/metrics, /api/v1/health) servers bind to |
COXSWAIN_INGRESS_ACCEPT_PROXY_PROTOCOL |
--ingress-accept-proxy-protocol |
false |
Require HAProxy PROXY v1/v2 on Ingress inbound connections; must be combined with --ingress-proxy-trusted-sources. Note: h2c prior-knowledge and h2 ALPN are not available on PROXY-wrapped connections (h1-only on that path). Gateway listeners use ClientTrafficPolicy instead (see below). |
COXSWAIN_PROXY_BIND_ADDRESS |
--proxy-bind-address |
0.0.0.0 |
IP the data-plane HTTP/HTTPS proxy listeners bind to; health and admin bind separately via --management-bind-address |
COXSWAIN_PROXY_DEFAULT_BACKEND_REQUEST_TIMEOUT |
--proxy-default-backend-request-timeout |
(none) | Default upstream-only timeout when HTTPRouteRule.timeouts.backendRequest is not set |
COXSWAIN_PROXY_DEFAULT_REQUEST_TIMEOUT |
--proxy-default-request-timeout |
(none) | Default total request timeout (client → proxy → upstream → client) when HTTPRouteRule.timeouts.request is not set |
COXSWAIN_PROXY_LISTENER_DRAIN_TIMEOUT |
--proxy-listener-drain-timeout |
30s |
Drain window for in-flight requests when a Gateway listener is removed at runtime |
COXSWAIN_PROXY_SHUTDOWN_GRACE_PERIOD |
--proxy-shutdown-grace-period |
30s |
Drain window after shutdown signal |
COXSWAIN_PROXY_SHUTDOWN_TIMEOUT |
--proxy-shutdown-timeout |
5s |
Hard deadline after the grace period; remaining connections are forcibly closed |
COXSWAIN_PROXY_THREADS |
--proxy-threads |
2 |
Worker threads per proxy service; set to CPU core count for maximum throughput |
COXSWAIN_PROXY_UPSTREAM_KEEPALIVE_POOL_SIZE |
--proxy-upstream-keepalive-pool-size |
128 |
Maximum idle upstream connections in Pingora's keepalive pool; connections beyond the limit are evicted LRU |
COXSWAIN_INGRESS_PROXY_TRUSTED_SOURCES |
--ingress-proxy-trusted-sources |
(none) | Comma-separated CIDRs allowed to send PROXY-protocol headers on Ingress listeners; only meaningful with --ingress-accept-proxy-protocol |
COXSWAIN_STATUS_ADDRESS |
--status-address |
(none) | IP or hostname written to Ingress.status and Gateway.status.addresses; required for cert-manager HTTP-01 and external-dns |
COXSWAIN_WATCH_NAMESPACE |
--watch-namespace |
(cluster-wide) | Restrict the controller and proxy watch to a single namespace; both pods must be set to the same value |
POD_NAME |
--pod-name |
coxswain-local |
Pod name used as the leader-election holder identity |
POD_NAMESPACE |
--pod-namespace |
coxswain-system |
Pod namespace used to scope the leader-election Lease |
Note
The dedicated proxy scope flags (--dedicated, --gateway-name, --gateway-namespace) are set by the controller on the proxy Deployments it provisions, or passed by hand when running a dedicated proxy manually. The discovery flags (--discovery-endpoint, --discovery-bootstrap-endpoint) are also set by the controller on provisioned Deployments. See Dedicated proxy pools.
Ports summary¶
| Port | Default | Env var | Endpoints |
|---|---|---|---|
| HTTP proxy | (none) | COXSWAIN_INGRESS_HTTP_PORT |
Inbound HTTP data plane |
| HTTPS proxy | (none) | COXSWAIN_INGRESS_HTTPS_PORT |
Inbound HTTPS data plane (SNI TLS) |
| Health | 8081 |
COXSWAIN_HEALTH_PORT |
/healthz, /readyz |
| Admin | 8082 |
COXSWAIN_ADMIN_PORT |
/metrics, /api/v1/health (controller role also serves /api/v1/{fleet,routing,problems,...}) |
| Discovery (Stream) | 50051 |
COXSWAIN_DISCOVERY_PORT |
(controller) mTLS gRPC routing-snapshot stream |
| Bootstrap | 50052 |
COXSWAIN_DISCOVERY_BOOTSTRAP_PORT |
(controller) server-auth gRPC SVID issuance |
Both API surfaces are enabled by default. Use controller.gatewayApi.enabled=false for Ingress-only installs and controller.ingress.enabled=false for Gateway-API-only installs. Port numbers are configured via proxy.ingress.http.port (default 80) and proxy.ingress.https.port (default 443).
Self-healing Gateway API CRD detection¶
When the Gateway API surface is enabled (--disable-gateway-api absent) but the Gateway API CRDs are not installed yet, Coxswain does not crash. Instead:
- A
gateway_api_crdshealth check is registered but staysPending, blocking/readyzuntil resolved. - A background re-probe task polls every 30 seconds.
- Once the CRDs appear, the Gateway API reflectors are started in-process and
gateway_api_crdsbecomesReady— no pod restart required.
The active API surfaces are visible in every pod's /api/v1/health response under api_surfaces.gateway_api and api_surfaces.ingress.
HTTP/2 support¶
Coxswain supports HTTP/2 on both the downstream (client → proxy) and upstream (proxy → backend) legs:
- h2 over TLS (HTTPS) — automatic. The TLS acceptor advertises
h2andhttp/1.1via ALPN; clients that don't offerh2fall back to HTTP/1.1 transparently. - h2c (cleartext HTTP/2, prior-knowledge) — automatic on plain-TCP listeners. The h2c preface is detected non-destructively; HTTP/1.1 clients on the same port are unaffected.
- Upstream h2c — enabled per-route via
appProtocol: kubernetes.io/h2con the backendServiceport (Gateway API GEP-1911HTTPRouteBackendProtocolH2C).
PROXY-protocol restriction: when --ingress-accept-proxy-protocol is set, Ingress inbound connections are h1-only. h2c prior-knowledge and h2 ALPN are disabled on PROXY-wrapped connections.
PROXY protocol¶
Coxswain supports HAProxy PROXY protocol v1 and v2 for real client-IP propagation behind L4 load balancers. The mechanism differs by listener origin:
Gateway listeners — ClientTrafficPolicy¶
Gateway listeners (including TLS passthrough/hybrid) are configured per-listener through the ClientTrafficPolicy CRD. This is dynamic: the controller reconciles policies at runtime with no proxy restart.
apiVersion: gateway.coxswain-labs.dev/v1alpha1
kind: ClientTrafficPolicy
metadata:
name: my-ctp
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: my-gateway
# Optional: scope to one listener. Omit to apply to all listeners on the Gateway.
sectionName: https
proxyProtocol:
enabled: true
trustedSources:
- 10.0.0.0/8
- 192.168.0.0/16
Precedence (GEP-713): a section-scoped policy (with sectionName) beats a gateway-scoped one for the targeted listener. When two policies at the same scope target the same listener, the older creationTimestamp wins; the loser receives Accepted=False / Conflicted=True in status.ancestors[].
TLS passthrough: PROXY headers are stripped before SNI detection, so passthrough routes work correctly whether or not PROXY headers are present. Without a ClientTrafficPolicy, PROXY headers on passthrough listeners are treated as raw connection bytes and cause the SNI lookup to fail.
Ingress listeners — flags¶
Ingress listeners are configured globally via two flags (or the equivalent Helm values). This applies to the entire Ingress plane; per-listener granularity is not available for Ingress because both :80 and :443 share a single L4 front load balancer.
| Flag | Helm value | Default | Description |
|---|---|---|---|
--ingress-accept-proxy-protocol |
proxy.shared.acceptProxyProtocol |
false |
Enable PROXY v1/v2 on Ingress listeners |
--ingress-proxy-trusted-sources |
proxy.shared.trustedSources |
(none) | CIDRs whose connections carry PROXY headers |
These flags do not affect Gateway listeners. Gateway listeners are always governed by ClientTrafficPolicy.
Discovery control plane¶
The controller and proxy communicate over a secured gRPC discovery channel:
the controller acts as a CA, proxies bootstrap an SVID with their ServiceAccount
token, and routing snapshots flow over mandatory mTLS. The COXSWAIN_DISCOVERY_*
settings above configure it; see Control-plane security
for the model, CA provisioning modes (auto / external + cert-manager / BYO),
SVID rotation, and troubleshooting.
Note
The data plane and the management surface bind independently: COXSWAIN_PROXY_BIND_ADDRESS for the HTTP/HTTPS proxy listeners, and COXSWAIN_MANAGEMENT_BIND_ADDRESS for the health and admin servers. Both default to 0.0.0.0; set the management address to a management-network IP to keep /metrics, /api/v1/health, and the health endpoints off the data-plane interface.
Leader election¶
All replicas maintain a current routing table and serve traffic; only the leader writes status back to Ingress, Gateway, and HTTPRoute objects. The lease parameters must satisfy lease-ttl ≥ 3 × lease-renew-interval.
The defaults (15 s TTL, 5 s renew interval) allow the leader to miss two renewal cycles before the lease expires. Reduce them if you need faster failover at the cost of more Kubernetes API traffic.
Duration format¶
Duration values use humantime syntax: 300ms, 5s, 1m30s, 2h, 1.5s. Unit-less integers are not accepted — always include a unit.
POD_NAME and POD_NAMESPACE¶
These are required for correct leader-election identity and are typically injected via the Kubernetes Downward API. The Helm chart handles this automatically. For raw manifests, add to the Deployment:
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace