Traefik is a modern reverse proxy and load balancer that integrates natively with k8s, Docker, and many other backends. It auto-discovers services, supports Ingress, IngressRoute (CRD), and Gateway API, and is the most flexible open-source ingress controller.

Why Traefik

FeatureTraefikNGINXHAProxy
Auto-discovery✅ (k8s, Docker, Consul)❌ manual config❌ manual config
Ingress support
Gateway API✅ (some)
CRDIngressRoute
Let’s Encrypt✅ built-inManualManual
Dashboard
Metrics✅ Prometheus
ConfigurationLabels, CRDsConfigMapConfig file
PerformanceGoodExcellentExcellent

Traefik shines when: you have lots of services, want auto-discovery, want Gateway API, want a dashboard.

NGINX shines when: you need maximum throughput, battle-tested config, complex rewrites.

The install

helm repo add traefik https://traefik.github.io/charts
helm repo update
# values.yaml
ingressClass:
  enabled: true
  name: traefik
  isDefaultClass: true
 
ports:
  web:
    port: 80
    redirectTo: websecure
  websecure:
    port: 443
    tls:
      enabled: true
      certResolver: letsencrypt
 
certificatesResolvers:
  letsencrypt:
    acme:
      email: ops@example.com
      storage: /data/acme.json
      httpChallenge:
        entryPoint: web
 
logs:
  general:
    level: INFO
  access:
    enabled: true
 
api:
  dashboard: true
  insecure: false
 
metrics:
  prometheus:
    enabled: true
 
resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 1
    memory: 512Mi
 
persistence:
  enabled: true
  size: 1Gi
  storageClass: gp3
  accessMode: ReadWriteOnce
helm install traefik traefik/traefik \
  --namespace traefik --create-namespace \
  --values values.yaml

Manifests (quick test)

kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.0/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
# CRDs
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.0/docs/content/reference/dynamic-configuration/kubernetes-auth.yml
# RBAC
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.0/docs/content/reference/dynamic-configuration/kubernetes-service.yml
# Service + Deployment

The Ingress resource

Traefik supports standard k8s Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
    traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
spec:
  ingressClassName: traefik
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app
            port:
              number: 80
  tls:
  - hosts:
    - app.example.com

The IngressRoute (CRD)

Traefik’s custom resource, more powerful than Ingress:

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: my-app
spec:
  entryPoints:
  - websecure
  routes:
  - match: Host(`app.example.com`) && PathPrefix(`/`)
    kind: Rule
    services:
    - name: my-app
      port: 80
  tls:
    certResolver: letsencrypt
    domains:
    - main: example.com
      sans:
      - "*.example.com"

Why IngressRoute over Ingress:

  • Multiple services per route
  • Middlewares per route
  • Weighted load balancing
  • Mirror services
  • Retry policies

Weighted load balancing

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: my-app
spec:
  entryPoints:
  - websecure
  routes:
  - match: Host(`app.example.com`)
    kind: Rule
    services:
    - name: my-app-v1
      port: 80
      weight: 90
    - name: my-app-v2
      port: 80
      weight: 10   # canary
  tls:
    certResolver: letsencrypt

This is the canary pattern. No need for Argo Rollouts (for HTTP-only canaries).

Middlewares

Traefik’s middlewares are powerful. Common ones:

Rate limiting

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: rate-limit
spec:
  rateLimit:
    average: 100
    burst: 200
    period: 1m
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: my-app
spec:
  routes:
  - match: Host(`app.example.com`)
    kind: Rule
    services:
    - name: my-app
      port: 80
    middlewares:
    - name: rate-limit

Authentication (Basic)

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: basic-auth
spec:
  basicAuth:
    users:
    - admin:$apr1$xyz$...    # htpasswd hash
    - alice:$apr1$abc$...
  removeHeader: true

Authentication (Forward / OIDC)

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: oidc-auth
spec:
  forwardAuth:
    address: http://oauth2-proxy.auth:4181/oauth2/auth
    trustForwardHeader: true
    authResponseHeaders:
    - X-Forwarded-User
    - X-Forwarded-Groups
    - X-Forwarded-Email

Use with oauth2-proxy or Pomerium for OIDC.

IP allowlist/blocklist

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: allow-internal
spec:
  ipAllowList:
    sourceRange:
    - 10.0.0.0/8
    - 192.168.0.0/16
    - 172.16.0.0/12
    - 127.0.0.1/32
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: block-bad-actors
spec:
  ipDenyList:
    sourceRange:
    - 1.2.3.0/24
    - 5.6.7.0/24

Header manipulation

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: add-headers
spec:
  headers:
    customRequestHeaders:
      X-Request-ID: "{{.RequestId}}"
      X-Forwarded-Proto: "https"
    customResponseHeaders:
      X-Frame-Options: "DENY"
      X-Content-Type-Options: "nosniff"
      Strict-Transport-Security: "max-age=31536000"
    sslProxyHeaders:
      X-Forwarded-Proto: "https"

Redirects

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: redirect-https
spec:
  redirectScheme:
    scheme: https
    permanent: true
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: redirect-www
spec:
  redirectRegex:
    regex: "^https?://www\\.(.+)"
    replacement: "https://${1}"
    permanent: true

Circuit breaker / retry

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: circuit-breaker
spec:
  retry:
    attempts: 3
    initialInterval: 100ms

