Traefik Ingress

Introduction

This page describes how to use various features of Traefik as an Ingress Controller in your Kubernetes cluster. These are the most common examples we notice when helping our customers to set up Ingresses.

Pre-requisites

First determine whether you want to use Traefik for your Ingress, we offer several options depending on your needs: Explanation: Ingress.

The Traefik Ingress Controller(s) also need to be deployed in your cluster. Usually this is determined and set-up during customer onboarding, however you can verify if Traefik is enabled by checking your Cluster Definition files for the following snippets:

apiVersion: skyscrapers.eu/v1beta2
kind: EksCluster
metadata:
  [...]
spec:
  [...]
  traefik:
    public:
      enabled: true
      [...]
    internal:
      enabled: true
      [...]

To ensure Traefik is correctly deployed, get in touch with our support team. By default you should have at least the internal-only controller deployed, as this is used for dashboarding like Grafana etc.

General usage

To create a basic Internet-facing Ingress with Traefik, you need to create an Ingress resource with the appropriate ingressClassName. Below is an example of a simple Ingress that routes traffic for foo.example.com to a backend Service named foo in the default namespace:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  name: my-public-ingress
  namespace: default
spec:
  ingressClassName: traefik
  rules:
    - host: foo.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service: # you need to point this to your app's K8s Service
                name: foo
                port:
                  name: http
  tls:
    - secretName: foo-example-com-tls
      hosts:
        - foo.example.com

There are a couple of other things that are happening in the background to make this work:

  1. The cert-manager.io/cluster-issuer: letsencrypt-prod annotation tells cert-manager to automatically issue a TLS certificate for this Ingress using Let’s Encrypt. The certificate will be requested for the specified hosts and stored in the foo-example-com-tls Secret. Further examples on how to use cert-manager with Traefik check our Cert-Manager How To Guide.
  2. Through external-dns we will automatically create a DNS A record for foo.example.com pointing to the Traefik load balancer. This requires that the DNS zone for example.com is hosted in Route53 in the same AWS account as the cluster.
    • If you don’t wish to auto-create a DNS record for this Ingress, you can add the annotation external-dns.alpha.kubernetes.io/exclude: "true" to disable this for this specific Ingress, for example when using an external CDN like CloudFront or Cloudflare.
  3. By default we will listen on both HTTP and HTTPS, where HTTP traffic is automatically redirected to HTTPS. You can disable this behaviour as described below.

Internal vs Internet-facing Ingress

The Skyscrapers platform supports both internal and Internet-facing Ingresses with Traefik. Internal-only Ingresses are only exposed within the AWS VPC, while Internet-facing Ingresses are exposed to the public Internet.

You can specify this by setting the appropriate IngressClassName when creating your Ingress resource:

  • traefik for Internet-facing Ingresses
  • traefik-internal for internal-only Ingresses

Internal-only example:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.middlewares: default-ip-allowlist@kubernetescrd
  name: my-private-ingress
  namespace: default
spec:
  ingressClassName: traefik-internal
  rules: []

Note

It’s important to remember that, by default, internal Ingresses will automatically have public DNS resolving configured like a normal Internet-facing Ingresses. This means anybody outside the VPC can still resolve the DNS name, but it will resolve to private IP addresses that are not reachable from the Internet. This is because we don’t currently support private hosted zones in Route53. Similarly, if you add the cert-manager.io/cluster-issuer: letsencrypt-prod annotation for automatic TLS certificates, the domain name will be registered in the public Certificate Transparency logs, even though the Ingress is internal-only.

Using Traefik Middlewares

Traefik uses the concept of Middlewares to modify requests and responses. Middlewares are defined as separate Kubernetes Custom Resources (CRDs) and then referenced from your Ingress via annotations.

The general pattern is:

  1. Create a Middleware Custom Resource in your namespace
  2. Reference it from your Ingress using the annotation: traefik.ingress.kubernetes.io/router.middlewares: <namespace>-<middleware-name>@kubernetescrd

You can chain multiple middlewares by comma-separating them:

annotations:
  traefik.ingress.kubernetes.io/router.middlewares: default-auth@kubernetescrd,default-ratelimit@kubernetescrd

Tip

If you want to create shared middlewares that can be used across multiple namespaces, consider creating them in a common namespace (e.g., default, production) and referencing them in your Ingress annotations. Please only use application-specific namespaces, and not any of the platform namespaces like infrastructure or traefik; these are reserved for Skyscrapers-provided components and middlewares.

Basic authentication

Upstream documentation

Traefik’s BasicAuth middleware requires credentials in htpasswd format, stored in a Kubernetes Secret.

Generate htpasswd credentials:

