Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Build and verify Traefik routing rules

By updated

Improve this page

Traefik logo

A Traefik routing rule describes which requests a router answers, and when two rules match the same request, Traefik keeps the one with the highest priority, not the most precise one. This lesson is for anyone who already publishes a service behind Traefik and wants to write safe rules. A router is the object that recognizes a request and hands it to a service; its priority is the number that ranks two routers. The lesson teaches you to choose what a rule tests (host name, path, header, method), to predict which one wins when they overlap, and to prove it with requests. The measurements quoted come from Traefik 3.7.15, the lab version.

What you will learn

The lesson follows the reasoning behind a rule: write it, predict how it is arbitrated, then check it.

  • Write a rule that combines host name, path, header and method.
  • Tell an exact path from a prefix, and predict how prefixes overflow.
  • Predict which router wins between two rules that overlap.
  • Restrict a sensitive application to its host name.
  • Qualify a service or middleware reference with its provider.
  • Prove with a request matrix that a change breaks nothing.

Prerequisites

This lesson starts from Traefik's vocabulary and does not redescribe service discovery or middlewares.

The Docker and file lessons teach how Traefik discovers a service. This one teaches what comes next: how a rule selects its requests, whichever provider declared it.

What can a routing rule test?

A rule is an expression that Traefik evaluates for every request received: if it is true, the router may take the request. Let us restate the words. A router is the object that recognizes a request and hands it to a service, that is, the list of servers (the backends) that answer it. The rule is the router's condition. Each elementary test it contains is called a matcher, such as Host or PathPrefix.

Here are the matchers this lesson uses, all in the v3 syntax described in the concepts lesson. The examples in the last column were tried on Traefik 3.7.15.

MatcherWhat it testsExample
Host(`…`)the requested host name (Host header)Host(`m.lab.test`)
Path(`…`)the path, exactlyPath(`/v2`)
PathPrefix(`…`)the start of the pathPathPrefix(`/api`)
Header(`…`, `…`)a request header and its valueHeader(`X-Env`, `staging`)
Method(`…`)the HTTP methodMethod(`POST`)
Query(`…`, `…`)a query string parameterQuery(`debug`, `1`)

The host name is the one the client writes in the Host header of its request, and it is the client that sets it. This remark matters further down: curl -H 'Host: m.lab.test' http://127.0.0.1/ is enough to impersonate any name, without touching DNS.

Matchers combine with && (AND), || (OR) and ! (NOT), and parentheses group them. The rule Host(`n.lab.test`) && !PathPrefix(`/prive`) takes everything aimed at n.lab.test except what starts with /prive: measured, /ok reaches the backend and /prive gets a 404. Values are written between backticks, as in the concepts lesson.

Where does a PathPrefix really stop?

The PathPrefix(`/api`) matcher compares characters, not path segments: it also takes /apiary. People often think a prefix designates a "folder", like /api/ and everything below it. Traefik applies the most literal definition: does the request path start with these characters?

The measurement confirms it. With the rule Host(`m.lab.test`) && PathPrefix(`/api`), the requests /api/x and /apiary both reach the backend. With Path(`/v2`), which requires equality, /v2 passes and /v2/x gets a 404.

RuleRequested pathMeasured result
PathPrefix(`/api`)/api/x200, router kept
PathPrefix(`/api`)/apiary200, router kept (unexpected)
Path(`/v2`)/v2200, router kept
Path(`/v2`)/v2/x404, no router

This overflow is not a Traefik defect, it is a rule you must write with the right matcher. To take only /api and what lies under /api/, the logical combination is Path(`/api`) || PathPrefix(`/api/`). Choose Path when the path is unique (a /health probe), PathPrefix when a whole subtree must follow, and add the trailing slash of the prefix when the boundary matters.

Above all, remember the method: for every PathPrefix, write a neighboring path that must not pass and try it. It is the only way to see an overflow before a user finds it.

How does Traefik choose between two routers that both match?

When several routers match a request, Traefik sorts them by descending priority and keeps the first one; the others receive nothing, without the slightest message. The priority is a number attached to each router. If you do not write it, Traefik uses the length of the rule in characters, as the concepts lesson explains.

The following diagram shows the path of a request. Look at the central block: the priority sort only looks at that number, never at the precision of the rule.