Gateway API

Traefik supports Gateway API as of v3.0:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - app.example.com
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: my-app
      port: 80

Gateway API is the future of k8s ingress. Traefik is one of the more mature implementations.

TLS / cert-manager

Two ways to handle TLS in Traefik:

Option 1: Traefik’s built-in ACME

certificatesResolvers:
  letsencrypt:
    acme:
      email: ops@example.com
      storage: /data/acme.json
      httpChallenge:
        entryPoint: web

Traefik handles cert issuance and renewal. Simple.

Option 2: cert-manager + TLS Store

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

cert-manager issues certs, Traefik serves them. More flexible.

The dashboard

# port-forward
kubectl port-forward -n traefik deploy/traefik 9000:9000
 
# open http://localhost:9000/dashboard/

Or expose via Ingress:

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
spec:
  entryPoints:
  - websecure
  routes:
  - match: Host(`traefik.example.com`)
    kind: Rule
    services:
    - name: api@internal
      kind: TraefikService
  tls:
    certResolver: letsencrypt

The dashboard shows:

  • Active routers
  • Services
  • Middlewares
  • Health
  • Metrics

Observability

Metrics

Traefik exposes Prometheus metrics on /metrics:

metrics:
  prometheus:
    enabled: true
    addEntryPointsLabels: true
    addServicesLabels: true
# Prometheus ServiceMonitor
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: traefik
  labels:
    release: prometheus
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: traefik
  endpoints:
  - port: metrics

Tracing

tracing:
  openTelemetry:
    enabled: true
    address: otel-collector.observability:4317
    insecure: true
    samplingRate: 0.1

Access logs

accessLog:
  enabled: true
  filePath: /var/log/traefik/access.log
  format: json
  fields:
    defaultMode: keep
    headers:
      defaultMode: drop
      names:
        User-Agent: keep
        X-Forwarded-For: keep

HA setup

# values.yaml
deployment:
  kind: Deployment
  replicas: 3
 
# or DaemonSet for max performance
deployment:
  kind: DaemonSet
 
updateStrategy:
  type: RollingUpdate
  rollingUpdate:
    maxUnavailable: 1
# anti-affinity
podAntiAffinity:
  preferredDuringSchedulingIgnoredDuringExecution:
  - weight: 100
    podAffinityTerm:
      topologyKey: kubernetes.io/hostname
      labelSelector:
        matchLabels:
          app.kubernetes.io/name: traefik
# topology spread
topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: ScheduleAnyway
  labelSelector:
    matchLabels:
      app.kubernetes.io/name: traefik

Common patterns

Blue-green deploy

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: my-app
spec:
  entryPoints:
  - websecure
  routes:
  - match: Host(`app.example.com`)
    kind: Rule
    services:
    - name: my-app-blue
      port: 80
  tls:
    certResolver: letsencrypt

To switch: change my-app-blue to my-app-green. Atomic. Instant rollback.

Sticky sessions

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: my-app
spec:
  routes:
  - match: Host(`app.example.com`)
    kind: Rule
    services:
    - name: my-app
      port: 80
    strategy: RoundRobin
    sticky:
      cookie:
        name: my-app-stickiness
        secure: true
        httpOnly: true
        sameSite: lax

Mirror (shadow traffic)

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: my-app
spec:
  routes:
  - match: Host(`app.example.com`)
    kind: Rule
    services:
    - name: my-app-v1
      port: 80
    - name: my-app-v2
      port: 80
      mirror: true   # mirror to v2, response discarded

Path-based routing

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: monorouter
spec:
  routes:
  - match: Host(`app.example.com`) && PathPrefix(`/api`)
    kind: Rule
    services:
    - name: api
      port: 80
  - match: Host(`app.example.com`) && PathPrefix(`/web`)
    kind: Rule
    services:
    - name: web
      port: 80

Migration from NGINX

# old
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://example.com"
spec:
  ingressClassName: nginx
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app
            port:
              number: 80
# new
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: my-app
spec:
  entryPoints:
  - websecure
  routes:
  - match: Host(`app.example.com`) && PathPrefix(`/`)
    kind: Rule
    services:
    - name: my-app
      port: 80
    middlewares:
    - name: cors
    - name: strip-prefix
  tls:
    certResolver: letsencrypt

Differences:

  • Traefik uses CRDs (IngressRoute), not just Ingress
  • Middlewares are first-class (not annotations)
  • TLS is per-route, not per-Ingress
  • Some NGINX-specific annotations don’t translate

Common gotchas

  • IngressRoute vs Ingress: Traefik supports both, but IngressRoute is more powerful.
  • Path matching syntax differs between Ingress and IngressRoute. IngressRoute uses Traefik’s match syntax.
  • The dashboard is a security risk if exposed. Use auth.
  • Custom headers can break things if you override X-Forwarded-* badly.
  • Sticky sessions are simple but don’t survive pod restarts. For real session management, use Redis.
  • ACME storage is a single file. If the Traefik pod restarts and storage isn’t persistent, you reissue certs.
  • Rate limiting is per Traefik instance unless you use Redis backend. For multi-replica, use Redis.
  • The websecure entrypoint requires TLS to be configured, or the route won’t work.
  • Traefik can be a SPOF if not HA. Run 3+ replicas across zones.
  • BackendRefs must exist when the route is created. Race conditions during deploy.

See also