# Generate a password hash (requires apache2-utils or httpd-tools)
htpasswd -nb admin t0p-Secret
# Output: admin:$apr1$xyz123$hashedpasswordhere

Example:

---
# Secret containing htpasswd-formatted credentials
apiVersion: v1
kind: Secret
metadata:
  name: basic-auth-secret
  namespace: default
type: Opaque
stringData:
  users: |
    admin:$apr1$xyz123$hashedpasswordhere
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: basic-auth
  namespace: default
spec:
  basicAuth:
    secret: basic-auth-secret
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.middlewares: default-basic-auth@kubernetescrd
  name: foo
  namespace: default
spec:
  ingressClassName: traefik
  rules: []

For multiple users, add one per line in the users field:

stringData:
  users: |
    admin:$apr1$...
    developer:$apr1$...
    readonly:$apr1$...

Note

This differs from ingress-nginx which uses kubernetes.io/basic-auth type secrets. When migrating, you’ll need to regenerate secrets in htpasswd format.

Buffering

Upstream documentation

The Buffering middleware controls how Traefik reads and buffers requests and responses. This is useful for handling large uploads or protecting backends from slow clients.

---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: buffering
  namespace: default
spec:
  buffering:
    maxRequestBodyBytes: 10485760    # 10MB - max request body size
    memRequestBodyBytes: 2097152     # 2MB - threshold before buffering to disk
    maxResponseBodyBytes: 10485760   # 10MB - max response body size
    memResponseBodyBytes: 2097152    # 2MB - threshold before buffering to disk
    retryExpression: "IsNetworkError() && Attempts() < 3"
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.middlewares: default-buffering@kubernetescrd
  name: foo
  namespace: default
spec:
  ingressClassName: traefik
  rules: []

CORS

Upstream documentation

CORS (Cross-Origin Resource Sharing) is configured using the Headers middleware. When CORS headers are set, Traefik handles preflight (OPTIONS) requests automatically without forwarding them to the backend.

Basic CORS example:

---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: cors
  namespace: default
spec:
  headers:
    accessControlAllowMethods:
      - "GET"
      - "POST"
      - "PUT"
      - "DELETE"
      - "OPTIONS"
    accessControlAllowHeaders:
      - "Content-Type"
      - "Authorization"
    accessControlAllowOriginList:
      - "https://app.example.com"
      - "https://admin.example.com"
    accessControlMaxAge: 86400
    addVaryHeader: true
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.middlewares: default-cors@kubernetescrd
  name: api
  namespace: default
spec:
  ingressClassName: traefik
  rules: []

Allow all origins (use with caution):

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: cors-allow-all
  namespace: default
spec:
  headers:
    accessControlAllowMethods:
      - "GET"
      - "POST"
      - "PUT"
      - "DELETE"
      - "OPTIONS"
    accessControlAllowHeaders:
      - "*"
    accessControlAllowOriginList:
      - "*"
    accessControlMaxAge: 86400
    addVaryHeader: true

With credentials support (cannot use * for origin):

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: cors-with-credentials
  namespace: default
spec:
  headers:
    accessControlAllowMethods:
      - "GET"
      - "POST"
      - "OPTIONS"
    accessControlAllowHeaders:
      - "Content-Type"
      - "Authorization"
      - "X-Requested-With"
    accessControlAllowOriginList:
      - "https://app.example.com"
    accessControlAllowCredentials: true
    accessControlExposeHeaders:
      - "X-Custom-Header"
    accessControlMaxAge: 86400
    addVaryHeader: true

Using regex for origin matching:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: cors-regex
  namespace: default
spec:
  headers:
    accessControlAllowMethods:
      - "GET"
      - "POST"
    accessControlAllowOriginListRegex:
      - "https://.*\\.example\\.com"  # Match all subdomains
    accessControlMaxAge: 86400
    addVaryHeader: true

Note

By default we provide a shared CORS middleware in the traefik namespace named cors-permissive, which you can reference from your Ingresses as traefik-cors-permissive@kubernetescrd. This CORS middleware is based upon the old ingress-nginx defaults and meant as a drop-in replacement.

Custom TLS configuration

Upstream documentation

To configure custom TLS options (minimum version, cipher suites), create a TLSOption resource:

---
apiVersion: traefik.io/v1alpha1
kind: TLSOption
metadata:
  name: strict-tls
  namespace: default
spec:
  minVersion: VersionTLS12
  cipherSuites:
    - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
  curvePreferences:
    - CurveP521
    - CurveP384
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.tls.options: default-strict-tls@kubernetescrd
  name: foo
  namespace: default
spec:
  ingressClassName: traefik
  rules: []
  tls:
    - secretName: foo-tls
      hosts:
        - foo.example.com

For TLS 1.3 only:

