By default, K3s ships with Traefik as the Ingress controller. You can configure it to automatically generate TLS certificates using Let’s Encrypt via the ACME protocol.

This guide assumes:

  • You're running K3s with Traefik enabled
  • You have a public domain name (e.g., me.example.com)
  • Ports 80 and 443 are open and forwarded to your K3s nodes
  • DNS for your domain points to your K3s cluster external IP

✅ Step 1: Check That Traefik Is Installed

Run the following to verify Traefik is deployed:

kubectl get pods -n kube-system -l app.kubernetes.io/name=traefik

If you see a Traefik pod running, you're good.

If not, reinstall K3s with the --disable traefik flag removed.


✍️ Step 2: Create a Traefik Configuration Override

📍 You’ll only do this on one of the control-plane nodes (any server running k3s server).

Create a file at:

/var/lib/rancher/k3s/server/manifests/traefik-config.yaml

Paste the following config (customize your email address):

apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    additionalArguments:
      - "[email protected]"
      - "--certificatesresolvers.default.acme.storage=/data/acme.json"
      - "--certificatesresolvers.default.acme.httpchallenge.entrypoint=web"
    ports:
      web:
        exposedPort: 80
      websecure:
        exposedPort: 443

Replace [email protected] with your real email. Let’s Encrypt will use this to notify you about certificate expiry issues.

Save and close the file.

K3s will detect the new manifest and automatically apply it (no restart needed).


🧪 Step 3: Verify Traefik Restarted With New Settings

Check that the Traefik pods restarted and picked up the changes:

kubectl get pods -n kube-system -l app.kubernetes.io/name=traefik

Watch logs to see ACME activity:

kubectl logs -n kube-system -l app.kubernetes.io/name=traefik --tail=100 -f

Look for lines like:

Starting provider aggregator.ProviderAggregator
... Using HTTP challenge
... Registering with ACME server https://acme-v02.api.letsencrypt.org

🌐 Step 4: Create an Ingress Resource with TLS

Here’s a sample Ingress you can use for something like me.example.com:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: me-ingress
  annotations:
    kubernetes.io/ingress.class: "traefik"
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls.certresolver: default
spec:
  rules:
    - host: me.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: me-loadbalancer
                port:
                  number: 9000
  tls:
    - hosts:
        - me.example.com

Replace me.example.com with your real domain, and me-loadbalancer with the name of your Service.


🌍 Step 5: Point DNS to Your Cluster

Make sure me.example.com points to your external IP (e.g., from a cloud provider or your router). You can use services like:

  • Cloudflare DNS
  • DuckDNS (free dynamic DNS)
  • Any custom domain registrar

🚀 Step 6: Apply the Ingress

kubectl apply -f me-ingress.yaml

Wait 30–60 seconds. Then check if the certificate was issued:

kubectl describe ingress me-ingress -n my-namesapce

You should see a section under TLS with a certificate from Let’s Encrypt.


🔐 Step 7: Access Your App via HTTPS

Open your browser and go to:

https://me.example.com

You should see a valid SSL certificate (green lock icon) and no warnings.


🧰 Troubleshooting Tips

Issue Solution
ERR Unable to obtain ACME certificate for domains error="unable to generate a certificate for the domains [me.example.com]: error: one or more domains had a problem:\n[me.example.com] acme: error: 403 :: urn:ietf:params:acme:error:unauthorized :: xxx.xxx.xxx.xxx: Invalid response from http://me.example.com/.well-known/acme-challenge/yyyy: 503\n" ACME CA=https://acme-v02.api.letsencrypt.org/directory acmeCA=https://acme-v02.api.letsencrypt.org/directory domains=["me.example.com"] providerName=default.acme routerName=minio-me-ingress-me-example-com@kubernetes rule="Host(me.example.com) && PathPrefix(/) This url http://me.example.com/.well-known/acme-challenge/yyyy shour return 404. If you are running a loadbalancer make shure that it allows 404 statuses to pass health check
When using Load Balancer Make sure that it is not proxied and have ports 80 and 443 open.
Certificate not generated Check if port 80 is open and reachable
DNS not working Use nslookup me.example.com from your local machine
Traefik logs show ACME errors Check email and domain configuration
Still using HTTP Ensure you're hitting https:// and not http://

✅ Summary

Task Done
Verified Traefik is installed
Configured Let's Encrypt via manifest
Created Ingress with TLS annotations
DNS points to your K3s cluster
Accessed service over HTTPS