
Traefik is a reverse proxy that sorts everything it does into five objects: the entrypoint listens, the router chooses, the middleware transforms, the service distributes, and the provider supplies the configuration. This lesson, written for administrators, ops engineers and developers who publish services, puts these five words on the request path you already know. It also explains what Traefik reloads on the fly and what it reads only at startup, then translates it into HAProxy and Nginx vocabulary. It targets version 3.7.15 (released 2026-10-15); the hands-on side, with a lab, is the lesson Traefik with Docker.
What you will learn
- Map each step of the path of a request to the Traefik concept that carries it
- Write a router, a service and a middleware, as a file or as Docker labels
- Distinguish static configuration from dynamic configuration
- Recognize an object's provider from the suffix of its name
- Translate Traefik's vocabulary into that of HAProxy and Nginx
Prerequisites
This lesson builds on two lessons of the "Understand" module:
- Publish a web service: the path of a request to the backend defines the reverse proxy, SNI, the Host header, TLS termination and probes: they are not repeated here;
- Automatic certificates: ACME, HTTP-01, TLS-ALPN-01 and DNS-01 challenges explains how a certificate is obtained from an authority; Traefik groups its settings into a "resolver", defined below.
To place the role of a reverse proxy within an architecture, read Reverse proxy: definition, role and which tool to choose. You also need to be able to read a YAML file and to have already started a Docker container.
To replay the Compose example in the providers section, you need the docker compose plugin (Compose v2 or later; tested with 5.6.0), curl and port 80 free on the machine.
How does Traefik split the path of a request?
Traefik turns each step of a request's path into a named configuration object: entrypoint, router, middleware, service. A fifth object, the provider, is not on the path: it feeds the configuration. The diagram follows a request from left to right and shows where each object gets its configuration.
The dotted arrows show where the configuration comes from. The static configuration, read at startup, sets the entrypoint (the listening port) and enables the providers. These then supply the dynamic configuration (routers, middlewares and services), and this is what changes when you deploy a container. The table links each object to the question that the "Understand" module already asked you.
| Traefik object | Question on the path that it answers | Configuration |
|---|---|---|
| Entrypoint | On which port and protocol does the connection arrive? | static |
| Router | Which route matches the requested name (Host) and path? Is TLS needed? | dynamic |
| Middleware | Must the request or response be modified before or after the backend? | dynamic |
| Service | Which backends receive the request, and which are healthy? | dynamic |
| Provider | Where do routers, services and middlewares come from? | static |
What is an entrypoint?
An entrypoint is a named listening point: an address, a port, a protocol. It knows nothing about routing; it receives the connection and hands it to the routers that selected it. The names web and websecure are a convention, not a reserved word: you can name them whatever you like, and routers will refer to them by that name.
# traefik.yaml (static configuration)entryPoints: web: address: ":80" websecure: address: ":443"An entrypoint belongs to the static configuration, because changing a listening port means reopening a socket (the door the system opens on that port). A Traefik restart is therefore the price of any change here. The redirect from web to websecure, which avoids exposing plain HTTP, is set in this same configuration; its details are in the lesson Traefik SSL/TLS.
What is a router?
A router associates a matching rule with a service, on one or more entrypoints. It is the object that applies, to the letter, the Host decision seen in the lesson on the request path: it reads the name and path of the request and designates the service that will handle it. It also carries the TLS settings, with the name of the ACME resolver: the group of settings (authority, challenge type, storage) that will obtain the certificate.
# FRAGMENT of dynamic configuration, as a file (not standalone)http: routers: api: rule: "Host(`api.example.com`) && PathPrefix(`/v1`)" entryPoints: - websecure middlewares: - rate-limit service: api tls: certResolver: letsencryptThis excerpt is not standalone: it cites the rate-limit middleware and the api service, defined in the next two sections, and the letsencrypt resolver, declared further down in the static configuration. On its own, it would not work. Rules combine with && (AND) and || (OR).
If several routers match the same request, Traefik picks the one with the highest priority. Without a priority field, this priority equals the length of the rule, in characters: it is a simple sort, which does not measure how precise the rule is. A long rule is therefore not necessarily the most specific one.
The trap appears in Traefik's own documentation: a general regex overrides an exact name, because it has more characters. Here is the case, with its fix on the second router:
# FRAGMENT of dynamic configuration (not standalone)http: routers: router-1: # 34 characters: default priority 34 rule: "HostRegexp(`[a-z]+\\.traefik\\.com`)" service: catch-all router-2: # 26 characters: default priority 26, so it loses rule: "Host(`foobar.traefik.com`)" service: api priority: 100Without the last line, all requests for foobar.traefik.com go to router-1, hence to the wrong service. Traefik's docs call this outcome a problem to solve with the priority field. Setting a higher explicit priority (here 100) on the exact router restores the intended order. As soon as an exact Host sits next to a HostRegexp or a broad PathPrefix, set priority instead of relying on length.
A router without entryPoints listens on the default entrypoints, meaning all those you declared. Tested with web and websecure: it answers on both, but not on the internal traefik entrypoint of the dashboard (Traefik's web interface, created by api.insecure).
This default behavior can be restricted: if some entrypoints carry asDefault: true, only those count. With asDefault: true on web, the same router now listens only on web. Always name a router's entryPoints.
| Rule (v3 syntax) | What it tests | Example |
|---|---|---|
Host(`…`) | the requested domain name | Host(`api.example.com`) |
HostRegexp(`…`) | the name, by regular expression | HostRegexp(`^[a-z]+\.example\.com$`) |
Path(`…`) | the exact path | Path(`/health`) |
PathPrefix(`…`) | the start of the path | PathPrefix(`/v1`) |
Header(`…`, `…`) | a header and its value | Header(`X-Env`, `staging`) |
ClientIP(`…`) | the client address | ClientIP(`192.168.1.0/24`) |
Values are written between backticks. Traefik 3.7 also accepts double quotes (Host("api.localhost") routes normally), but rejects single quotes and the bare name: the router is then marked disabled and the log shows the parsing error, for example Host('sq.localhost'): illegal rune literal, or Host(bare.localhost): bare.localhost is not defined. In a Compose file, backticks also save you from escaping the YAML quotes.
What is a service?
A service describes a router's backends and how to reach them. A Traefik "service" is therefore not a container: it is the list of addresses (the backends, meaning the application servers) across which Traefik distributes requests (loadBalancer), with, if you ask for it, an active probe (healthCheck) that removes a failing server from the rotation.
# FRAGMENT: api-1 and api-2 are example server names (not standalone)http: services: api: loadBalancer: servers: - url: "http://api-1:3000" - url: "http://api-2:3000" healthCheck: path: /health interval: 10s timeout: 3sThis probe is the one described in the lesson on the request path: every 10 seconds, Traefik queries /health on each server; one that does not answer in time is marked unhealthy and no longer receives requests, instead of producing errors on the client side. With the Docker provider, you almost never write this list: Traefik derives it from the containers that carry the labels, and you only give the port (loadbalancer.server.port).
What is a middleware?
A middleware is a transformation inserted between the router and the service: it reads or modifies the request, or the response on the way back, or rejects it. A router references zero, one or several, which run in list order (the middlewares field). Defining a middleware does not enable it: only a router that cites it puts it on the path.
# FRAGMENT: two definitions, with no effect until a router cites themhttp: middlewares: rate-limit: rateLimit: average: 100 burst: 50 compress: compress: {}The catalog is broad (authentication, redirect, path rewriting, headers, rate limiting, compression, retry, circuit breaker). This lesson keeps only the mechanism; chains and rewriting are in Traefik middlewares: rewrite, redirect and chain, and protection of the entry point (authentication, IP filtering, rate limiting) in Protecting the Traefik entry point.
What is a provider?
A provider is a source of dynamic configuration: Traefik queries or watches it to build its routers, services and middlewares. You enable providers in the static configuration; they then bring in everything else. Two are enough for this training.
| Provider | Source | Use |
|---|---|---|
docker | labels carried by containers | publishing containers, Compose |
file | YAML or TOML files in a directory | publishing a service outside a container, stable rules |
swarm, consul, consulcatalog, nomad, http | HashiCorp orchestrators, Swarm, remote HTTP endpoint | outside the scope of this training |
kubernetesgateway, kubernetescrd, kubernetesingress | Kubernetes objects | covered in Traefik on Kubernetes (in French) |
Traefik suffixes each object's name with its provider: an api router coming from Docker labels is called api@docker, the same name defined in a file is called api@file. This suffix is used to reference an object from another provider (a rate-limit@file middleware cited by a router declared with Docker labels) and to diagnose: in the dashboard, it tells you who created what. Without a suffix, Traefik looks for the object in the router's provider: a Docker label middlewares=rate-limit looks for rate-limit@docker, does not find it, disables the router (404) and writes middleware "rate-limit@docker" does not exist to the log.
Static or dynamic configuration: what can you change without restarting?
Static configuration is read at startup and needs a restart to change; dynamic configuration reloads on the fly, without cutting open connections. The first describes Traefik itself, the second describes what it publishes. For the static part, Traefik uses only one source at a time: a traefik.yaml file, command-line arguments (--entrypoints.web.address=:80), or environment variables.
| Static configuration (restart) | Dynamic configuration (on the fly) |
|---|---|
| entrypoints | routers |
| enabled providers | services |
ACME resolvers (certificatesResolvers) | middlewares |
| log level and format, metrics | TLS options, certificates supplied by file |
A trap follows from this: the ACME resolver is declared in the static part, but a router references it in the dynamic part (tls.certResolver). Changing the authority's address or the challenge type therefore requires a restart; having a certificate issued for a new name does not. The ACME mechanism itself is covered in the lesson Automatic certificates: ACME, HTTP-01, TLS-ALPN-01 and DNS-01 challenges.
# /etc/traefik/traefik.yaml (static configuration)entryPoints: web: address: ":80" websecure: address: ":443"
providers: docker: exposedByDefault: false file: directory: /etc/traefik/dynamic watch: true
certificatesResolvers: letsencrypt: acme: email: admin@example.org storage: /acme.json httpChallenge: entryPoint: webThis file enables two providers. The docker provider publishes only containers that carry traefik.enable=true, thanks to exposedByDefault: false, which avoids accidentally exposing a database container. The file provider watches a directory (watch: true): dropping or editing a YAML file in /etc/traefik/dynamic creates or changes objects without restarting Traefik. The dashboard and its secure exposure belong to Protecting the Traefik entry point.
How is the same router written with each provider?
A router has the same structure whatever the provider: only the notation changes. In a file, it is a YAML block; with Docker, it is labels set on the container, which Traefik reads on the engine's socket. The api router above, reduced to a rule on the name and completed with a middleware that strips the /api prefix, is written as follows. Unlike the previous excerpts, this Compose file is standalone: just save it and run docker compose up -d.
services: traefik: image: traefik:v3.7.15@sha256:0f6150202bc1fed72a7bd2107460eaa510e5b8a905f3fdb5b84f2c8619c33368 command: - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" ports: - "80:80" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro
api: image: traefik/whoami:v1.11.0@sha256:200689790a0a0ea48ca45992e0450bc26ccab5307375b41c84dfc4f2475937ab labels: - "traefik.enable=true" - "traefik.http.routers.api.rule=Host(`api.localhost`)" - "traefik.http.routers.api.entrypoints=web" - "traefik.http.services.api.loadbalancer.server.port=80" # the middleware is defined here, by labels (stripprefix type) - "traefik.http.middlewares.api-strip.stripprefix.prefixes=/api" # the router cites it with its suffix; several middlewares are # separated by a comma, and a middleware written in a file # carries @file (api-strip@docker,headers@file) - "traefik.http.routers.api.middlewares=api-strip@docker"Each label reads from left to right: traefik.http.routers.api.rule writes the rule of the router named api, and traefik.http.services.api.loadbalancer.server.port gives the port of the service of the same name. A middleware is defined with traefik.http.middlewares.<name>.<type>.<option>: here api-strip, of type stripprefix, removes /api from the path before calling the backend. The router cites it through traefik.http.routers.api.middlewares: without this line, the middleware exists but is on no path. The whoami container returns the request it received. To check, query Traefik while forcing the requested name, once without the prefix, once with it:
curl -s -H "Host: api.localhost" http://127.0.0.1/curl -s -H "Host: api.localhost" http://127.0.0.1/api/hello | grep -E '^(GET|X-Forwarded-(For|Prefix))'The first response lists the headers received by the backend, including X-Forwarded-For, as the lesson on the request path describes. The second proves the middleware's effect: the backend receives GET /hello and not GET /api/hello, and X-Forwarded-Prefix: /api keeps a trace of the stripped prefix. Without the Host header, Traefik finds no router and answers 404.
Mounting the Docker socket (/var/run/docker.sock) gives Traefik access equivalent to administrator access to the machine, a risk to manage. The lesson Traefik with Docker covers the risks and replays it in a lab, and the lesson Docker socket-proxy (in French) shows how to limit this access.
How do you read Traefik if you know HAProxy or Nginx?
All three tools do the same job with different splits: Traefik separates listening, rule, transformation and distribution into distinct objects, where HAProxy and Nginx group them into file blocks. This table is a bridge to the HAProxy and Nginx (in French) guides.
| Traefik | HAProxy | Nginx |
|---|---|---|
| entrypoint | bind of a frontend | listen of a server block |
| router (rule) | acl and use_backend | server_name and location |
| middleware | http-request and http-response rules | location directives (add_header, limit_req, auth_basic) |
service (loadBalancer) | backend and its server lines | upstream and proxy_pass |
healthCheck (active probe) | option httpchk and check | active probes reserved for NGINX Plus; passive in open source |
| provider | no built-in equivalent to labels: reloaded file | no built-in equivalent to labels: reloaded file (nginx -s reload) |
The last row is the real design difference. HAProxy and Nginx read a file and reload it when you ask them to; Traefik watches its providers and reconfigures itself. HAProxy can indeed discover servers through DNS (server-template and resolvers) and be modified through its Runtime API, but neither builds its routing rules from container labels without an extra component. This is not an advantage in itself: if your routes rarely change, a file reloaded on demand is simpler to audit.
When should you choose Traefik over HAProxy or Nginx?
Traefik fits when services appear and disappear often and the team wants to publish them by describing them as close as possible to the application. A container deployed with its labels is published without touching the proxy, and the built-in ACME resolver handles certificates. Conversely, HAProxy or Nginx keep the advantage when the configuration is stable, already mastered by the team, or when you need a feature specific to them. To place these tools relative to each other, see Reverse proxy: definition, role and which tool to choose.
One point of caution comes with this choice: Traefik reads a dynamic source, so anyone who can set labels on a container can create a public route. The protection combines exposedByDefault: false and restricted access to the Docker engine.
What changes between Traefik 2 and Traefik 3?
Traefik 3.0 mostly changes the rule syntax and consolidates routing-related features. The v2 to v3 migration guide notes a revised rule syntax, now the default: regular-expression matchers (HostRegexp, PathRegexp, HeaderRegexp) use the Go syntax, Headers and HeadersRegexp become Header and HeaderRegexp, and Path or PathPrefix no longer accept path parameters such as {id}. On the feature side, HTTP/3 leaves experimental mode, tracing goes exclusively through OpenTelemetry (see Observing Traefik with logs and metrics) and the Kubernetes Gateway API provider moves to version 1.0 of the API. The examples on this page are written for v3 syntax.
Which errors are the most common with these objects?
Most of a beginner's failures come from an object with the wrong name, the wrong entrypoint or the wrong network. The error codes themselves are those of the lesson on the request path; this table links them to the Traefik object to look at first, with what Traefik actually writes in its log (docker logs).
Two reading notes. Traefik's default log level is ERROR (--log.level, checked with --help): docker logs shows router construction errors there, tagged ERR, but neither INFO lines nor DEBUG messages. The 404, 502 and 504 lines exist only if the access log is enabled (--accesslog=true in command:, or accessLog: {} in traefik.yaml). Traefik's API (/api/http/routers) additionally shows the disabled status and the error field of each router.
| Symptom | What Traefik writes (docker logs) | Object to check |
|---|---|---|
404 page not found: no router matches | nothing at the default ERROR level; the accessLog shows it with "-" as the router | router rule, entryPoints |
404: badly written rule | in ERR: error while parsing rule Host('sq.localhost') ... illegal rune literal (single quotes) or bare.localhost is not defined (bare name) | rule notation |
404: forgotten provider suffix | in ERR: middleware "rate-limit@docker" does not exist | middleware name, @file suffix |
404: nonexistent service or middleware | in ERR: the service "absent@docker" does not exist (or middleware "absent@docker" does not exist) | names cited by the router |
The container does not appear (exposedByDefault: false without traefik.enable=true) | nothing in ERROR or INFO; in DEBUG only: Filtering disabled container | container labels |
502 | nothing in the default log; the accessLog gives the router and the target, here "api@docker" "http://172.18.0.3:81" | loadbalancer.server.port port |
504 | nothing by default; the accessLog shows 504 after about 30 s (30003ms in our test) toward the IP of another Docker network | Docker network shared with Traefik |
docker compose up fails: Bind for 0.0.0.0:80 failed: port is already allocated | nothing: the error comes from Docker, Traefik did not start | process or container already using port 80 |
| Router created, never used | nothing: no error, both routers are enabled; the router with the highest priority wins | priority, rule length |
| Change has no effect | nothing: an entrypoint added to traefik.yaml exists only after docker compose restart traefik; and a traefik.yaml mounted in /etc/traefik/ is the only static source read, so command: arguments are then ignored | restart Traefik; a single static source |
Several rows say "nothing": with the default ERROR level and without an access log, a 404 with no router, a 502 or a 504 leaves no trace in docker logs. Enable the access log before diagnosing, otherwise an empty log makes you believe Traefik saw nothing.
Practice
The following lab has you read a Traefik configuration in which some links between routers and services are wrong, then fix them across two providers, the file and Docker. You apply what the page has just taught: a router chooses a service by its name, and the provider suffix decides where that name is looked up. The validation measures three things: the path of model router A does reach application A, the provider qualifies model router B toward application B, and an unknown name reaches no application. This last check ties back to the error table: a request that no rule selects ends in 404, whatever the length of the competing rules.
Key points
- Five objects: the entrypoint listens, the router chooses, the middleware transforms, the service distributes, the provider supplies the configuration.
- Static or dynamic: entrypoints, providers and ACME resolvers require a restart; routers, services and middlewares reload on the fly.
- A router carries the rule (
Host,PathPrefix, values between backticks), the entrypoints, the middlewares, the service and the TLS settings. - A Traefik service is a list of backends with distribution and an active probe, not a container.
- The suffix
@dockeror@fileindicates an object's provider and is used to reference it across providers; if forgotten, the object is looked up in the router's provider, and the router is disabled. - With Docker,
exposedByDefault: falseplustraefik.enable=trueavoid exposing a container by accident. - In HAProxy and Nginx, the same roles exist, grouped into file blocks reloaded on demand.
- To diagnose, enable the access log: at the default
ERRORlevel, a404with no router, a502or a504leave no trace indocker logs.
Knowledge check
Knowledge check
Check what you have learned with this interactive quiz
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
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Install Traefik v3: binary, Docker and package manager
A new full quiz with random questions
Work again only on the questions you missed