apiVersion: traefik.io/v1alpha1
kind: TLSOption
metadata:
  name: tls13-only
  namespace: default
spec:
  minVersion: VersionTLS13

TLS/SSL Passthrough

Upstream documentation

To enable TLS/SSL passthrough (for end-to-end TLS), you need to create an IngressRouteTCP resource instead of a normal Ingress as we’ll leave the TLS termination to the backend service instead of Traefik.

Example:

apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
  name: foo
  namespace: default
spec:
  entryPoints:
    - websecure
  routes:
    - match: HostSNI(`foo.example.com`)
      services:
        - name: foo
          port: 8443
  tls:
    passthrough: true
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: foo
  namespace: default
spec:
  ingressClassName: traefik
  rules:
    - host: foo.example.com

Note

The Ingress object is not strictly necessary here, but it allows external-dns to automatically create the DNS record for foo.example.com. At a later time we will try to automate this process for IngressRouteTCP resources as well.

Disable automatic HTTPS redirection

By default, our Traefik controller redirects HTTP traffic to HTTPS. To disable this for a specific Ingress, create an IngressRoute with a higher priority than our default redirection rule (100).

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: foo
  namespace: default
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`foo.example.com`)
      kind: Rule
      priority: 200
      services:
        - name: foo
          port: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: foo
  namespace: default
spec:
  ingressClassName: traefik
  rules:
    - host: foo.example.com

Note

The Ingress object is not strictly necessary here, but it allows external-dns to automatically create the DNS record for foo.example.com. At a later time we will try to automate this process for IngressRoute resources as well.

IP allowlisting

Upstream documentation

Restrict access to specific IP ranges using the IPAllowList middleware:

---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: ip-allowlist
  namespace: default
spec:
  ipAllowList:
    sourceRange:
      - "10.0.0.0/8"
      - "172.16.0.0/12"
      - "192.168.0.0/16"
      - "203.0.113.0/24"
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.middlewares: default-ip-allowlist@kubernetescrd
  name: foo
  namespace: default
spec:
  ingressClassName: traefik
  rules: []

Tip

You can create a shared IPAllowList middleware in a common namespace and reference it across multiple Ingresses. This makes it easier to maintain a central list of allowed IPs.

OAuth / OIDC integration

Upstream documentation

Traefik supports external authentication via the ForwardAuth middleware. This allows integration with OAuth2-Proxy, which we provide by default.

Example using our OAuth2-Proxy service for authentication:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.middlewares: infrastructure-oauth-errors@kubernetescrd,infrastructure-oauth-auth@kubernetescrd
  name: protected-app
  namespace: default
spec:
  ingressClassName: traefik
  rules: []

Rate limiting

Upstream documentation

Traefik has native rate limiting support via the RateLimit middleware:

Basic rate limiting:

---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: ratelimit
  namespace: default
spec:
  rateLimit:
    average: 100   # Average requests per second
    burst: 50      # Maximum burst size
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.middlewares: default-ratelimit@kubernetescrd
  name: foo
  namespace: default
spec:
  ingressClassName: traefik
  rules: []

Rate limiting per client IP:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: ratelimit-per-ip
  namespace: default
spec:
  rateLimit:
    average: 10
    period: 1m     # Per minute instead of per second
    burst: 20
    sourceCriterion:
      ipStrategy: {}

Rate limiting by header (e.g., API key):

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: ratelimit-by-apikey
  namespace: default
spec:
  rateLimit:
    average: 100
    burst: 50
    sourceCriterion:
      requestHeaderName: X-API-Key

Redirects

Upstream documentation | RedirectRegex

Redirect to another domain

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: redirect-domain
  namespace: default
spec:
  redirectRegex:
    regex: "^https://old-domain\\.com/(.*)"
    replacement: "https://new-domain.com/${1}"
    permanent: true

Non-www to www redirect

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: www-redirect
  namespace: default
spec:
  redirectRegex:
    regex: "^https://example\\.com/(.*)"
    replacement: "https://www.example.com/${1}"
    permanent: true

Redirect with path modification

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: redirect-path
  namespace: default
spec:
  redirectRegex:
    regex: "^https://example\\.com/old-path/(.*)"
    replacement: "https://example.com/new-path/${1}"
    permanent: true

Session affinity / stickiness

Upstream documentation

Session affinity in Traefik is configured on the Service resource, not on the Ingress. This is a fundamental difference from ingress-nginx.

Example using sticky session annotations on the Service:

---
apiVersion: v1
kind: Service
metadata:
  annotations:
    traefik.ingress.kubernetes.io/service.sticky.cookie: "true"
    traefik.ingress.kubernetes.io/service.sticky.cookie.name: sticky-app-session
    traefik.ingress.kubernetes.io/service.sticky.cookie.secure: "true"
  name: sticky-app
  namespace: default
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app.kubernetes.io/name: sticky-app
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  name: sticky-app
  namespace: default
