How to Install & Automate SSL Certificates on Kubernetes Using Traefik Ingress and ACME
-
Automating SSL/TLS certificates on Kubernetes eliminates manual renewals and configuration errors. This guide configures cert-manager with External Account Binding (EAB) and integrates it with Traefik Ingress Controller to issue and automatically renew SSL certificates from an ACME provider.
-
-
*
Prerequisites
Mandatory (required for ACME automation)
- Working Kubernetes cluster
- kubectl access
- Traefik Ingress Controller publicly reachable on port 80
- Domain with DNS A record pointing to the cluster’s external IP
- ACME subscription with External Account Binding (EAB) credentials
- ACME server directory URL
- Working Kubernetes Service to route traffic
Environment Used in This Guide (Reference Only)
The steps and examples in this guide were validated using the environment below. Users do not need to match this setup exactly.
- Ubuntu 22.04
- kubeadm-based cluster
- containerd runtime
- MetalLB for LoadBalancer IP assignment
Any Kubernetes distribution, Linux OS, runtime, or load-balancing method may be used as long as the mandatory prerequisites are satisfied.
-
1
Step 1: Install cert-manager
Icert-manager is responsible for requesting, issuing, and renewing ACME certificates.
- Apply CRDs
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.1/cert-manager.crds.yaml --validate=false - Create namespace
kubectl create namespace cert-manager - Install cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.1/cert-manager.yaml - Verify pods
kubectl get pods -n cert-manager
Image Caption: cert-manager CRDs and controller pods running successfully.Troubleshooting Tips:- CRDs failing → re-apply with --validate=false
- Webhook errors:
kubectl logs -n cert-manager deploy/cert-manager-webhook - Pods Pending → check node resources and CNI
- Apply CRDs
-
2
Step 2: Create ACME EAB Secret
Store EAB credentials securely.
- Create Secret
kubectl create secret generic<EAB_SECRET_NAME> \ n cert-manager \ --from-literal=hmac-key=<EAB_HMAC_KEY>
Image Caption: ACME EAB secret created in cert-manager namespace.
Replace these placeholders with your own values:- <EAB_SECRET_NAME> secret name in your cluster/li>
- <EAB_HMAC_KEY> HMAC key provided by ACME provider
Notes:- Namespace must be cert-manager
- Key name must match ClusterIssuer reference
- Create Secret
-
3
Step 3: Create ClusterIssuer (ACME + EAB)
This registers your Kubernetes cluster with the ACME provider.
- Create cluster-issuer.yaml
apiVersion:cert-manager.io/v1 kind:ClusterIssuer metadata: name:<CLUSTER_ISSUER_NAME> spec: acme: email:<ACCOUNT_EMAIL> server:<ACME_SERVER_URL> privateKeySecretRef: name:<ACCOUNT_PRIVATE_KEY_SECRET_NAME> externalAccountBinding: keyID:<EAB_KEY_ID> keySecretRef: name:<EAB_SECRET_NAME> key:hmac-key solvers: - http01: ingress: class:traefikPlaceholder Notes: Replace <CLUSTER_ISSUER_NAME>, <ACCOUNT_EMAIL>, <ACME_SERVER_URL>, <EAB_KEY_ID>, and <EAB_SECRET_NAME> with your actual values. - Apply ClusterIssuer
kubectl apply -f cluster-issuer.yaml - Verify Status
kubectl get clusterissuer Expected: <CLUSTER_ISSUER_NAME> Ready=True
Image Caption: ClusterIssuer registered successfully.Troubleshooting:- READY=False → incorrect EAB key, email, or ACME server URL
- HTTP-01 failures → Traefik must be reachable on port 80.
- Create cluster-issuer.yaml
-
4
Step 4: Configure HTTP → HTTPS Redirection (Traefik-Specific)
Traefik does not auto-redirect HTTP → HTTPS when TLS is enabled. A dedicated HTTP router + middleware is required.
- Create Redirect Middleware
apiVersion:traefik.io/v1alpha1 kind:Middleware metadata: name:redirect-to-https namespace:default spec: redirectScheme: scheme:https permanent:true
Apply:kubectl apply -f redirect-middleware.yaml
Image Caption: Redirect middleware created for HTTP → HTTPS.Note:- The redirect middleware is intentionally created in the same namespace as the application ingress (default) to avoid cross-namespace resolution issues in Traefik.
- Placeholder Notes: Replace <INGRESS_NAMESPACE> with the namespace where the middleware should reside.
- Create HTTP Ingress (Redirect Only)
apiVersion:networking.k8s.io/v1 kind:Ingress metadata: name:<APP_NAME>-http namespace:default annotations: kubernetes.io/ingress.class:traefik traefik.ingress.kubernetes.io/router.entrypoints:web traefik.ingress.kubernetes.io/router.middlewares:default-redirect-to-https@kubernetescrd spec: rules: - host:<DOMAIN_NAME> http: paths: - path:/ pathType:Prefix backend: service: name:<SERVICE_NAME> port: number:80
Apply:kubectl apply -f <APP_NAME>-http.yaml
Image Caption: HTTP ingress created only for HTTPS redirection.Note:- Note: Screenshots use whoami as a sample application for demonstration purposes only. Replace it with your own application name when applying manifests.
- Troubleshooting: HTTP returns 200 → middleware reference incorrect. Namespace mismatch → redirect will not work
- Placeholder: Replace <SERVICE_NAME>, <DOMAIN_NAME>, <INGRESS_NAMESPACE>, <APP_NAME> as per your environment.
- Create HTTPS Ingress (TLS + cert-manager)
apiVersion:networking.k8s.io/v1 kind:Ingress metadata: name:<APP_NAME>-https namespace:default annotations: kubernetes.io/ingress.class:traefik cert-manager.io/cluster-issuer:<CLUSTER_ISSUER_NAME> traefik.ingress.kubernetes.io/router.entrypoints:websecure traefik.ingress.kubernetes.io/router.tls:"true" spec: tls: - hosts: -<DOMAIN_NAME> secretName:<TLS_SECRET_NAME> rules: - host:<DOMAIN_NAME> http: paths: - path:/ pathType:Prefix backend: service: name:<SERVICE_NAME> port: number:80
Apply:kubectl apply -f <APP_NAME>-https.yaml
Image Caption: HTTP ingress created only for HTTPS redirection.Note:- Note: Screenshots use whoami as a sample application for demonstration purposes only. Replace it with your own application name when applying manifests.
- Troubleshooting: HTTP returns 200 → middleware reference incorrect
- Placeholder: Replace <SERVICE_NAME>, <DOMAIN_NAME>, <INGRESS_NAMESPACE>, <APP_NAME> as per your environment.
- Create Redirect Middleware
-
5
Step 5: Monitor Certificate Issuance
kubectl get certificatekubectl describe certificate <CERTIFICATE_NAME>kubectl get certificaterequestkubectl describe certificaterequest <CERTIFICATE_REQUEST_NAME>
Image Caption: Certificate and CertificateRequest showing READY=True.Note:- Troubleshooting: Self-signed certificate → ACME issuance not completed. Pending challenge → DNS record or port 80 unreachable
- Placeholder: Replace <TLS_SECRET_NAME> and <CERTIFICATE_REQUEST_NAME> with your environment values.
-
6
Step 6: Manual Renewal Test (TEST ENVIRONMENTS ONLY – DO NOT RUN IN PRODUCTION)
Important:
This step intentionally deletes the TLS secret to force certificate re-issuance. Do NOT perform this step in a production environment, as deleting the TLS secret may cause temporary downtime or HTTPS failures until the certificate is reissued.kubectl delete secret <TLS_SECRET_NAME>
Watch re-issuance:kubectl get certificaterequest -w kubectl get certificate
Expected:- New CertificateRequest created
- Certificate returns to READY=True
- TLS secret recreated automatically
Image Caption: Certificate automatically reissued after secret deletion.Placeholder Notes: Replace <TLS_SECRET_NAME> with your actual secret name. -
7
Step 7: Final Validation
curl -I http://<DOMAIN_NAME> Expected: HTTP/1.1 308 Permanent Redirect Location: https://<DOMAIN_NAME>/ curl -I https://<DOMAIN_NAME> Expected: HTTP/2 200
Image Caption: HTTP to HTTPS redirection verification using curl, confirming a 308 Permanent Redirect and successful HTTPS response.Placeholder Notes:Replace <DOMAIN_NAME> with your domain. -
*
Summary
You have successfully:
- Installed cert-manager
- Configured ACME with External Account Binding
- Integrated Traefik with HTTP-01 challenges
- Implemented production-safe HTTP → HTTPS redirection
- Verified SSL issuance and renewal
-
*
Placeholder Table (For Reference)
Placeholder - Description
- <ACCOUNT_EMAIL> - Email address used for ACME account registration
- <ACCOUNT_PRIVATE_KEY_SECRET_NAME> - Kubernetes Secret used by cert-manager to store the ACME account private key
- <ACME_SERVER_URL> - ACME server endpoint (e.g. https://acme.sectigo.com/v2/DV)
- <APP_NAME> - Logical application name used for ingress and manifest filenames
- <CERTIFICATE_NAME> - cert-manager Certificate resource name (used with kubectl describe certificate)
- <CERTIFICATE_REQUEST_NAME> - Name of the CertificateRequest resource created by cert-manager
- <CERT_MANAGER_VERSION> - cert-manager version used (v1.15.1 or later)
- <CLUSTER_ISSUER_NAME> - cert-manager ClusterIssuer name
- <DOMAIN_NAME> - Fully qualified domain name pointing to Traefik LoadBalancer IP (example.com)
- <EAB_HMAC_KEY> - External Account Binding HMAC key provided by CA
- <EAB_KEY_ID> - External Account Binding Key ID provided by CA
- <EAB_SECRET_NAME> - Kubernetes Secret storing EAB HMAC key
- <SERVICE_NAME> - Backend Kubernetes Service name
- <SERVICE_PORT> - Backend Kubernetes Service port (e.g. 80)
- <TLS_SECRET_NAME> - Kubernetes TLS Secret name (used in Ingress secretName)
- <TRAEFIK_INGRESS_CLASS> - Traefik ingress class name (default: traefik)
-