🔄 This post builds on my previous guide:
Self-Managed Kubernetes Cluster on AWS (with Traefik & MetalLB)
If you haven’t set up your Kubernetes cluster, MetalLB, and Traefik yet, start there first!
In this follow-up, we’ll extend that setup by enabling automatic HTTPS using Let’s Encrypt with the DNS-01 challenge in Traefik. This method is ideal for:
- Environments behind firewalls or NAT (no need to expose port 80/443)
- Wildcard certificates like
*.maksonlee.com
- Clusters managed in self-hosted infrastructure
This guide uses Cloudflare as the DNS provider.
âś… Prerequisites
- You’ve deployed Traefik via Helm
- MetalLB is exposing Traefik with a static IP (e.g.,
10.0.128.240
) - Your domain (e.g.,
maksonlee.com
) is managed by Cloudflare - You created
A
orCNAME
records in Cloudflare for your app (e.g.,whoami.maksonlee.com
) - Those DNS records must be DNS-only (Cloudflare proxy disabled)
- Create a Cloudflare API Token
- Go to https://dash.cloudflare.com/profile/api-tokens
- Click “Create Token”
- Choose “Edit zone DNS” template
- Scope the token to your zone (e.g.,
maksonlee.com
) - Copy the token — you’ll use it in the next step
- Store the API Token as a Kubernetes Secret
Create a secret in the same namespace as Traefik (e.g., kube-system
):
kubectl create secret generic cloudflare-api-secret --namespace kube-system --from-literal=CLOUDFLARE_DNS_API_TOKEN=your-cloudflare-token
Replace your-cloudflare-token
with your real token string.
- Create a Persistent Volume for
acme.json
Let’s Encrypt needs to persist certificate data in acme.json
.
Create a PVC for that:
# acme-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: acme-pvc
namespace: kube-system
spec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-sc
resources:
requests:
storage: 1Gi
Apply it:
kubectl apply -f acme-pvc.yaml
- Update
traefik-values.yaml
for Cloudflare DNS-01
Edit the values used to deploy Traefik via Helm:
service:
type: LoadBalancer
loadBalancerIP: 10.0.128.240
additionalArguments:
- "--ping=true"
- "--ping.entrypoint=traefik"
- "--api.dashboard=true"
- "--certificatesresolvers.dns.acme.dnschallenge=true"
- "--certificatesresolvers.dns.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.dns.acme.email=your-email@example.com"
- "--certificatesresolvers.dns.acme.storage=/data/acme.json"
- "--certificatesresolvers.dns.acme.dnschallenge.resolvers=1.1.1.1:53"
envFrom:
- secretRef:
name: cloudflare-api-secret
ingressClass:
enabled: true
isDefaultClass: true
name: traefik
deployment:
additionalVolumes:
- name: acme-storage
persistentVolumeClaim:
claimName: acme-pvc
additionalVolumeMounts:
- name: acme-storage
mountPath: /data
nodeSelector:
kubernetes.io/hostname: ip-10-0-128-6
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
âś… Be sure to:
- Replace
your-email@example.com
with your real email. - Replace
ip-10-0-128-6
with your control-plane node hostname.
- Upgrade Traefik with New Settings
Apply the updated Helm values:
helm upgrade traefik traefik/traefik --namespace kube-system -f traefik-values.yaml
- Add or Update Your Ingress for TLS
Update the Ingress to request a certificate via Let’s Encrypt DNS-01:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.tls.certresolver: dns
spec:
rules:
- host: whoami.maksonlee.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami
port:
number: 80
Apply it:
kubectl apply -f whoami.yaml
- Confirm Certificate Issuance
Check Traefik logs:
kubectl logs -n kube-system deploy/traefik
Traefik’s log should include:
Register... providerName=dns.acme
Testing certificate renew... acmeCA=https://acme-v02.api.letsencrypt.org/directory providerName=dns.acme
These lines confirm:
- ACME provider is registered (
dns.acme
) - Traefik is checking and renewing certs properly