Controller Options¶
This document describes all configuration options for the controller binary. For Helm chart values, see Helm Values.
Command Line Flags¶
| Flag | Environment Variable | Default | Description |
|---|---|---|---|
--controller-name | CF_CONTROLLER_NAME | cf.k8s.lex.la/tunnel-controller | Controller name matching GatewayClass spec.controllerName |
--cluster-domain | CF_CLUSTER_DOMAIN | (auto-detect) | Kubernetes cluster domain. Also appended to any --proxy-endpoints host ending in .svc |
--metrics-addr | CF_METRICS_ADDR | :8080 | Metrics endpoint address |
--health-addr | CF_HEALTH_ADDR | :8081 | Health probe endpoint address |
--log-level | CF_LOG_LEVEL | info | Log level (debug, info, warn, error) |
--log-format | CF_LOG_FORMAT | json | Log format (json, text) |
--leader-elect | CF_LEADER_ELECT | false | Enable leader election for HA |
--leader-election-namespace | CF_LEADER_ELECTION_NAMESPACE | Namespace for leader election lease | |
--leader-election-name | CF_LEADER_ELECTION_NAME | cloudflare-tunnel-gateway-controller-leader | Leader election lease name |
--proxy-endpoints | CF_PROXY_ENDPOINTS | Proxy config API endpoints for L7 proxy sync (required). A host ending in .svc gets the resolved cluster domain appended, which is how the chart passes the proxy's headless Service. The Service each one names has requirements, see Proxy Endpoints | |
--proxy-auth-token | CF_PROXY_AUTH_TOKEN | Bearer token for proxy config push authentication, as a direct value. For callers outside the chart only -- the chart always sets --proxy-auth-secret-ref instead, for both the bring-your-own and generated cases. Overridden by --proxy-auth-secret-ref when both are set | |
--proxy-auth-secret-ref | CF_PROXY_AUTH_SECRET_REF | Shared-proxy config-API auth-token Secret to resolve, in <namespace>/<name> form, and use for the controller's own push auth. Resolved directly via the API, not a pod-level secretKeyRef (which the controller creating its own dependency would deadlock on). The single mechanism the chart uses for both the bring-your-own and generated cases; combine with --proxy-auth-secret-generate | |
--proxy-auth-secret-key | CF_PROXY_AUTH_SECRET_KEY | auth-token | Data key to read within the --proxy-auth-secret-ref Secret |
--proxy-auth-secret-generate | CF_PROXY_AUTH_SECRET_GENERATE | false | Allow creating the --proxy-auth-secret-ref Secret (with a random token) when it does not exist. false is the bring-your-own contract: the Secret must already exist. The chart sets this true only when proxy.authTokenSecretRef.name is empty |
--proxy-config-ca-secret | CF_PROXY_CONFIG_CA_SECRET | Config API CA Secret in <namespace>/<name> form. Setting it turns on config API TLS: the controller creates the CA once, issues every data plane's serving certificate from it, and pushes only to planes presenting one (https:// endpoints only). Empty keeps the plaintext push and creates nothing. Set together with --proxy-config-tls-secret; the chart sets both when proxy.configAPITLS.enabled is true | |
--proxy-config-tls-secret | CF_PROXY_CONFIG_TLS_SECRET | Shared data plane's config API serving-certificate Secret in <namespace>/<name> form, created and renewed in place by the controller and mounted by the shared proxy. Set together with --proxy-config-ca-secret | |
--proxy-token-secret | CF_PROXY_TOKEN_SECRET | Tunnel-token Secret to watch in <namespace>/<name> form; the controller rolls the proxy Deployment when the Secret data changes. Empty disables the watcher | |
--proxy-deployment-label | CF_PROXY_DEPLOYMENT_LABEL | app.kubernetes.io/component=proxy | Label selector (key=value) identifying the proxy Deployment(s) to roll on tunnel-token change. Per-Gateway data planes (Deployments controlled by a Gateway) are skipped: they roll when the token Secret their GatewayConfig names changes |
--tunnel-protocol | CF_TUNNEL_PROTOCOL | auto | Edge transport protocol (auto, http2, quic); used to warn when GRPCRoutes are present on an explicit quic tunnel |
--ws-idle-timeout | CF_WS_IDLE_TIMEOUT | Idle bound (Go duration) rendered onto per-Gateway data planes: an established WebSocket session carrying no bytes in either direction for this long is closed. Empty leaves the proxy's own 1h default. The chart passes proxy.websocket.idleTimeout here, so one value covers the shared plane and the per-Gateway ones | |
--proxy-config-api-port | CF_PROXY_CONFIG_API_PORT | 8081 | Config-API port per-Gateway data planes listen on, rendered into their container port, headless Service and NetworkPolicy, and used for the controller's pushes to them. The chart passes proxy.configAPIPort here when it is not 8081, so one value covers the shared plane and the per-Gateway ones |
--mirror-max-in-flight | CF_MIRROR_MAX_IN_FLIGHT | 0 | Mirror dispatches each RequestMirror filter may keep in flight, rendered onto per-Gateway data planes. 0 or less leaves the proxy's own default of 64. The chart passes proxy.mirror.maxInFlight here, so one value covers the shared plane and the per-Gateway ones |
--proxy-allow-x-original-host | CF_PROXY_ALLOW_X_ORIGINAL_HOST | false | Render per-Gateway data planes that trust the client-supplied X-Original-Host header. Test deployments only (details). The chart passes proxy.allowXOriginalHost here, so one value covers the shared plane and the per-Gateway ones; GatewayConfig has no field for it |
--tracing-enabled | CF_TRACING_ENABLED | false | Enable OpenTelemetry distributed tracing |
--tracing-endpoint | CF_TRACING_ENDPOINT | OTLP/gRPC collector endpoint (defers to OTEL_EXPORTER_OTLP_ENDPOINT when empty) | |
--tracing-sample-rate | CF_TRACING_SAMPLE_RATE | 1.0 | Head-sampling probability in [0,1] |
--proxy-image | CF_PROXY_IMAGE | Container image for per-Gateway rendered proxy Deployments (per-Gateway isolation). When BOTH this flag and the GatewayConfig.spec.image override are empty, rendering is skipped and the Gateway surfaces a Warning Event instead of a data plane. The Helm chart always sets it; manual installs should pass it (or set spec.image per GatewayConfig) | |
--hostname-ownership-enforce | CF_HOSTNAME_OWNERSHIP_ENFORCE | false | Controller-side hostname-ownership layer: routes whose hostnames fall outside their namespace's allowed-suffix label are rejected (HostnameNotPermitted) and never programmed. Independent of the chart's ValidatingAdmissionPolicy — see Multi-Tenancy |
--hostname-ownership-label-key | CF_HOSTNAME_OWNERSHIP_LABEL_KEY | cf.k8s.lex.la/hostname-suffix | Namespace label carrying the tenant's allowed hostname suffix |
--hostname-ownership-namespace-selector | CF_HOSTNAME_OWNERSHIP_NAMESPACE_SELECTOR | Label selector (kubectl syntax) scoping which namespaces are policed; empty polices every namespace (fail-closed) | |
--controller-pod-selector | CF_CONTROLLER_POD_SELECTOR | Label selector (kubectl syntax) identifying this controller's own pods, AND'd with the controller namespace in the rendered per-Gateway NetworkPolicy; empty admits every pod in that namespace. The chart sets it from the controller's own labels | |
--monitoring-namespace-selector | CF_MONITORING_NAMESPACE_SELECTOR | Label selector (kubectl syntax) for namespaces additionally allowed to reach a per-Gateway proxy's config-API/metrics port in the rendered NetworkPolicy; empty admits the controller pod only — see Per-Gateway Isolation | |
--render-network-policy | CF_RENDER_NETWORK_POLICY | true | Render the per-Gateway config-API NetworkPolicy. Wired from the chart's proxy.networkPolicy.enabled. Set false on strict CNIs where node-sourced kubelet probes are blocked by the policy's namespaceSelector ingress rule; a previously-rendered policy is then deleted |
For full distributed-tracing setup, see Distributed Tracing.
Proxy Endpoints¶
Each --proxy-endpoints URL names the Service the controller pushes proxy config to. The chart passes its own headless proxy Service, and the controller renders one per Gateway data plane; both meet the requirements below. A Service you bring yourself must meet them too.
- It is headless (
clusterIP: None), so its name resolves to every proxy pod. A ClusterIP Service name resolves to one virtual IP, so each push reaches a single pod and the other replicas never get a config. - It sets
publishNotReadyAddresses: true, so DNS also returns NotReady pods. A joining proxy stays NotReady until it has its first config, so without the setting DNS never returns it and it never receives that config. - Its name resolves through the controller's own DNS. A name that only an egress proxy set in
HTTP_PROXY/HTTPS_PROXYcan resolve fails the push instead of being sent through that proxy.
Environment Variables¶
All flags can be set via environment variables with the CF_ prefix. Dashes in flag names are replaced with underscores.
Examples:
--controller-name→CF_CONTROLLER_NAME--log-level→CF_LOG_LEVEL--leader-elect→CF_LEADER_ELECT
Cluster Domain Auto-Detection¶
The controller automatically detects the Kubernetes cluster domain from /etc/resolv.conf search domains. If detection fails, it falls back to cluster.local.
To override auto-detection:
Or via environment variable:
Leader Election¶
For high availability deployments with multiple controller replicas, enable leader election:
Only the leader processes events; other replicas remain on standby for automatic failover.
flowchart LR
subgraph Replicas
R1[Replica 1<br/>Leader]
R2[Replica 2<br/>Standby]
R3[Replica 3<br/>Standby]
end
LEASE[(Lease)]
R1 -->|holds| LEASE
R2 -.->|watches| LEASE
R3 -.->|watches| LEASE Logging¶
Log Levels¶
| Level | Description |
|---|---|
debug | Detailed debugging information |
info | Normal operational messages |
warn | Warning conditions |
error | Error conditions |
Log Formats¶
| Format | Description |
|---|---|
json | Structured JSON logs (recommended for production) |
text | Human-readable text logs (useful for development) |
Example JSON log:
{
"level": "info",
"ts": "2024-01-15T10:30:00Z",
"msg": "HTTPRoute reconciled",
"route": "default/my-app",
"hostnames": ["app.example.com"]
}
Health Endpoints¶
The controller exposes health endpoints for Kubernetes probes:
| Endpoint | Port | Description |
|---|---|---|
/healthz | 8081 | Liveness probe |
/readyz | 8081 | Readiness probe |
These are used by Kubernetes to determine pod health:
Metrics Endpoint¶
The controller exposes Prometheus metrics on the metrics port:
| Endpoint | Port | Description |
|---|---|---|
/metrics | 8080 | Prometheus metrics |
See Metrics & Alerting for available metrics.