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.

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.

Note: Traefik v3 (released 2024) introduced breaking changes in CRD API versions and the way middlewares are referenced. Always check your Traefik version matches the Helm chart and CRD manifests you are deploying.

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
HTTP to HTTPS redirect: The 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.

Namespace isolation: By default, IngressRoute resources can only reference Services in the same namespace. Set 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
Production recommendation: For clusters with multiple Traefik replicas, use cert-manager instead of Traefik's built-in ACME to manage certificates. cert-manager stores certs in Kubernetes Secrets that are shared across all pods, avoiding the "only one pod can renew" problem.

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, code
  • traefik_service_request_duration_seconds — backend latency histogram
  • traefik_entrypoint_open_connections — active connections per entrypoint
  • traefik_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: 1 to prevent all Traefik pods being evicted simultaneously during node maintenance.
  • Topology spread: Use topologySpreadConstraints to distribute Traefik pods across availability zones.
  • Access logs in JSON: Enable logs.access.format: json for structured log shipping to Loki or Elasticsearch.
  • Sticky sessions: Use sticky.cookie on the TraefikService for stateful workloads that cannot use distributed sessions.
  • Timeout configuration: Set respondingTimeouts.readTimeout and forwardingTimeouts.responseHeaderTimeout to 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
IngressClass annotation: If you run both Traefik and another ingress controller (e.g., NGINX for legacy services), ensure each Ingress resource has the correct ingressClassName field or kubernetes.io/ingress.class annotation to avoid duplicate processing.