# Fix Traefik middlewares that don't run: provider namespaces, chain order, and the headers overwrite trap
Use this when a Traefik middleware seems to do nothing, the dashboard shows
the router disabled, or chaining two middlewares gives surprising results.
Middleware failures fall into three distinct classes — a dead reference, a
wrong order, or a merge that never happens — and each has a different fix.
## 1. Classify: dead reference, wrong order, or silent overwrite
Check the dashboard (or `GET /api/http/routers`) for your router:
- Router **disabled** with an error like
`middleware "authelia-forwardauth@file" does not exist` → **dead
reference**. Go to step 2.
- Router enabled, middleware listed, but auth/redirect/rate-limit never
triggers → the middleware is attached to the **wrong router**, or a
**chain runs in an unexpected order**. Go to step 3.
- Router enabled, two `headers` middlewares chained, but only one set of
headers appears on the response → the **headers overwrite trap**. Go to
step 4.
- Error `Router defined multiple times with different configurations` →
the same router name is defined on two containers with different
middleware lists. Go to step 5.
## 2. Dead reference: the `@provider` namespace
Every middleware lives in a provider namespace, and a bare name resolves in
the **router's own provider**. A router built from Docker labels that
references a middleware defined in the file provider must qualify it:
```yaml
# WRONG: resolves to authelia@docker, which does not exist -> router disabled
- "traefik.http.routers.myapp.middlewares=authelia"
# RIGHT: the middleware is defined in the file provider
- "traefik.http.routers.myapp.middlewares=authelia@file"
```
Rules:
- From Docker labels, file-provider middlewares need the `@file` suffix;
Docker-defined middlewares referenced from the file provider need
`@docker`.
- The error names the exact missing reference
(`middleware "authelia-forwardauth@file" does not exist`) — trust it and
check that the defining file is actually loaded (mounted, valid YAML,
watched by the file provider).
- Middleware names are global within a provider. Defining the same
middleware name in two providers creates two distinct middlewares.
## 3. Order: chains run in list order on the request path
Middlewares listed on a router execute **in the order they are listed**
for the request; a `chain` middleware runs its members in its own list
order. Response-side effects (response headers) apply in reverse as the
response unwinds.
```yaml
# request passes simple_ratelimit first, then digest_auth, then the service
- "traefik.http.middlewares.secured_chain.chain.middlewares=simple_ratelimit,digest_auth"
- "traefik.http.middlewares.simple_ratelimit.ratelimit.average=5"
- "traefik.http.middlewares.simple_ratelimit.ratelimit.period=5s"
- "traefik.http.middlewares.simple_ratelimit.ratelimit.burst=2"
- "traefik.http.routers.whoami_route.middlewares=secured_chain"
```
Practical consequences:
- Put **auth before rate limiting** if unauthenticated requests shouldn't
consume quota — or rate limit first if you want to blunt brute force.
The choice is yours, but it must be deliberate: list order is the policy.
- A `redirectRegex`/`redirectScheme` middleware **before** auth means
redirects happen without credentials; after auth means the redirect only
fires for authenticated requests.
- The `chain` type is just a named list — it has no other semantics. Any
middleware referenced inside a chain follows the same `@provider`
namespace rule as step 2.
## 4. The headers overwrite trap: `Set`, not merge
The `headers` middleware applies Go's header `Set` semantics: for each
configured header it **overwrites** any existing value. It never appends and
never merges. Consequences that bite people:
- Two chained `headers` middlewares that set the **same header key** —
the later one in the chain wins, silently.
- A `headers` middleware that sets `Content-Security-Policy` **replaces**
the backend's own `Content-Security-Policy` response header; there is no
option to concatenate the two values.
- Fix: combine all the headers for one concern into a **single**
`headers` middleware (one global security-headers middleware, one
per-service middleware only when its keys are disjoint), e.g.:
```yaml
# file provider: one middleware holding all security headers
http:
middlewares:
secure-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
contentTypeNosniff: true
referrerPolicy: "strict-origin-when-cross-origin"
customResponseHeaders:
X-Robots-Tag: "none,noarchive"
```
The dashboard showing both middlewares as "enabled, no errors" does not
contradict this — both run; the later `Set` simply wins.
## 5. Router defined multiple times
Middlewares attach to a **router**, not a host. If two containers define
the same router name (`traefik.http.routers.backend...`) with different
middleware lists, Traefik rejects the conflict:
```
Router defined multiple times with different configurations
```
Fix: give each container's router a unique name
(`traefik.http.routers.backend1`, `traefik.http.routers.backend2`), each
with its own middleware list. Containers behind one router must share the
identical router configuration, including middlewares.
## 6. Checklist
1. Dashboard/API: is the router enabled? Copy the exact error text.
2. `middleware "X@provider" does not exist` → qualify the reference with
`@file`/`@docker` and confirm the defining config is loaded (step 2).
3. Middleware runs but order matters (auth vs rate limit vs redirect) →
reorder the list; the list is the policy (step 3).
4. Chained `headers` middlewares clobbering each other → merge into one
`headers` middleware per key-space; remember `Set`, never merge (step 4).
5. `Router defined multiple times` → unique router names per container,
middleware lists identical per router (step 5).