# Fix Traefik 404s and hijacked routes: rule matching, priority, and PathPrefix traps

Fix Traefik 404s and wrong-service routing: distinguish no-router-matched from wrong-router-won, set explicit router priority (default is rule length, longest wins), avoid the PathPrefix raw-string-prefix trap, handle Host wildcards and catch-alls, and check entrypoint/TLS binding.

Exact reference: {"kind":"skill_version","skill_id":"skl_agQDxPkCch5ylGzR-GsbSg","version_id":"skv_3iyoNtneaXOji3mFs3A5Zg"}

Applicability: [{"constraint":">=2.0","technology":"Traefik","version_scheme":"semver"}]

# Fix Traefik 404s and hijacked routes: rule matching, priority, and PathPrefix traps

Use this when requests get `404 page not found` from Traefik, or when
traffic lands on the wrong service even though every router "looks right".
Traefik picks the winning router by **priority**, and the default priority
is the **length of the rule string** — so a longer, sloppier rule silently
outranks the exact route you meant. Classify the 404 first, then fix the
rule or the priority.

## 1. Classify the 404: no router matched vs wrong router won

Traefik's own response when **no router matches** is a plain
`404 page not found` produced at the entrypoint — no upstream is ever
contacted. That is a different problem from a 404 returned by your app
(right service, wrong path).

- `curl -v` shows `404` with an empty/short body and the access log shows
  no backend request → **no router matched**. Check rule syntax and
  entrypoint binding (steps 2 and 5).
- The request reaches a service but the wrong one (assets load from the
  wrong app, auth headers appear on the wrong host) → a **broader rule
  outranked your route**. Check priority and PathPrefix (steps 3 and 4).
- The dashboard (or `GET /api/http/routers`) shows each router's rule and
  its computed priority — read that table before changing anything.

## 2. Rule syntax: backticks, v3 vs v2

- Rule values use **backticks**, not single quotes:
  `Host(`app.example.com`)`. Single quotes are rejected — the values are
  Go string literals.
- `Host(`*.example.com`)` matches exactly **one** subdomain label:
  `foo.example.com` yes; `example.com` itself no; `foo.bar.example.com`
  no. A bare `Host(`*`)` is a **catch-all** matching every request
  regardless of host.
- Traefik v3 parses rules with the **v3 syntax by default**. Old
  copy-pasted v2 regexes like ``HostRegexp(`{subdomain:[a-z]+}.example.com`)``
  silently stop matching under v3; the v3 equivalent is
  ``HostRegexp(`[a-z]+\.example\.com`)``. If you must keep v2 rules, set
  `ruleSyntax: v2` on that router (dynamic config).

## 3. Priority: longest rule wins unless you say otherwise

> "To avoid path overlap, routes are sorted, by default, in descending
> order using rules length. The priority is directly equal to the length of
> the rule, and so the longest length has the highest priority."

The docs' own example: a router with
``rule: HostRegexp(`[a-z]+\.traefik\.com`)`` (priority **34**) steals
`foobar.traefik.com` from a router with ``rule: Host(`foobar.traefik.com`)``
(priority **26**) — the catch-all regex wins purely by being longer.
The fix is an explicit priority:

```yaml
http:
  routers:
    catchall:
      rule: "HostRegexp(`[a-z]+[.]example[.]com`)"
      priority: 1          # stays below everything specific
      service: fallback
    exact:
      rule: "Host(`foobar.example.com`)"
      priority: 10         # wins for its own host
      service: foobar
```

Rules:

- Set explicit priorities wherever a general rule and a specific rule can
  both match. The catch-all gets the lowest number.
- `priority: 0` is **ignored** — it falls back to the rule-length default.
  Use 1 (or negative values, which are supported) for a true bottom.
- When two routers from **different providers** tie on priority, the
  `providers.precedence` static option breaks the tie (first listed wins).

## 4. PathPrefix is a raw string prefix, not segment-aware

From the docs: ``PathPrefix(`/products`)`` matches `/products`,
`/products/shoes`, `/products/` — **and `/products-for-sale`**.
This is the most common way a new route silently captures a neighbor's
traffic: `PathPrefix(`/rag`)` also matches `/rag-web-api`.

Defenses, in order of preference:

- Prefer a trailing-slash prefix for subtrees:
  ``PathPrefix(`/rag/`)`` does not match `/rag-web-api`. (Add a second
  router with ``Path(`/rag`)`` if the bare path itself must route.)
- Use ``Path(`/exact`)`` for single endpoints.
- Use ``PathRegexp(`^/rag(/|$)`)`` when you need a real segment boundary.
- If two routers must overlap, give the more specific one the higher
  explicit `priority` (step 3) instead of hoping rule length saves you.

## 5. Entrypoint binding: the router must listen where the request arrives

A router only sees traffic on its declared `entryPoints`. If the router
isn't in the dashboard's list for the entrypoint the request hits, the
rule never gets evaluated:

```yaml
# labels
- "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls=true"
```

- For HTTPS (`:443`) traffic the router needs TLS enabled
  (`tls=true` label / `tls: {}` in file config); a plain-HTTP rule on the
  `websecure` entrypoint won't match the TLS handshake.
- A router with no `entryPoints` set listens on **all** entrypoints —
  convenient, but also how a debug router accidentally answers production
  traffic. Declare entrypoints explicitly.

## 6. Checklist

1. `curl -v`: Traefik's bare `404 page not found` with no backend hit →
   no router matched; app's 404 → wrong path/service after routing.
2. Dashboard router table: read the rule **and the computed priority**
   for every router on the entrypoint.
3. Longer general rule outranking a specific one → set explicit
   `priority` (catch-all lowest; never rely on `priority: 0`).
4. Neighbor traffic captured → `PathPrefix` is a raw string prefix;
   use trailing-slash prefixes, `Path()`, or `PathRegexp`.
5. Router not evaluated at all → check `entrypoints` list and `tls`
   for `:443`; check backtick quoting and v2-vs-v3 rule syntax.


## Supporting basis and limitations

Built from the official Traefik rules-and-priority reference (default priority equals rule length, priority 0 ignored, PathPrefix raw-prefix table, Host wildcard semantics, v3 rule syntax) and real-world catch-all/priority patterns from operator runbooks.

## Change and rationale

New skill: fix Traefik 404s and hijacked routes via rule matching and priority.

Router 404s and traffic hijacks are among the most confusing Traefik issues because the default priority (rule-string length) is invisible in most configs and PathPrefix is a raw string prefix, not segment-aware. A classify-first procedure — no match vs wrong winner — points at syntax/entrypoints versus priority/prefix, which are fixed completely differently.
