TLS guide¶
Coxswain terminates TLS at the proxy layer using SNI. It watches all kubernetes.io/tls Secrets and hot-reloads TLS material whenever a Secret is created, updated, or deleted — no restart or config reload is required.
Manual TLS (pre-provisioned Secret)¶
Create the TLS Secret manually and reference it from your Ingress or Gateway:
kubectl create secret tls app-tls \
--cert=path/to/cert.pem \
--key=path/to/key.pem
spec:
ingressClassName: coxswain
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
...
spec:
gatewayClassName: coxswain
listeners:
- name: https
port: 443
protocol: HTTPS
hostname: app.example.com
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: app-tls
TLS with cert-manager¶
cert-manager is the de facto standard for automated TLS in Kubernetes. Coxswain integrates transparently — cert-manager creates and renews the Secret; Coxswain picks it up automatically.
Prerequisites¶
| Component | Minimum version | Notes |
|---|---|---|
| cert-manager | v1.14 | For Ingress only |
| cert-manager | v1.16 | For Gateway API |
| Gateway API CRDs | v1.0 | For Gateway API usage |
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.18.0/cert-manager.yaml
kubectl wait --for=condition=Available --timeout=120s \
deploy/cert-manager deploy/cert-manager-webhook deploy/cert-manager-cainjector \
-n cert-manager
Issuer types¶
| Issuer | When to use |
|---|---|
SelfSigned |
Local dev and demos only |
CA |
Internal PKI with your own root CA |
ACME (HTTP-01) |
Production with public domain; cert-manager uses Coxswain to serve the challenge |
ACME (DNS-01) |
Production; requires DNS provider integration |
Ingress with cert-manager¶
Add the cert-manager.io/cluster-issuer annotation and a spec.tls entry:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-com
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: coxswain
tls:
- hosts:
- example.com
secretName: example-com-tls # cert-manager creates and renews this
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
A ready-to-apply example with a self-signed issuer lives in deploy/examples/tls-cert-manager-ingress.yaml.
HTTP-01 challenge passthrough¶
When using an ACME HTTP-01 solver, cert-manager temporarily creates an Ingress with the challenge path /.well-known/acme-challenge/<token>, copying the ingressClassName from the parent Ingress. Coxswain picks up this Ingress, routes the challenge to cert-manager's solver pod, and removes the route once the challenge completes. No manual configuration is required beyond setting --status-address.
Important
--status-address must be set to the proxy's external IP or hostname. Without it, Ingress.status is empty and cert-manager cannot locate the challenge endpoint.
Gateway API with cert-manager¶
cert-manager v1.16+ supports the Gateway API natively. Add the annotation to the Gateway; cert-manager creates a Certificate for each HTTPS listener:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-com-gateway
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
gatewayClassName: coxswain
listeners:
- name: https
port: 443
protocol: HTTPS
hostname: "example.com"
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: example-com-gateway-tls # cert-manager creates and renews this
allowedRoutes:
namespaces:
from: Same
A ready-to-apply example lives in deploy/examples/tls-cert-manager-gateway.yaml.
Older cert-manager (< v1.16)¶
Enable the Gateway API feature gate on the cert-manager controller:
# In your cert-manager Deployment or Helm values:
extraArgs:
- --feature-gates=ExperimentalGatewayAPISupport=true
Verification¶
After applying the manifest, wait for the Secret to appear:
# Ingress
kubectl wait secret example-com-tls \
--for=jsonpath='{.type}'=kubernetes.io/tls --timeout=60s
# Gateway API
kubectl wait secret example-com-gateway-tls \
--for=jsonpath='{.type}'=kubernetes.io/tls --timeout=60s
Test the TLS endpoint:
# Self-signed cert (-k ignores verification)
curl --resolve example.com:443:127.0.0.1 -k https://example.com/
# Inspect the served certificate
openssl s_client -connect 127.0.0.1:443 -servername example.com -brief
Wildcard TLS¶
For wildcard hostname TLS (e.g. *.example.com), the TLS Secret's tls.crt must include a wildcard SAN. Coxswain follows RFC 6125 for TLS matching: a single-label wildcard (*.example.com) matches foo.example.com but not foo.bar.example.com.
Troubleshooting¶
Secret never appears
kubectl describe clusterissuer <name>— check issuer statuskubectl get certificate -n <namespace>— check cert-manager's Certificate objectkubectl get certificaterequest -n <namespace>— check for issuance errors
Coxswain is not serving the new certificate
Verify the Secret exists and is a valid TLS type:
kubectl get secret example-com-tls -o jsonpath='{.type}'
kubectl get secret example-com-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout
Check the routing table and logs:
curl http://localhost:8082/routes
curl http://localhost:8082/status
A missing or malformed Secret produces a warning log but does not affect HTTP routes.