Context
- k3s with built-in Traefik (v3.x, deployed via Helm)
- Domain:
test.tkweb.site - ACME challenge: HTTP-01 (port 80 must be publicly reachable)
Step 1 — Create the HTTP→HTTPS redirect Middleware
Create redirect-middleware.yaml:
1apiVersion: traefik.io/v1alpha12kind: Middleware3metadata:4 name: redirect-https5 namespace: default6spec:7 redirectScheme:8 scheme: https9 permanent: trueApply:
1kubectl apply -f redirect-middleware.yamlStep 2 — Split Ingress into HTTP (redirect) + HTTPS (TLS)
A single ingress with router.tls: "true" only creates an HTTPS router — HTTP gets a 404.
The fix is two separate Ingress objects.
ingress.yaml:
1apiVersion: networking.k8s.io/v12kind: Ingress3metadata:4 name: chat2md-http5 annotations:6 traefik.ingress.kubernetes.io/router.entrypoints: web7 traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd8spec:9 ingressClassName: traefik10 rules:11 - host: test.tkweb.site12 http:13 paths:14 - path: /15 pathType: Prefix16 backend:17 service:18 name: chat2md19 port:20 number: 8021---22apiVersion: networking.k8s.io/v123kind: Ingress24metadata:25 name: chat2md26 annotations:27 traefik.ingress.kubernetes.io/router.entrypoints: websecure28 traefik.ingress.kubernetes.io/router.tls: "true"29 traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt30spec:31 ingressClassName: traefik32 tls:33 - hosts:34 - test.tkweb.site35 rules:36 - host: test.tkweb.site37 http:38 paths:39 - path: /40 pathType: Prefix41 backend:42 service:43 name: chat2md44 port:45 number: 80Apply:
1kubectl apply -f ingress.yamlMiddleware annotation format:
<namespace>-<middleware-name>@kubernetescrdSodefault-redirect-https@kubernetescrd= Middleware namedredirect-httpsindefaultnamespace.
Step 3 — Configure Traefik with Let's Encrypt via HelmChartConfig
In k3s, Traefik is managed by Helm. The correct way to customize it is via a HelmChartConfig
resource — do not edit Traefik's deployment directly.
Important: The correct Helm values key is
certificatesResolvers(notcertResolvers).
Apply this with kubectl apply -f or directly:
1cat <<'EOF' | kubectl apply -f -2apiVersion: helm.cattle.io/v13kind: HelmChartConfig4metadata:5 name: traefik6 namespace: kube-system7spec:8 valuesContent: |-9 certificatesResolvers:10 letsencrypt:11 acme:12 email: maiden.tech@gmail.com13 storage: /data/acme.json14 httpChallenge:15 entryPoint: web16 persistence:17 enabled: true18 path: /data19 size: 128Mi20EOFStep 4 — Wait for Traefik to redeploy
k3s automatically triggers a Helm upgrade job. Wait for it:
1# Wait for the Helm job to finish2kubectl wait job/helm-install-traefik -n kube-system --for=condition=complete --timeout=180s3
4# Wait for the Traefik pod to restart5kubectl rollout status deployment/traefik -n kube-system --timeout=90sStep 5 — Verify
Check cert resolver args are present in Traefik pod:
1kubectl get pod -n kube-system -l app.kubernetes.io/name=traefik \2 -o jsonpath='{.items[0].spec.containers[0].args}' | tr ',' '\n' | grep certExpected output:
"--certificatesresolvers.letsencrypt.acme.email=maiden.tech@gmail.com"
"--certificatesresolvers.letsencrypt.acme.httpChallenge.entryPoint=web"
"--certificatesresolvers.letsencrypt.acme.storage=/data/acme.json"
Check Traefik registered with Let's Encrypt:
1kubectl logs -n kube-system deployment/traefik --since=5m | grep -i "acme\|letsencrypt\|register"Expected:
INF Testing certificate renew... acmeCA=https://acme-v02.api.letsencrypt.org/directory
INF Register... providerName=letsencrypt.acme
Test HTTP redirect:
1curl -sv http://10.10.0.210 -H "host: test.tkweb.site" 2>&1 | grep -E "HTTP|Location"2# Expected: HTTP/1.1 301 Moved Permanently + Location: https://test.tkweb.site/Test HTTPS:
1curl -sk https://test.tkweb.site2# Expected: 200 OK with a valid Let's Encrypt cert (after first public request triggers issuance)How Let's Encrypt cert issuance works
- First real request to
https://test.tkweb.sitefrom the internet triggers Traefik to request a cert. - Let's Encrypt sends an HTTP-01 challenge to
http://test.tkweb.site/.well-known/acme-challenge/<token>. - Traefik answers the challenge automatically.
- Certificate is issued and stored in
/data/acme.jsoninside the Traefik pod (persisted via PVC). - Subsequent requests use the real cert — no more "TRAEFIK DEFAULT CERT" warning.
Requirement: Port 80 of
test.tkweb.sitemust be publicly reachable from the internet.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Router uses a nonexistent certificate resolver | HelmChartConfig not applied or wrong key name | Re-apply Step 3, verify with Step 5 |
| HTTP returns 404 | Single ingress with router.tls: "true" has no HTTP router | Use split ingress from Step 2 |
| HTTP returns 404 (still) | Middleware name wrong in annotation | Format must be <namespace>-<name>@kubernetescrd |
| Cert still self-signed after setup | Normal until first public request triggers HTTP-01 challenge | Access via public domain |
secret default/chat2md-tls does not exist | secretName uncommented in TLS block | Keep secretName commented out when using certresolver |