Arbitration between routers: Traefik evaluates the rule of each router for the request received. If no rule matches, the response is a 404. Otherwise, the routers whose rule matches are sorted by descending priority, using the explicit priority or, failing that, the length of the rule. The first one wins and the request goes to the service of the winning router; the other routers receive nothing and report no error.noneone or moreIncoming requestHost, path, headersTraefik evaluates each router ruleNo rule matches404 responseRouters whose rule matchesSorted by descending priorityexplicit priority, else rule lengthThe first one winsService of the winning routerbackendThe other routersreceive nothing, no error
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

Take two routers aimed at the same host name. The first is generic, the second reserves a sub-path.

# FRAGMENT of dynamic configuration (not standalone)
http:
routers:
boutique:
rule: Host(`boutique.lab.test`)
entryPoints: [web]
service: boutique
boutique-api:
rule: Host(`boutique.lab.test`) && PathPrefix(`/api`)
entryPoints: [web]
service: api

The first rule is 25 characters long, the second 47. Without priority, boutique-api therefore has priority 47 and wins for /api, while boutique receives the rest. The order is the right one, but by arithmetic accident: a longer rule goes first. The measurement on Traefik 3.7.15 shows it with Traefik's API, which displays a priority of 24 for Host(`routage.lab.test`) and of 45 for the same rule followed by && PathPrefix(`/v2`). These numbers differ from the 25 and 47 above because the host name measured is one character shorter: the priority follows the exact length of the rule as written, name included.

The trap appears when the two mechanisms cross. If the generic router carries priority: 100 and the specific router priority: 10, the generic one goes first. Measured: a /v2 request then went to the generic router's backend, with a perfectly normal 200, hence no alert to flag the error.

Should you write an explicit priority?

Write priority when the order you want does not follow from the length of the rules, and only then. This is a judgment of good practice and not a Traefik rule: an explicit priority is a number that nobody rereads, and it must stay consistent with all the other routers on the same entry point.

The case that requires it is the one from the concepts lesson: a short, exact rule that must go before a longer regular expression. Outside that case, letting the length sort things avoids adding an arbitrary number to maintain, provided you check the result. A rule Host(`a`) && PathPrefix(`/b`) is naturally longer than Host(`a`) alone, so it has higher priority.

Two habits avoid most collisions.

  • One method per group of routers. Either all the routers that overlap let the length sort them, or all carry an explicit priority. Mixing the two compares a length to a hand-picked number, and the resulting order becomes hard to read.
  • A gap you can read. If you set priorities, order them in steps (10, 50, 100) and note in a comment why that router goes first, otherwise the next reader will not dare touch it.

After every change of priority, do not just reread the file: send the requests that must reach each router, as in the verification section below.

How do you stop a sensitive application from answering under every name?

A router whose rule contains no Host answers under every host name the client chooses to write. An administration panel published with the single rule PathPrefix(`/admin`) is reachable through /admin on any name that reaches Traefik.

Measured with such a rule: a request with a randomly chosen host name on /admin received a 200 from the administration backend. Once the rule was completed with a Host for the intended name, the same request gets a 404, and only the one that carries the intended name reaches the application.

This 404 is the normal outcome when no router matches: Traefik answers by itself, without consulting any backend. It is useful to know when interpreting a test, because it does not say "forbidden", it says "no rule concerns me".

Keep two limits in mind. First, restricting the host name is not authenticating: since the client writes the Host header, anyone who knows the intended name can write it. The restriction reduces the surface exposed by mistake, it does not replace access control. Second, real protection of a panel goes through middlewares and limited network exposure, which Traefik middlewares and protecting the entry point cover.

How do you reference a service or middleware from another provider?

A service or middleware name without a suffix is looked up in the provider of the router that cites it; to target another provider, you write name@provider. A provider is the source that declares objects to Traefik: a file, Docker labels. Traefik suffixes each object with its provider: @file for a file, @docker for containers. The concepts lesson details this suffix; here it serves to remove an ambiguity.

