How to Configure Traefik Ingress on K3s with Let’s Encrypt and cert-manager

Deploying microservices inside a Kubernetes cluster is only half the battle; routing inbound external traffic seamlessly and securing it with automatic SSL/TLS encryption is what transforms a local test cluster into a production-grade platform. When running K3s on bare metal or in a homelab, Rancher provides a major advantage out of the box: Traefik is pre-installed as the default Ingress Controller.

Kubernetes engineer configuring Traefik Ingress on K3s with cert-manager and Let's Encrypt SSL
How to Configure Traefik Ingress on K3s with Let's Encrypt and cert-manager 3

However, many administrators struggle with automated certificate management. In a standard Docker Compose setup, tools like Caddy handle Let’s Encrypt natively. In Kubernetes, the cloud-native standard for managing certificates across dozens of independent services, namespaces, and subdomains is cert-manager.

By pairing K3s’s built-in Traefik controller with cert-manager, you unlock zero-touch SSL automation. When you deploy a new web application and declare an Ingress manifest, cert-manager automatically performs the ACME HTTP-01 challenge, acquires a signed certificate from Let’s Encrypt, creates a Kubernetes Secret, and instructs Traefik to terminate TLS with zero downtime.

In this comprehensive, production-tested guide, you will learn step-by-step how to deploy and configure cert-manager on K3s, establish a ClusterIssuer for Let’s Encrypt (staging and production), configure Traefik Ingress routes, and enforce permanent HTTP-to-HTTPS redirects.

Understanding the Architecture: Traefik + cert-manager

To operate this pipeline effectively, it helps to understand the responsibilities of each component as defined in the K3s Ingress Controller Guide and the cert-manager ACME Documentation:

  • Traefik Ingress Controller: Listens on ports 80 and 443 of your K3s cluster nodes (or MetalLB virtual IP). Traefik reads Kubernetes Ingress objects, matches incoming HTTP Host headers, and routes traffic to the appropriate backend Service pods.
  • cert-manager: An open-source Kubernetes controller that watches for Certificate resources or annotations on Ingress objects. When an ingress requests TLS, cert-manager coordinates with Let’s Encrypt, spins up a temporary challenge pod to solve the HTTP-01 verification challenge, and stores the resulting TLS keypair in a Kubernetes Secret.
  • Traefik Dynamic TLS: Traefik watches the secret generated by cert-manager and mounts it dynamically without restarting the ingress controller.

Technical Prerequisites

Ensure your environment satisfies the following conditions before proceeding:

  1. K3s Cluster: An operational K3s cluster (v1.28+) with Traefik enabled (the K3s default).
  2. kubectl Access: Working command-line access via kubectl.
  3. Public Domain & DNS: A public domain (e.g., app.yourdomain.com) with a DNS A record pointing directly to your cluster node’s public IP address (or port-forwarded router IP).
  4. Open Firewall Ports: Inbound TCP ports 80 and 443 must reach your K3s cluster. Let’s Encrypt requires port 80 to complete the HTTP-01 challenge.

Step 1: Installing cert-manager on K3s

The cleanest way to deploy cert-manager is using its official Kubernetes release manifest, which installs the cert-manager controller, webhook, CA injector, and required Custom Resource Definitions (CRDs) in a dedicated namespace:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml

Wait a few moments for the pods to initialize, then verify that all three cert-manager components are in Running status:

kubectl get pods -n cert-manager

You should see cert-manager, cert-manager-cainjector, and cert-manager-webhook all reporting 1/1 Running.

Step 2: Creating Let’s Encrypt ClusterIssuers

A ClusterIssuer is a cluster-wide resource that defines which certificate authority to use. We will create two ClusterIssuers:

  1. Staging Issuer: Uses Let’s Encrypt’s staging API. Crucial for testing new configurations without burning through Let’s Encrypt’s strict production rate limits (5 failed orders per hour per domain).
  2. Production Issuer: Issues genuine, globally trusted SSL certificates once routing is validated.

Create a manifest named cluster-issuers.yaml. Make sure to replace admin@yourdomain.com with your actual email address:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: admin@yourdomain.com
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
      - http01:
          ingress:
            class: traefik
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@yourdomain.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - http01:
          ingress:
            class: traefik

Apply the manifest:

kubectl apply -f cluster-issuers.yaml

