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
Related Articles
UniFi Inter-VLAN Routing: Bypassing the Dream Machine Bottleneck
How to configure L3 inter-VLAN routing on a USW Pro Max 16 PoE switch so that PC-to-NAS traffic never hits the Dream Machine's 1Gbps backplane — unlocking the full 2.5Gbps path between devices on the same switch.
Fixing NAT After Inter-VLAN Routing: OS-Level VLAN Tagging on Windows with Hyper-V
When UniFi inter-VLAN routing breaks port forwarding and makes gaming NAT go Strict, the fix is OS-level VLAN tagging — keeping NAS traffic on a tagged path while internet and gaming traffic flows through the Dream Machine as normal.
Automating Cloudflare DNS Updates from a Kubernetes CronJob
When my ISP started rotating my residential IP weekly, I stopped updating Cloudflare manually and built a Kubernetes CronJob that does it automatically every 15 minutes.
k3s HA on Raspberry Pi: Zero-Downtime Cluster in 45 Minutes (Multi-Master + MySQL)
Stop single-point failures. Build a production-grade k3s cluster with 3 master nodes, external MySQL, and load balancing—all on budget Pi hardware. Step-by-step guide with troubleshooting.