Skip to main content
  1. Posts/
  2. Traefik/

Middlewares in Traefik - what they are, why you need them, and the full list for a homelab

·2051 words·10 mins· loading · loading · ·
Stilicho2011
Author
Stilicho2011
Writing about homelab, self-hosting, automation and open-source solutions
Table of Contents
Traefik - This article is part of a series.
Part : This Article

What middleware in Traefik is
#

When a request reaches the Traefik reverse proxy, it passes through a processing chain before reaching the actual service (your application behind the proxy) - or instead gets a response directly from Traefik itself, without ever reaching the backend. Middleware is exactly the mechanism that lets you intervene in the request or response along this path: modify headers, reject the request, throttle the rate of requests, require authentication, rewrite the path, and so on.

Essentially, middleware in Traefik is a direct analogue of middleware in any web framework (Express, Django, ASP.NET) - just at the reverse-proxy level rather than the application level. The difference matters: Traefik’s middleware runs before the request ever reaches your service, which means it can protect an application that has no protection of its own (no authentication, no rate limiting, no header validation).

Why bother, if you can configure all of this in the application itself
#

Formally, a lot of what middleware does can also be implemented on the application side. But there are reasons why doing it at the proxy level is more convenient and more correct from a management and security standpoint:

  • Centralization. Rate limiting, basic authentication, security headers - if all of this is configured once in Traefik, you don’t need to repeat the logic in every individual application, some of which don’t even offer that capability.
  • Consistency. The same policy (for example, security headers) is applied to all services at once, without the risk of forgetting it somewhere.
  • Protecting what can’t protect itself. Many self-hosted applications (dashboards, simple utilities) have no built-in authentication at all - a middleware like forwardAuth or basicAuth in front of such a service closes that gap without touching the application’s own code.
  • Separation of concerns. The application handles its own direct task, while questions like “who is allowed in here” and “how often can they knock” are resolved at the infrastructure level.

How middleware is applied to a request
#

Middleware doesn’t work on its own - it’s declared in the dynamic config, and then attached to a specific router by name:

http:
  routers:
    my-service:
      rule: "Host(`app.example.com`)"
      middlewares:
        - default-headers
        - rate-limit
      service: my-service

  middlewares:
    default-headers:
      headers:
        frameDeny: true
    rate-limit:
      rateLimit:
        average: 100

Order in the list matters. Middlewares are applied sequentially, top to bottom - the request passes through default-headers, then through rate-limit, and only after that (if none of the middlewares rejected it) reaches the service. The response goes in the reverse order. This matters, for example, for authentication - a middleware that checks access should generally come before ones that simply modify headers, otherwise an unauthorized request might manage to touch something before it’s rejected.

The same middleware can be reused across any number of routers - you declare it once and attach it anywhere by name. When using multiple providers (file provider, Docker provider), the provider suffix is appended to the name: authentik@file, redirect@docker - this is how Traefik distinguishes between identically named middlewares declared in different backends.

Defining and applying are not the same thing
#

Middleware is always defined in the dynamic config - its type and parameters (headers, rateLimit, forwardAuth, etc.) can’t be described directly in the static config (traefik.yaml); that option simply doesn’t exist there. But an already-defined middleware can be applied in two different ways.

Selectively, to a specific router - as in the example above:

# dynamic config
http:
  routers:
    my-service:
      middlewares:
        - default-headers
        - authentik

In this example we first apply the standard request headers, and then redirect the request to Authentik

Globally, via the entryPoint in the static config. In traefik.yaml you can reference a middleware by name (with the provider suffix), and it will then be applied to all requests passing through that entrypoint, before Traefik even figures out which router the request belongs to:

# traefik.yaml (static config)
entryPoints:
  websecure:
    address: ":443"
    http:
      middlewares:
        - rate-limit@file

The middleware is still defined in the dynamic config - the static config only holds a reference to its name. This approach is convenient for things that should apply uniformly to all traffic (rate limit, the CrowdSec bouncer) - you don’t need to list them in the middlewares: section of every individual service router, since they’re already applied globally through the entrypoint binding.

What a Chain is
#

A Chain is a middleware that itself consists of other middlewares:

http:
  middlewares:
    secured:
      chain:
        middlewares:
          - default-whitelist
          - default-headers

You attach the secured chain to a router - and get both (default-whitelist, then default-headers) applied in the given order, in one line. Convenient when the same combination of several middlewares repeats across many routers - instead of listing the whole set every time, you reference a single chain.

Open-source version vs Traefik Hub
#

Some of the middlewares in the Traefik documentation are marked as available only in Traefik Hub (the paid add-on/Enterprise) - for example, JWT, OIDC, OAuth2, OPA, WAF, LDAP, HMAC, APIKey, Distributed RateLimit. Below are only the middlewares available in the open-source version that most homelab setups (including mine) actually use.


Full list of middlewares
#

AddPrefix
#

Adds a prefix to the request path before it goes to the backend. Useful when the backend expects a path like /api/..., but you want to expose it without the prefix externally.

http:
  middlewares:
    add-prefix-example:
      addPrefix:
        prefix: "/api"

BasicAuth
#

Classic HTTP Basic Authentication - a login/password dialog in the browser. The password hash is generated via htpasswd (from the apache2-utils package):

htpasswd -nB username
http:
  middlewares:
    basic-auth-example:
      basicAuth:
        users:
          - "username:$2y$05$..."
        realm: "Restricted"

I use this to protect the Traefik dashboard itself - a simple and reliable fallback that doesn’t depend on an external identity provider being available.

Buffering
#

Buffers the request/response body before passing it on - useful for applications with large files (document, photo, video uploads) and for configuring retries on network errors.

http:
  middlewares:
    middlewares-buffering:
      buffering:
        maxRequestBodyBytes: 10485760
        memRequestBodyBytes: 2097152
        maxResponseBodyBytes: 10485760
        memResponseBodyBytes: 2097152
        retryExpression: "IsNetworkError() && Attempts() <= 2"

I keep this as a template for services with large uploads (Nextcloud, Immich, OnlyOffice), though it isn’t attached to any of them directly yet.

Chain
#

We already covered this above - it combines several middlewares under one name.

http:
  middlewares:
    secured:
      chain:
        middlewares:
          - default-whitelist
          - default-headers

CircuitBreaker
#

Automatically cuts off traffic to a service if it starts returning mass errors or slowing down - protects the rest of the system from a cascading effect caused by one failing backend.

http:
  middlewares:
    circuit-breaker-example:
      circuitBreaker:
        expression: "NetworkErrorRatio() > 0.30 || ResponseCodeRatio(500, 600, 0, 600) > 0.25"
        checkPeriod: "10s"
        fallbackDuration: "30s"
        recoveryDuration: "30s"

Compress
#

Compresses the response body (gzip) before sending it to the client - saves bandwidth. It’s worth excluding already-compressed formats (images, video) - compressing them again is pointless, they’re already compressed, and it just wastes CPU cycles.

http:
  middlewares:
    compress:
      compress:
        excludedContentTypes:
          - "image/png"
          - "image/jpeg"
          - "image/webp"
          - "video/mp4"

ContentType
#

In Traefik v3, Content-Type auto-detection is enabled by default - this middleware is only needed if you want to explicitly disable auto-detection for a specific service.

http:
  middlewares:
    content-type-example:
      contentType: {}

DigestAuth
#

Similar to BasicAuth, but the password is never transmitted in plain form, even within the HTTP scheme (a hash-based challenge-response is used).

http:
  middlewares:
    digest-auth-example:
      digestAuth:
        users:
          - "username:Restricted:ha1_hash"
        realm: "Restricted"

Errors (Custom Error Pages)
#

Replaces the default Traefik/backend error page with your own - serving it from a separate service (for example, a static page).

http:
  middlewares:
    error-pages-example:
      errors:
        status:
          - "500-599"
        service: error-pages-service
        query: "/{status}.html"

ForwardAuth
#

One of the most useful middlewares for a homelab with SSO. Redirects each incoming request to an external authentication service (in my case, Authentik) before letting it through. If the external service responds with success - the request goes to the real backend, with headers added from the response (username, groups, etc.); if not - Traefik itself returns the authentication response (redirect to login), without touching the backend at all.

http:
  middlewares:
    authentik:
      forwardAuth:
        address: "http://10.10.10.10:9000/outpost.goauthentik.io/auth/traefik"
        trustForwardHeader: true
        maxResponseBodySize: 1048576
        authResponseHeaders:
          - X-authentik-username
          - X-authentik-groups
          - X-authentik-email
          - X-authentik-name
          - X-authentik-uid

An important nuance: this is only needed for applications that don’t know how to talk to an identity provider directly (via OIDC). If an application supports an OIDC client itself (as many modern self-hosted services do) - forwardAuth isn’t needed; the application is configured directly against Authentik as an Identity Provider, without Traefik being involved in the authentication chain.

It should also be noted that not every authentication service can work with forwardAuth directly. Authentik and Authelia can; Keycloak, as of the time of writing, doesn’t have this built in out of the box.

GrpcWeb
#