Verify that both issuers successfully registered their ACME accounts:

kubectl get clusterissuers -o wide

The READY column must display True for both issuers.

Step 3: Deploying a Test Application

Deploy a sample web application (the popular podinfo diagnostic service or a simple Nginx container) and expose it via a standard ClusterIP service:

cat << 'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-webapp
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: demo-webapp
  template:
    metadata:
      labels:
        app: demo-webapp
    spec:
      containers:
        - name: webapp
          image: nginxdemos/hello:plain-text
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: demo-webapp-svc
  namespace: default
spec:
  type: ClusterIP
  selector:
    app: demo-webapp
  ports:
    - port: 80
      targetPort: 80
EOF

Step 4: Creating the Ingress Resource with Automatic TLS

Now, create the Kubernetes Ingress manifest. Notice the two critical annotations:

  • cert-manager.io/cluster-issuer: letsencrypt-prod: Tells cert-manager to provision a production certificate for the hosts declared in tls.
  • traefik.ingress.kubernetes.io/router.entrypoints: web,websecure: Explicitly routes both HTTP and HTTPS entrypoints in Traefik.

Create ingress.yaml. Replace app.yourdomain.com with your actual public subdomain:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-webapp-ingress
  namespace: default
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
spec:
  ingressClassName: traefik
  tls:
    - hosts:
        - app.yourdomain.com
      secretName: demo-webapp-tls # cert-manager stores the keypair here
  rules:
    - host: app.yourdomain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: demo-webapp-svc
                port:
                  number: 80

Apply the ingress manifest:

kubectl apply -f ingress.yaml

Step 5: Monitoring the Certificate Issuance Process

Once the ingress is applied, cert-manager automatically creates a CertificateRequest and begins the ACME challenge. You can monitor the lifecycle in real time:

kubectl get certificates -w

Within 30–60 seconds, you will observe the status transition to ready:

NAME              READY   SECRET            AGE
demo-webapp-tls   True    demo-webapp-tls   45s

If the certificate remains in READY: False, inspect the challenge status to debug DNS or routing errors:

kubectl describe challenges

Step 6: Enforcing HTTP-to-HTTPS Redirection

In production, you want all plaintext HTTP traffic on port 80 to automatically redirect to encrypted HTTPS on port 443. In Traefik on K3s, this is achieved cleanly using a Traefik Middleware resource.

Create a global redirect middleware:

cat << 'EOF' | kubectl apply -f -
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: redirect-to-https
  namespace: default
spec:
  redirectScheme:
    scheme: https
    permanent: true
EOF

Now, attach the middleware to your ingress by adding the middleware annotation to your ingress.yaml:

metadata:
  annotations:
    traefik.ingress.kubernetes.io/router.middlewares: default-redirect-to-https@kubernetescrd

Re-apply your ingress with kubectl apply -f ingress.yaml. Now, any visitor accessing http://app.yourdomain.com will immediately receive a permanent 301/308 redirect to https://app.yourdomain.com.

Troubleshooting Common K3s Ingress & SSL Errors

1. ACME HTTP-01 Challenge Times Out

  • Cause: Inbound port 80 is blocked by your ISP, firewall router, or cloud security group. Let’s Encrypt cannot validate domain ownership over port 443 alone; port 80 is mandatory for the HTTP-01 solver.
  • Fix: Verify port 80 is open with curl -I http://app.yourdomain.com/.well-known/acme-challenge/test from an external network.

2. Fake/Untrusted Certificate Warning

  • Cause: You left the ingress annotated with letsencrypt-staging instead of letsencrypt-prod. Staging certificates are cryptographically valid but issued by the *Fake LE Root X1* authority.
  • Fix: Update the annotation to letsencrypt-prod, delete the existing secret with kubectl delete secret demo-webapp-tls, and cert-manager will immediately request a clean production certificate.

Conclusion & Next Steps

By integrating cert-manager with K3s’s built-in Traefik Ingress Controller, you have eliminated the manual overhead of managing SSL certificates on your Kubernetes cluster. New services can now be published with zero friction, receiving automated Let’s Encrypt certificates and seamless HTTPS redirection.

Now that external ingress routing is secure, your next step is unlocking hardware compute power. In our next guide, we will explore how to configure NVIDIA GPU passthrough on K3s to run local LLMs with Ollama in Kubernetes.