Imagine a middleware limite declared in a file, and a service catalogue published by the labels of a container. A router in the file that cites service: catalogue looks for catalogue@file. Two situations arise, and they do not produce the same error.

  • An object with the same name exists in the file. Traefik takes it, without complaining. Measured with a service cible declared in the file and another published by Docker: the route answered 502 Bad Gateway (the file's service pointed to an unreachable address), and the middleware applied added the file's header, not the container's. The route seems to work, with the wrong objects.
  • No object with that name exists in the file. The router is marked disabled and answers 404. The log writes an ERR line like the service "inconnu@file" does not exist.

The remedy is to write the suffix of the object you target, for example service: catalogue@docker and middlewares: [limite@file]. It amounts to naming who is responsible for the object: it is a separation of responsibilities between what the file describes and what the containers publish.

The two providers complement each other: the file sets stable rules, Docker publishes services that come and go. To read how each one declares its objects, see the Docker and file lessons.

How do you check that a rule does what you think?

A rule is checked by requests that must pass and requests that must fail, never by rereading the file alone. Since the host name is set on the client side, curl -H 'Host: …' is enough to simulate any name without configuring DNS.

Terminal window
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: m.lab.test' http://127.0.0.1/apiary

This command sends a request to Traefik with the name m.lab.test and prints only the HTTP code. Then check which backend answered: the backends used by the lab are whoami servers, whose response contains a Hostname: line that names the machine that answered. That line says which service the request reached, which a 200 code alone does not.

To build a useful proof, draw up a matrix: one row per (host name, path) pair, with the expected backend. The important rows are the negative cases.

Host namePathExpected
the intended namethe rootthe default backend
the intended namethe reserved path and a sub-paththe path's backend
a randomly chosen namethe sensitive patha 404
the intended namea neighboring path that must not passthe default backend or a 404

Traefik also offers a direct checkpoint: its API lists each router with its rule, its computed priority, its state (enabled or disabled) and, for a disabled router, the cause of the error. Start Traefik with --api.insecure=true: the option opens the API on the entrypoint named traefik, which listens by default on port 8080. The request curl localhost:8080/api/http/routers then returns these fields in JSON, which was measured on 3.7.15 through a test port published by the lab. This option exposes the dashboard without authentication: it suits a local test bench, never an exposed entry point, as protecting the entry point reminds you.

When a request does not go where you expected, do not change several things at once. First look at the state and the priority of the routers aimed at that name, then at the rule that won; the lesson Troubleshooting a Traefik entry point details how to read 404, 502 and 504.

Can adding a route break another one?

Adding an unrelated router breaks neither the existing routes nor the Traefik process, but a router that overlaps an existing route can hijack it. With the file provider, Traefik watches its directory and reloads the configuration on the fly, after a delay of at least 2 seconds by default (providersThrottleDuration), as the file provider lesson shows.

Measured on 3.7.15: adding a second file declaring a router on a new host name left the responses of the two existing routes unchanged, made the new route reachable, and the start time of the Traefik container stayed the same. Nothing was restarted, and nothing broke.

The risk lies in the sort seen above. A new rule that also matches requests already served enters the sort: if it is longer or has higher priority, it goes first, with no error. A new host name is harmless; a broad PathPrefix on a name already in use is not.

A good practice, which is a matter of judgment: after every addition, replay the complete matrix and not just the row for the new route. The old rows that had to stay stable are the ones that detect a hijack.

Hands-on: resolve routing collisions

The lab sets up a Traefik whose routes are misconfigured: a priority that prevents a more specific route from being reached, an administration application that answers under every name, and unqualified service and middleware references to the Docker provider. You fix the routes file, then prove that adding an unrelated route disturbs nothing.

The validation sends real requests to port 80 of the machine and reads the response code, the backend that answers and a header. It checks four things: the root and the reserved path each reach their backend, the catalog answers with the header coming from Docker, the administration application is reachable only under its intended name, and a router added at runtime leaves the other routes intact without restarting Traefik. The lab scenario gives the mission, not the solution.

Key points

A routing rule is judged by its effects on requests, not by reading it.

  • A rule combines matchers (Host, Path, PathPrefix, Header, Method, Query) with &&, || and !.
  • The PathPrefix(`/api`) matcher also takes /apiary: use Path for the exact path, a trailing slash for the boundary.
  • Between two routers that match, the highest priority wins; without priority, it is the length of the rule, not its precision.
  • A router without Host answers under every name; restricting the name reduces exposure but does not authenticate.
  • A name without a suffix is looked up in the router's provider: write name@provider to target another provider.
  • Prove with a request matrix, negative cases included, and replay it after every added route.

Knowledge check

The quiz covers reading a rule, arbitration between routers, restriction by host name and qualification of references.

Knowledge check

Check what you have learned with this interactive quiz

8 questions
8 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn