Multi-Layer Routing¶
Hierarchical Router Relationships for Advanced Routing Scenarios.
Overview¶
Multi-layer routing enables you to create hierarchical relationships between routers, where parent routers can process requests through middleware before child routers make final routing decisions.
This feature allows middleware at the parent level to modify requests (adding headers, performing authentication, etc.) that influence how child routers evaluate their rules and route traffic to services.
Multi-layer routing is particularly useful for progressive request enrichment, where each layer adds context to the request, enabling increasingly specific routing decisions:
- Authentication-Based Routing: Parent router authenticates requests and adds user context (roles, permissions) as headers, child routers route based on these headers
- Staged Middleware Application: Apply common middleware (rate limiting, CORS) at parent level (for a given domain/path), but specific middleware at child level
Provider Support
Multi-layer routing is supported by the following providers:
- File provider (YAML, TOML, JSON)
- KV stores (Consul, etcd, Redis, ZooKeeper)
- Kubernetes CRD (IngressRoute)
Multi-layer routing is not available for other providers (Docker, Kubernetes Ingress, Gateway API, etc.).
How It Works¶
Request → EntryPoint → Parent Router → Middleware → Child Router A → Service A
↓ → Child Router B → Service B
Modify Request
(e.g., add headers)
- Request arrives at an entrypoint
- Parent router matches based on its rule (e.g.,
Host(`example.com`)) - Parent middleware executes, potentially modifying the request
- One child router matches based on its rule (which may use modified request attributes)
- Request is forwarded to the matching child router's service
Building a Router Hierarchy¶
Root Routers¶
- Have no
parentRefs(top of the hierarchy) - Can have
tls,observability, andentryPointsconfiguration - Can be either parent routers (with children) or standalone routers (with service)
- Can have models applied (non-root routers cannot have models)
Intermediate Routers¶
- Reference their parent router(s) via
parentRefs - Have one or more child routers
- Must not have a
servicedefined - Must not have
entryPoints,tls, orobservabilityconfiguration
Leaf Routers¶
- Reference their parent router(s) via
parentRefs - Must have a
servicedefined - Must not have
entryPoints,tls, orobservabilityconfiguration
Configuration Example¶
Authentication-Based Routing
## Dynamic configuration
http:
routers:
# Parent router with authentication
api-parent:
rule: "PathPrefix(`/api`)"
middlewares:
- auth-middleware
entryPoints:
- websecure
tls: {}
# Note: No service defined - this is a parent router
# Child router for admin users
api-admin:
rule: "HeadersRegexp(`X-User-Role`, `admin`)"
service: admin-service
parentRefs:
- api-parent
# Child router for regular users
api-user:
rule: "HeadersRegexp(`X-User-Role`, `user`)"
service: user-service
parentRefs:
- api-parent
middlewares:
auth-middleware:
forwardAuth:
address: "http://auth-service:8080/auth"
authResponseHeaders:
- X-User-Role
- X-User-Name
services:
admin-service:
loadBalancer:
servers:
- url: "http://admin-backend:8080"
user-service:
loadBalancer:
servers:
- url: "http://user-backend:8080"## Dynamic configuration
[http.routers]
# Parent router with authentication
[http.routers.api-parent]
rule = "PathPrefix(`/api`)"
middlewares = ["auth-middleware"]
entryPoints = ["websecure"]
[http.routers.api-parent.tls]
# Note: No service defined - this is a parent router
# Child router for admin users
[http.routers.api-admin]
rule = "HeadersRegexp(`X-User-Role`, `admin`)"
service = "admin-service"
parentRefs = ["api-parent"]
# Child router for regular users
[http.routers.api-user]
rule = "HeadersRegexp(`X-User-Role`, `user`)"
service = "user-service"
parentRefs = ["api-parent"]
[http.middlewares]
[http.middlewares.auth-middleware.forwardAuth]
address = "http://auth-service:8080/auth"
authResponseHeaders = ["X-User-Role", "X-User-Name"]
[http.services]
[http.services.admin-service.loadBalancer]
[[http.services.admin-service.loadBalancer.servers]]
url = "http://admin-backend:8080"
[http.services.user-service.loadBalancer]
[[http.services.user-service.loadBalancer.servers]]
url = "http://user-backend:8080"| Key | Value |
|------------------------------------------------------------------------|---------------------------------|
| `traefik/http/routers/api-parent/rule` | `PathPrefix(\`/api\`)` |
| `traefik/http/routers/api-parent/middlewares/0` | `auth-middleware` |
| `traefik/http/routers/api-parent/entrypoints/0` | `websecure` |
| `traefik/http/routers/api-parent/tls` | `true` |
| `traefik/http/routers/api-admin/rule` | `HeadersRegexp(\`X-User-Role\`, \`admin\`)` |
| `traefik/http/routers/api-admin/service` | `admin-service` |
| `traefik/http/routers/api-admin/parentrefs/0` | `api-parent` |
| `traefik/http/routers/api-user/rule` | `HeadersRegexp(\`X-User-Role\`, \`user\`)` |
| `traefik/http/routers/api-user/service` | `user-service` |
| `traefik/http/routers/api-user/parentrefs/0` | `api-parent` |
| `traefik/http/middlewares/auth-middleware/forwardauth/address` | `http://auth-service:8080/auth` |
| `traefik/http/middlewares/auth-middleware/forwardauth/authresponseheaders/0` | `X-User-Role` |
| `traefik/http/middlewares/auth-middleware/forwardauth/authresponseheaders/1` | `X-User-Name` |
| `traefik/http/services/admin-service/loadbalancer/servers/0/url` | `http://admin-backend:8080` |
| `traefik/http/services/user-service/loadbalancer/servers/0/url` | `http://user-backend:8080` |How it works:
- Request to
/api/endpointmatchesapi-parentrouter auth-middleware(ForwardAuth) validates the request and addsX-User-Roleheader- Modified request is evaluated by child routers
- If
X-User-Role: admin,api-adminrouter matches and forwards toadmin-service - If
X-User-Role: user,api-userrouter matches and forwards touser-service
Using Traefik OSS in Production?
If you are using Traefik at work, consider adding enterprise-grade API gateway capabilities or commercial support for Traefik OSS.
Adding API Gateway capabilities to Traefik OSS is fast and seamless. There's no rip and replace and all configurations remain intact. See it in action via this short video.