spec:
  ingressClassName: traefik
  rules:
    - host: foo.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: sticky-app
                port:
                  name: http
  tls:
    - secretName: foo-example-com-tls
      hosts:
        - foo.example.com

Timeouts

Timeouts in Traefik are configured at different levels depending on the type:

Response forwarding timeout (via ServersTransport)

For backend connection timeouts, create a ServersTransport:

---
apiVersion: traefik.io/v1alpha1
kind: ServersTransport
metadata:
  name: custom-timeouts
  namespace: default
spec:
  serverName: "backend"
  forwardingTimeouts:
    dialTimeout: 30s           # Time to establish connection
    responseHeaderTimeout: 60s # Time to wait for response headers
    idleConnTimeout: 90s       # Keep-alive timeout
---
apiVersion: v1
kind: Service
metadata:
  name: my-app
  namespace: default
  annotations:
    traefik.ingress.kubernetes.io/service.serverstransport: default-custom-timeouts@kubernetescrd
spec:
  ports:
    - port: 80
  selector:
    app: my-app

Request timeout via middleware

For request-level timeouts, you can use a combination of retry and circuit breaker middlewares:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: retry
  namespace: default
spec:
  retry:
    attempts: 3
    initialInterval: 100ms

Note

Traefik doesn’t have direct equivalents for all ingress-nginx proxy timeout annotations. The proxy-read-timeout and proxy-send-timeout are handled via ServersTransport, while request-level timeouts require middleware or service mesh integration.

URL rewriting

Upstream documentation | ReplacePathRegex

Strip path prefix

Remove a prefix from the URL path before forwarding to the backend:

---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: strip-api-prefix
  namespace: default
spec:
  stripPrefix:
    prefixes:
      - "/api"
    # forceSlash: false  # Don't add a trailing slash
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    traefik.ingress.kubernetes.io/router.middlewares: default-strip-api-prefix@kubernetescrd
  name: api
  namespace: default
spec:
  ingressClassName: traefik
  rules:
    - host: example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80

Request to /api/users → Backend receives /users

Strip prefix with regex

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: strip-version-prefix
  namespace: default
spec:
  stripPrefixRegex:
    regex:
      - "/v[0-9]+/"

Request to /v1/users → Backend receives /users

Replace path

Replace the entire path:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: replace-path
  namespace: default
spec:
  replacePath:
    path: "/new-path"

Replace path with regex

For more complex rewriting patterns:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: rewrite-api
  namespace: default
spec:
  replacePathRegex:
    regex: "^/api/v1/(.*)"
    replacement: "/internal/$1"

Request to /api/v1/users → Backend receives /internal/users

Add prefix

Add a prefix to all requests:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: add-prefix
  namespace: default
spec:
  addPrefix:
    prefix: "/backend"

Request to /users → Backend receives /backend/users

Migration from ingress-nginx

When migrating from ingress-nginx to Traefik, use this annotation mapping table:

ingress-nginxTraefik
nginx.ingress.kubernetes.io/ssl-redirect: "true"Default behavior (automatic HTTPS redirect)
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"Default behavior
nginx.ingress.kubernetes.io/rewrite-target: /$1StripPrefix or ReplacePathRegex middleware
nginx.ingress.kubernetes.io/whitelist-source-rangeIPAllowList middleware
nginx.ingress.kubernetes.io/auth-url & nginx.ingress.kubernetes.io/auth-signinForwardAuth + Errors middleware chain, use traefik.ingress.kubernetes.io/router.middlewares: infrastructure-oauth-errors@kubernetescrd,infrastructure-oauth-auth@kubernetescrd annotation
nginx.ingress.kubernetes.io/proxy-body-sizeBuffering middleware
nginx.ingress.kubernetes.io/proxy-read-timeoutServersTransport CRD
nginx.ingress.kubernetes.io/enable-corsHeaders middleware with CORS options
nginx.ingress.kubernetes.io/limit-rpsRateLimit middleware
nginx.ingress.kubernetes.io/affinity: cookieUse traefik.ingress.kubernetes.io/service.sticky.cookie* annotations on the Service
nginx.ingress.kubernetes.io/auth-secret (basic auth)BasicAuth middleware with htpasswd secret
nginx.ingress.kubernetes.io/permanent-redirectRedirectRegex middleware
nginx.ingress.kubernetes.io/ssl-ciphersTLSOption CRD

Warning

Custom nginx snippets (server-snippet, configuration-snippet) have no direct equivalent in Traefik. These require refactoring to use appropriate middleware or moving the logic to the application layer.

Further reading

Last updated on