Relevant only if you’re proxying a grpc-web client to a regular gRPC backend - converts the protocol on the fly.

http:
  middlewares:
    grpc-web-example:
      grpcWeb:
        allowOrigins:
          - "*"

Headers
#

One of the most commonly used middlewares - manages HTTP headers on the request and response: security headers (HSTS, X-Frame-Options, CSP), custom headers, CORS.

http:
  middlewares:
    default-headers:
      headers:
        frameDeny: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15552000
        customFrameOptionsValue: SAMEORIGIN
        customRequestHeaders:
          X-Forwarded-Proto: https

I keep two baseline variants - a lighter one for most services and a hardened one (hiding the server version, longer HSTS for the preload list) for more sensitive cases - plus separate variants for specific applications that need a non-standard CSP (for example, to embed a document editor in an iframe from a different domain).

InFlightReq
#

Limits the number of concurrent connections (as opposed to rate limit, which limits requests per second). Useful for resource-intensive endpoints - for example, video transcoding in Immich (Plex or Jellyfin can already limit concurrent streams on their own).

http:
  middlewares:
    in-flight-req-example:
      inFlightReq:
        amount: 20

IPAllowList
#

Restricts access by a list of allowed IPs/subnets.

http:
  middlewares:
    default-whitelist:
      IPAllowList:
        sourceRange:
          - "10.0.0.0/8"
          - "192.168.0.0/16"
          - "172.16.0.0/12"

All of my services are locked behind authentication, so this middleware isn’t currently attached anywhere - I keep it as a ready-to-use tool just in case.

PassTLSClientCert
#

Relevant for mTLS scenarios, where the backend itself wants to see the client certificate in a header.

http:
  middlewares:
    pass-tls-client-cert-example:
      passTLSClientCert:
        pem: true

RateLimit
#

Limits the request rate from a single client (by default, by source IP) - protection against flooding/DDoS.

http:
  middlewares:
    rate-limit:
      rateLimit:
        average: 100
        burst: 50

I have this attached globally on both entrypoints (web/websecure), rather than selectively per service - a general line of defense for all traffic.

RedirectScheme
#

Redirects a request from one scheme to another - most often HTTP → HTTPS.

http:
  middlewares:
    https-redirect:
      redirectScheme:
        scheme: https
        permanent: true

RedirectRegex
#

The same thing, but with an arbitrary regex pattern for more complex redirects, not just scheme changes.

http:
  middlewares:
    redirect-regex-example:
      redirectRegex:
        regex: "^https://(.*)/old-path"
        replacement: "https://${1}/new-path"
        permanent: true

ReplacePath
#

Completely replaces the request path before passing it to the backend.

http:
  middlewares:
    replace-path-example:
      replacePath:
        path: "/new/path"

ReplacePathRegex
#

The same thing via regex, with the ability to capture and reuse parts of the original path.

http:
  middlewares:
    replace-path-regex-example:
      replacePathRegex:
        regex: "^/old/(.*)"
        replacement: "/new/$${1}"

Retry
#

Retries the request on a network error up to a set number of attempts - unlike retryExpression inside buffering, this is a separate, more general mechanism.

http:
  middlewares:
    retry-example:
      retry:
        attempts: 3
        initialInterval: "100ms"

StripPrefix
#

Removes a given prefix from the path before passing it to the backend - the reverse of AddPrefix.

http:
  middlewares:
    strip-prefix-example:
      stripPrefix:
        prefixes:
          - "/api"

StripPrefixRegex
#

The same thing, but using a regex pattern.

http:
  middlewares:
    strip-prefix-regex-example:
      stripPrefixRegex:
        regex:
          - "^/api/v[0-9]+"

How I organized middleware in my own setup
#

Each middleware is a separate .yaml file at /etc/traefik/dynamic/middlewares/, with the filename matching its purpose. Some middlewares are actually attached to services (default-headers, rate-limit, https-redirect, the authentik forwardAuth), while some I keep as a ready-made library on standby - for whenever a specific scenario comes up (a whitelist for admin panels, buffering for uploads, an in-flight limit for transcoding), without having to invent the config from scratch on the spot.

The key benefit of this approach is that when adding a new service, I don’t need to think “how does this middleware even get written” - I just grab the ready-made file, copy the relevant lines into services/<new-service>.yaml, and attach it by name.

Links#

Traefik - This article is part of a series.
Part : This Article

Related

Traefik in Docker: setting up a reverse proxy from scratch

··2395 words·12 mins· loading · loading
A step-by-step guide to installing and configuring Traefik in Docker to set up a reverse proxy and manage web traffic. Covers container configuration, routing, SSL integration, and recommendations for managing web services.