Traefik on k3s: Let's Encrypt & Prometheus Monitoring
estimated read time: 4 minutes
Traefik v3 on K3s: Ingress Routing and TLS with cert-manager
One thing that caught me out early on with K3s is that it ships with Traefik already installed as the default ingress controller. You don’t need to install it — it’s running in kube-system from the moment your cluster comes up. This trips people up when they try to install their own ingress controller and end up with two fighting over the same ports.
kubectl get pods -n kube-system | grep traefik
This post covers what Traefik actually does in that context, how to expose services through it using IngressRoute (Traefik’s native CRD), and how to wire up TLS certificates via cert-manager using DNS01 validation.
IngressRoute vs Standard Ingress
Traefik supports both the standard Kubernetes Ingress resource and its own IngressRoute CRD. Standard Ingress works fine for basic routing, but IngressRoute gives you access to Traefik-specific features: middleware chains, priority rules, and more granular TLS configuration.
The tradeoff worth knowing upfront: cert-manager’s automatic certificate annotations only work on standard Ingress resources, not on IngressRoute. If you want IngressRoute, you manage certificates explicitly with a Certificate resource and reference the resulting secret directly. It’s a small amount of extra YAML, but you get more visibility into what’s happening.
Also worth noting — the API group changed in Traefik v3:
| Version | API Group |
|---|---|
| v2 | traefik.containo.us/v1alpha1 |
| v3 | traefik.io/v1alpha1 |
If you’re following older guides and your IngressRoute resources aren’t being picked up, this is the likely cause.
Exposing a Service with IngressRoute
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: my-service-route
namespace: default
spec:
entryPoints:
- websecure
routes:
- match: Host(`my-service.example.com`)
kind: Rule
services:
- name: my-service
port: 80
tls:
secretName: my-service-tls
The secretName here is where cert-manager will store the issued certificate — more on that below.
TLS with cert-manager
I use cert-manager with a Cloudflare DNS01 solver across my cluster — if you haven’t set that up yet, see this post which walks through the full configuration.
The pattern for issuing a certificate for a Traefik IngressRoute is:
- Create a
Certificateresource pointing at yourClusterIssuer - cert-manager handles the DNS01 challenge and stores the certificate in a
Secret - The
IngressRoutereferences thatSecretviatls.secretName
Certificate Resource
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-service-tls
namespace: default
spec:
secretName: my-service-tls
dnsNames:
- my-service.example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
Once cert-manager provisions the certificate, the secret my-service-tls will appear in the namespace and the IngressRoute will pick it up automatically. Renewals are handled by cert-manager without any manual intervention.
Redirecting HTTP to HTTPS
In Traefik v3, HTTP-to-HTTPS redirection is handled via Middleware. Create it once and reference it on any route that needs it:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: redirect-to-https
namespace: default
spec:
redirectScheme:
scheme: https
permanent: true
Then add a separate IngressRoute on the web entrypoint that applies the middleware:
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: my-service-http
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`my-service.example.com`)
kind: Rule
middlewares:
- name: redirect-to-https
services:
- name: my-service
port: 80
Putting It All Together
A full working setup for a service with HTTPS and HTTP redirect:
# cert-manager handles issuance and renewal automatically
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-service-tls
namespace: default
spec:
secretName: my-service-tls
dnsNames:
- my-service.example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
---
# Redirect HTTP to HTTPS
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: redirect-to-https
namespace: default
spec:
redirectScheme:
scheme: https
permanent: true
---
# HTTP entrypoint — redirect only
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: my-service-http
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`my-service.example.com`)
kind: Rule
middlewares:
- name: redirect-to-https
services:
- name: my-service
port: 80
---
# HTTPS entrypoint — actual traffic
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: my-service-https
namespace: default
spec:
entryPoints:
- websecure
routes:
- match: Host(`my-service.example.com`)
kind: Rule
services:
- name: my-service
port: 80
tls:
secretName: my-service-tls
The DNS01 challenge means cert-manager doesn’t need inbound port 80 traffic to prove domain ownership — it creates a temporary DNS record via the Cloudflare API instead. This is particularly useful when your cluster is behind a firewall or NAT where HTTP01 challenges aren’t practical.
Pi Cluster Series
- Part 1 — Introduction: Pi Cluster
- Part 2 — Setting Up the Cluster
- Part 3 — Persistent Storage with Longhorn
- Part 4 — Offsite Backups with AWS S3
- Part 5 — Load Balancing with MetalLB
- Part 6 — Ingress and TLS with Traefik v3 ← you are here
- Part 7 — Edge Protection with Cloudflare