Kubernetes Traefik Ingress: Dynamic Routing and Middleware
Traefik is a cloud-native edge router and Kubernetes Ingress controller that automatically discovers services and updates routing rules without any downtime. Unlike NGINX Ingress, Traefik's dynamic configuration model means that adding a new service or changing a routing rule requires only deploying a Kubernetes resource — Traefik picks up the change in real time through its built-in provider system. Its middleware pipeline, native Let's Encrypt TLS automation, and rich observability make it a compelling choice for production Kubernetes clusters.
Table of Contents
Traefik Architecture and Concepts
Traefik's configuration model is built on four core primitives that map cleanly to Kubernetes resources:
- EntryPoints — the network ports Traefik listens on (e.g., port 80 for HTTP, 443 for HTTPS)
- Routers — rules that match incoming requests to services (by host, path, headers, etc.)
- Middlewares — transformations applied to requests or responses before they reach the backend
- Services — the upstream backends (Kubernetes Services) that receive traffic
Traefik discovers these primitives through providers. In Kubernetes, there are two providers running simultaneously: the standard Kubernetes Ingress provider (reads standard Ingress objects) and the Kubernetes CRD provider (reads IngressRoute, Middleware, and other Traefik-specific CRDs). The CRD provider offers far more power and is the recommended approach for production workloads.
A key differentiator from NGINX is Traefik's hot reload behaviour. Traefik watches the Kubernetes API server and applies configuration changes without restarting or dropping connections. This enables zero-downtime deployments of routing changes, which is critical in high-traffic environments where an NGINX reload would briefly stall active connections.
Installing Traefik with Helm
The official Traefik Helm chart installs the controller, RBAC, CRDs, and a default IngressClass in one command. For production, you should customise resource limits, replica count, and expose the dashboard securely.
# Add the Traefik Helm repository
helm repo add traefik https://traefik.github.io/charts
helm repo update
# Create namespace
kubectl create namespace traefik
# Install Traefik with custom values
helm upgrade --install traefik traefik/traefik \
--namespace traefik \
--values traefik-values.yaml
# traefik-values.yaml
deployment:
replicas: 2
ingressClass:
enabled: true
isDefaultClass: true
providers:
kubernetesCRD:
enabled: true
allowCrossNamespace: true
kubernetesIngress:
enabled: true
ports:
web:
port: 8000
expose:
default: true
exposedPort: 80
redirectTo:
port: websecure
websecure:
port: 8443
expose:
default: true
exposedPort: 443
tls:
enabled: true
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
resources:
requests:
cpu: 200m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
logs:
access:
enabled: true
format: json
redirectTo: port: websecure setting in the web port configuration automatically redirects all HTTP traffic to HTTPS — this is the recommended approach rather than a middleware redirect.
IngressRoute CRDs and Routing Rules
The IngressRoute CRD is Traefik's native routing resource. It provides a much richer rule language than the standard Kubernetes Ingress object, supporting header matching, query parameter matching, and boolean logic.
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: api-server-ingress
namespace: production
spec:
entryPoints:
- websecure
routes:
# Route /api/v2 to v2 service
- match: Host(`api.example.com`) && PathPrefix(`/api/v2`)
kind: Rule
services:
- name: api-v2-service
port: 8080
middlewares:
- name: auth-middleware
- name: rate-limit
# Route /api/v1 to legacy service
- match: Host(`api.example.com`) && PathPrefix(`/api/v1`)
kind: Rule
services:
- name: api-v1-service
port: 8080
# Route by header for internal tools
- match: Host(`api.example.com`) && HeadersRegexp(`X-Debug`, `true`)
kind: Rule
priority: 100
services:
- name: api-debug-service
port: 8080
tls:
secretName: api-example-tls
The priority field controls rule evaluation order when multiple rules could match. Higher priority wins. Traefik computes a default priority based on rule length (longer rules win), but explicit priority overrides that for ambiguous cases like the debug header route above.
allowCrossNamespace: true in the Helm values if you need to route from an IngressRoute in one namespace to a Service in another.
Middleware Chains: Auth, Rate Limiting, Headers
Middleware in Traefik is composable — you define each middleware as a separate Kubernetes resource and reference them by name in your IngressRoute. They execute in the order listed. Traefik ships with over 20 built-in middleware types.
# Rate limiting middleware
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: rate-limit
namespace: production
spec:
rateLimit:
average: 100 # requests per second
burst: 200
period: 1s
sourceCriterion:
ipStrategy:
depth: 1 # trust first X-Forwarded-For hop
---
# Basic auth middleware
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: auth-middleware
namespace: production
spec:
basicAuth:
secret: basic-auth-secret # htpasswd-formatted Secret
---
# Security headers middleware
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: security-headers
namespace: production
spec:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
customResponseHeaders:
X-Robots-Tag: noindex,nofollow,nosnippet
Server: "" # remove server header
For JWT authentication, use the ForwardAuth middleware to delegate auth decisions to an external service such as OAuth2-proxy or a custom auth service:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: jwt-auth
namespace: production
spec:
forwardAuth:
address: http://oauth2-proxy.auth.svc.cluster.local:4180/oauth2/auth
trustForwardHeader: true
authResponseHeaders:
- X-Auth-User
- X-Auth-Email
Automatic TLS with Let's Encrypt
Traefik has a built-in ACME client that provisions and renews Let's Encrypt certificates automatically. Certificates are stored in a Kubernetes Secret or a file on disk. For multi-replica deployments, use a distributed storage backend to avoid cert duplication issues.
# traefik-values.yaml — ACME configuration
additionalArguments:
- "--certificatesresolvers.letsencrypt.acme.email=ops@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/data/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
persistence:
enabled: true
size: 128Mi
# Reference the cert resolver in IngressRoute
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: secure-ingress
namespace: production
spec:
entryPoints:
- websecure
routes:
- match: Host(`app.example.com`)
kind: Rule
services:
- name: frontend-service
port: 3000
tls:
certResolver: letsencrypt
domains:
- main: app.example.com
sans:
- www.app.example.com
Weighted Traffic Splitting and Canary Deployments
Traefik's TraefikService CRD enables weighted traffic splitting between multiple backends, making canary deployments and blue-green switches first-class operations.
# Weighted canary: 90% to stable, 10% to canary
apiVersion: traefik.io/v1alpha1
kind: TraefikService
metadata:
name: api-weighted
namespace: production
spec:
weighted:
services:
- name: api-stable
port: 8080
weight: 90
- name: api-canary
port: 8080
weight: 10
---
# Reference the TraefikService from IngressRoute
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: api-canary-route
namespace: production
spec:
entryPoints:
- websecure
routes:
- match: Host(`api.example.com`)
kind: Rule
services:
- name: api-weighted
kind: TraefikService
tls:
secretName: api-tls
To promote the canary to 100%, simply update the weights in the TraefikService — no IngressRoute change required, and the change takes effect within seconds with no connection drops.
Dashboard, Metrics, and Tracing
Traefik exposes a real-time web dashboard and Prometheus metrics out of the box. The dashboard shows all discovered routers, middlewares, and services along with their health status.
# Expose the Traefik dashboard securely via IngressRoute
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard
namespace: traefik
spec:
entryPoints:
- websecure
routes:
- match: Host(`traefik.internal.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))
kind: Rule
services:
- name: api@internal
kind: TraefikService
middlewares:
- name: dashboard-auth
tls:
secretName: traefik-dashboard-tls
# Check Traefik metrics endpoint
kubectl port-forward -n traefik svc/traefik 9100:9100
curl http://localhost:9100/metrics | grep traefik_router_requests_total
Key Prometheus metrics to alert on:
traefik_router_requests_total— total requests by router, method, codetraefik_service_request_duration_seconds— backend latency histogramtraefik_entrypoint_open_connections— active connections per entrypointtraefik_tls_certs_not_after— certificate expiry (alert before 30 days)
Production Hardening Tips
Before running Traefik in production, address these common gaps that can cause reliability or security issues:
- Pod Disruption Budget: Set
minAvailable: 1to prevent all Traefik pods being evicted simultaneously during node maintenance. - Topology spread: Use
topologySpreadConstraintsto distribute Traefik pods across availability zones. - Access logs in JSON: Enable
logs.access.format: jsonfor structured log shipping to Loki or Elasticsearch. - Sticky sessions: Use
sticky.cookieon the TraefikService for stateful workloads that cannot use distributed sessions. - Timeout configuration: Set
respondingTimeouts.readTimeoutandforwardingTimeouts.responseHeaderTimeoutto prevent slow backend connections from exhausting the connection pool.
# PodDisruptionBudget for Traefik
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: traefik-pdb
namespace: traefik
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: traefik
ingressClassName field or kubernetes.io/ingress.class annotation to avoid duplicate processing.