Kubernetes controller that automatically creates Gateway API HTTPS listeners from HTTPRoutes annotated with cert-manager issuer annotations.
The Gateway API requires HTTPS listeners to be explicitly defined on a Gateway before HTTPRoutes can attach to them. When using cert-manager to provision TLS certificates, you need to manually create a listener for each hostname. This controller closes the gap by watching HTTPRoutes and auto-creating the corresponding Gateway listeners with TLS configuration.
HTTPRoute (with cert-manager annotation)
|
v
gateway-auto-listener detects new/updated HTTPRoute
|
v
Creates HTTPS listener on Gateway
- Port 443, TLS terminate mode
- Certificate reference: <hostname>-tls
- AllowedRoutes: from all namespaces
|
v
cert-manager sees the listener and provisions a certificate
|
v
HTTPRoute attaches to the new listener
cert-manager's gateway-shim works in the opposite direction: given an existing Gateway listener, it creates a Certificate resource. gateway-auto-listener creates the listener itself from HTTPRoute annotations — they complement each other.
- Kubernetes >= 1.28
- Gateway API CRDs installed
- cert-manager installed
- A Gateway implementation (e.g., NGINX Gateway Fabric, Envoy Gateway, Istio)
| Implementation | Status |
|---|---|
| NGINX Gateway Fabric | Tested |
| Envoy Gateway | Untested (should work) |
| Istio | Untested (should work) |
| Other | Untested |
# Install CRDs (if not already present)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/latest/download/standard-install.yaml
# Deploy gateway-auto-listener
kubectl apply -f https://raw.githubusercontent.com/an0nfunc/gateway-auto-listener/main/deploy/manifests.yaml
# Create an HTTPRoute with a cert-manager annotation
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
hostnames:
- my-app.example.com
parentRefs:
- name: default
namespace: nginx-gateway
rules:
- backendRefs:
- name: my-app
port: 80
EOFThe controller will automatically create an HTTPS listener for my-app.example.com on the Gateway.
helm install gateway-auto-listener oci://ghcr.io/an0nfunc/gateway-auto-listener/chart \
--namespace nginx-gateway \
--set gateway.name=default \
--set gateway.namespace=nginx-gatewayOr from source:
git clone https://github.com/an0nfunc/gateway-auto-listener.git
helm install gateway-auto-listener ./chart/gateway-auto-listener \
--namespace nginx-gatewaykubectl apply -f https://raw.githubusercontent.com/an0nfunc/gateway-auto-listener/main/deploy/manifests.yaml| Flag | Default | Description |
|---|---|---|
--gateway-name |
default |
Name of the Gateway to manage listeners on |
--gateway-namespace |
nginx-gateway |
Namespace of the Gateway |
--validated-ns-prefix |
"" (disabled) |
Namespace prefix triggering hostname validation |
--allowed-domain-suffix |
"" |
Domain suffix for tenant default subdomains |
--allowed-hostnames-annotation |
gateway-auto-listener/allowed-hostnames |
Namespace annotation key for allowed custom hostnames |
--metrics-bind-address |
:8080 |
Metrics endpoint bind address |
--health-probe-bind-address |
:8081 |
Health probe bind address |
--version |
Print version and exit |
See values.yaml for all available Helm values.
When --validated-ns-prefix is set (e.g., tenant-), namespaces matching that prefix are subject to hostname validation:
- Default subdomain:
<anything>.<namespace>.<domain-suffix>is always allowed (when--allowed-domain-suffixis set). - Custom domains: Listed in the namespace annotation (comma-separated). Subdomains are also allowed.
apiVersion: v1
kind: Namespace
metadata:
name: tenant-acme
annotations:
gateway-auto-listener/allowed-hostnames: "acme.com, shop.acme.org"Namespaces not matching the prefix can use any hostname.
Before uninstalling, ensure you clean up managed listeners. The controller uses finalizers to remove listeners when HTTPRoutes are deleted. If you remove the controller first, finalizers on existing HTTPRoutes will prevent their deletion.
# Option 1: Delete all managed HTTPRoutes first, then uninstall
kubectl delete httproutes -A -l cert-manager.io/cluster-issuer
# Option 2: Remove finalizers manually
kubectl get httproutes -A -o json | jq '.items[] | select(.metadata.finalizers[]? == "gateway-auto-listener/finalizer") | .metadata.name + " -n " + .metadata.namespace' -r | xargs -I {} kubectl patch httproute {} --type=json -p='[{"op":"remove","path":"/metadata/finalizers"}]'
# Then uninstall
helm uninstall gateway-auto-listener
# or
kubectl delete -f deploy/manifests.yamlListener not created: Check that the HTTPRoute has a cert-manager.io/cluster-issuer or cert-manager.io/issuer annotation.
Hostname rejected: Check the namespace annotation for allowed hostnames and verify the --validated-ns-prefix and --allowed-domain-suffix flags.
Finalizer stuck on HTTPRoute: The controller must be running to process finalizer removal. If the controller is gone, manually remove the finalizer.
Apache 2.0 - see LICENSE.