Traefik, a reverse proxy that finds services on its own
Traefik is a reverse proxy and load balancer written in Go that reads its routing configuration straight from Docker, Kubernetes, Consul or Nomad instead of a hand written file. The current release is 3.7.11, the license is MIT, and Let's Encrypt certificates are ordered and renewed by the server process itself.
Where Traefik gets its configuration
The difference between Traefik and a classic proxy comes down to one concept: the provider. In nginx or HAProxy the configuration file is the single source of truth about routes, and every change means editing that file and reloading the process. Traefik splits configuration into two layers with completely different life cycles.
The static layer holds things fixed at process start that cannot change without a restart: which ports listen, which configuration sources are enabled, how the certificate authority is reached. The dynamic layer holds routers, services and middleware, meaning everything that changes on every deployment. That second layer does not come from a file, it comes from a provider.
A provider is an adapter to a specific system. The Docker provider attaches to the daemon socket and watches events about containers starting and stopping, reading labels that begin with traefik.. The Kubernetes CRD provider reads IngressRoute resources from the traefik.io/v1alpha1 group. There are also providers for Consul, Nomad, ECS and Redis, plus a plain file provider for when you do want to write routes by hand. Several providers can run at once and their results add up.
The practical effect is that a new container carrying the right set of labels starts receiving traffic without touching the proxy. There is no reload command, no template generating a file, no step in the deployment pipeline that could corrupt that file. Smoothing out bursts of events is handled by providers.providersThrottleDuration with a default of 2 seconds, so ten containers coming up at once produce one rebuild of the routing table rather than ten.
The price of that convenience is real too. Configuration stops living in one place. It spreads across docker-compose.yml files, Kubernetes manifests and annotations, and answering "why does this host go where it goes" requires opening the Traefik dashboard or the API rather than one text file.
Version, license and the third check
The current release on the third branch is 3.7.11 from 21 August 2026. That three lines are maintained in parallel shows in 3 August, when 3.7.10, 3.6.25 and 2.11.55 all shipped on the same day, one release from each of them. The code builds with go 1.26.0 declared in go.mod.
I checked the license in three places, because this is the most common source of mistakes in dependency audits.
The first source is the file in the repository. github.com/traefik/traefik keeps it as LICENSE.md, not LICENSE, and it holds the full MIT text with the notice "Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs". Containous is the former name of the company that renamed itself to Traefik Labs in 2020, and that trace shows up directly in the header.
The second source is the package registry. This is where it gets unpleasant, and it gets its own section below, because the npm package named traefik does not come from Traefik Labs.
The third source is the content of the artifact actually distributed, and here the result is split. The archive traefik_v3.7.11_linux_amd64.tar.gz from the releases section weighs about 48 MB and contains exactly three entries: CHANGELOG.md, LICENSE.md and the traefik binary of about 176 MB unpacked. The license text is therefore present, unlike many projects that ship bare binaries. The official container image, however, does not carry it. The Dockerfile in the traefik/traefik-library-image repository for the alpine variant unpacks the archive with a command that extracts a single entry from it: tar xzvf /tmp/traefik.tar.gz -C /usr/local/bin traefik. LICENSE.md and CHANGELOG.md stay behind in the discarded archive. If your image scanner looks for a license file inside the layers, it will not find one for traefik:v3.7.11, even though the project is plainly MIT. You then have to record the license manually or take it from the OCI labels the image does set, including org.opencontainers.image.source pointing at the repository.
The name trap: the npm package traefik
The traefik package in the npm registry is not published by Traefik Labs. The newest version is 1.0.0 released on 2 August 2021, the whole registry entry has not changed since May 2022, and the repository.url field points at github.com/hello-seam/node-traefik. It is one person's unofficial wrapper.
Unlike several other lookalikes, this one does contain code: fourteen files and about 14.8 kB unpacked. index.js exports a start function that converts a configuration object to TOML through json2toml and launches the binary as a child process. The problem lies elsewhere. The file download-traefik.js has the version to fetch hard coded and asks the GitHub programming interface for it without authentication.
// node_modules/traefik/download-traefik.js, version 1.0.0 from 2021
const releaseVersionToUse = "2.4.9"
const releaseAPIUrl =
`https://api.github.com/repos/traefik/traefik/releases/tags/v${releaseVersionToUse}`Installing the package in 2026 therefore pulls Traefik 2.4.9 from 2021, a release five years old and two major branches behind what is maintained. The download happens in the install script, so it runs automatically during npm install, and the unauthenticated request to GitHub hits rate limits at the first coincidence in a continuous integration environment.
A separate curiosity concerns the license. The npm package declares "license": "MIT" and includes a LICENSE.md file, but its content is the copied Traefik license text with the notice "Copyright (c) 2016-2020 Containous SAS; 2020-2021 Traefik Labs". A tool collecting license metadata from node_modules will therefore report a component covered by Traefik Labs rights in your project, even though somebody else wrote the wrapper code. This is a case where all three sources say "MIT" and the picture is still misleading.
# this is NOT the package from Traefik Labs
npm view traefik version # 1.0.0
npm view traefik repository.url # git+https://github.com/hello-seam/node-traefik.git
npm view traefik time.modified # 2022-05-22T03:25:33.105Z
# this is how the real Traefik is installed
docker pull traefik:v3.7.11
curl -sL https://github.com/traefik/traefik/releases/download/v3.7.11/traefik_v3.7.11_linux_amd64.tar.gz \
| tar -xz traefik LICENSE.mdTraefik is a Go program distributed as a single binary or a container image. There is no official npm channel, no official PyPI package, and no reason for a proxy to enter your project through package.json.
Static and dynamic configuration
Static configuration goes into a file, into environment variables or into command line flags. By default the process looks for a file under four base paths, in this order: /etc/traefik/traefik, $XDG_CONFIG_HOME/traefik, $HOME/.config/traefik and ./traefik, trying the extensions toml, yaml and yml. A custom location is given by the --configFile flag.
# /etc/traefik/traefik.yml, the static layer
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: "proxy"
watch: true
certificatesResolvers:
letsencrypt:
acme:
email: admin@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: webOne field in that example deserves particular attention. providers.docker.exposedByDefault defaults to true, which means that without this line Traefik will expose to the internet every container visible on the daemon socket, including your database and your queue. The default rule is Host({{ normalize .Name }}), the container name used as the host name. Setting false inverts the logic: only containers carrying the traefik.enable=true label get routed.
The dynamic layer for Docker is labels. The router name and the service name inside a label are arbitrary and serve only to bind related entries together.
services:
traefik:
image: traefik:v3.7.11
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./letsencrypt:/letsencrypt
networks: [proxy]
api:
image: ghcr.io/example/api:1.4.2
labels:
- traefik.enable=true
- traefik.http.routers.api.rule=Host(`api.example.com`)
- traefik.http.routers.api.entrypoints=websecure
- traefik.http.routers.api.tls.certresolver=letsencrypt
- traefik.http.services.api.loadbalancer.server.port=8080
- traefik.docker.network=proxy
networks: [proxy]In Kubernetes the same description takes the shape of a custom resource. The group is traefik.io/v1alpha1, and before first use you have to apply the resource definitions and access rules from the dynamic-configuration directory in the repository.
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: api-route
namespace: apps
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`api.example.com`) && PathPrefix(`/v1`)
priority: 10
services:
- kind: Service
name: api
port: 8080
passHostHeader: true
tls:
certResolver: letsencryptThe priority field settles collisions between rules. Without it Traefik sorts rules by the length of their textual form, which can be surprising when two routes match the same request.
ACME certificates and their limits
Certificates are handled by a mechanism called the certificate resolver. You declare one once in the static layer and then refer to it by name from a route through tls.certresolver. The required fields are email and storage, where storage defaults to acme.json in the working directory.
You pick one of three challenges. httpChallenge.entryPoint names an entry point listening on port 80 that must be reachable from outside. tlsChallenge uses TLS-ALPN-01 on port 443. dnsChallenge.provider reaches for a TXT record in the DNS zone, and only this route yields wildcard certificates, because writing a DNS record needs credentials for the provider, passed through environment variables according to the list maintained by the Lego library.
The default authority is https://acme-v02.api.letsencrypt.org/directory, the default key type is RSA4096, and Traefik assumes 90 day certificates and begins renewal 30 days before expiry. For authorities issuing certificates with a different validity period there is certificatesDuration, given in hours, with a default of 2160.
Here lies the most important limitation of the free variant, and you need to know it before designing a deployment. The acme.json store is an ordinary file on disk, not a distributed store with consistency guarantees. Two Traefik replicas reading the same file over a shared volume will fight over it and both will order their own certificates, burning through the authority's limits. The vendor's answer is a feature called Distributed Let's Encrypt, available only in the paid Traefik Hub. In the open variant the correct exits are a single replica terminating TLS, certificates issued outside Traefik and injected as secrets, or an external mechanism such as cert-manager in Kubernetes.
Traefik Proxy, Hub and Enterprise
The documentation and the product site mix three products on one level of navigation, so it is very easy to write an example that uses a feature the free version simply does not have.
| Feature | Traefik Proxy (MIT) | Traefik Hub API Gateway | Traefik Hub API Management |
|---|---|---|---|
| Automatic service discovery | yes | yes | yes |
| HTTP/2, HTTP/3, TCP, UDP, gRPC, WebSockets | yes | yes | yes |
| OpenTelemetry metrics and traces | yes | yes | yes |
| Canary deployments, ingress view dashboard | yes | yes | yes |
| OIDC, LDAP, JWT, HMAC, API keys, Open Policy Agent | no | yes | yes |
| Native web application firewall | no | yes | yes |
| Distributed Let's Encrypt, distributed rate limiting | no | yes | yes |
| HTTP caching, cluster view dashboard, multi-cluster | no | yes | yes |
| FIPS 140-2 and 140-3 | no | yes | yes |
| Developer portal, API versioning and plans | no | no | yes |
| Air-gapped mode, API mocking | no | no | yes |
There is no price list. The traefik.io/pricing page lines up three columns, but Traefik Proxy carries a download button and a separate support offering for the open version, while both Hub variants show only "Get pricing". No figure, no tier, no information about the billing unit. A quote has to be extracted through a conversation with sales, which when comparing against competitors means weeks rather than minutes.
Traefik Enterprise, described a year ago as a separate product, now carries a banner on its own page stating plainly that it has been moved into Traefik Hub. The documentation at doc.traefik.io/traefik-enterprise/ still responds, so existing deployments have somewhere to look, but a new project has no reason to go there. That is also a signal about vendor lock-in risk: the paid line changed its name and shape, and customers had to migrate.
In fairness, Traefik Proxy on its own is complete. Reverse proxy, load balancer, ingress controller, middleware with redirects, headers, basic authentication and per-instance request rate limiting, plus a catalogue of plugins written in Go. For a single server or a single cluster that is the whole set. Paid variants start making sense only with multiple clusters and compliance requirements.
Traefik versus Caddy and nginx
Caddy solves part of the same problem: it is also written in Go, it also fetches certificates on its own, and it also wants to replace the nginx plus certbot pair. The difference sits in where the list of routes comes from.
| Criterion | Traefik Proxy 3.7 | Caddy 2 | nginx with certbot |
|---|---|---|---|
| Source of routes | providers: Docker, Kubernetes, Consul, Nomad, file | Caddyfile or JSON through the admin API | files in a configuration directory |
| Reaction to a new container | automatic, no reload | edit the file and reload | edit the file and reload |
| Certificates | certificatesResolvers with file storage | built in, on by default | separate process and scheduler |
| Whole configuration in one file | no, routes live in labels | yes | yes |
| License | MIT | Apache 2.0 | BSD 2-clause |
| Paid variant | Traefik Hub, price on request | none, funded by sponsorship | none in the core |
The choice is settled by how volatile the environment is, not by taste in syntax. With a fixed set of a few services whose addresses change once a quarter, the Caddy file is simply better: it fits on a screen, goes into the repository, can be reviewed in a minute, and during an outage the whole truth is visible in one place. Traefik in that setup adds a daemon watching events to detect a change that does not happen anyway.
With dozens of containers coming up and going down at the rhythm of deployments the proportions flip. Generating a Caddy file from a template and reloading it on every change reproduces exactly the mechanism Traefik has built in, only with your own code that has to be maintained and that can fail quietly. A label next to a container lives beside the service definition, so the deployment and the route change in one revision.
It is also worth recording that many deployment platforms settle this choice for you. Coolify sets up Traefik as its default proxy and adds the labels to launched containers itself. Railway and Fly.io have their own networking layers and the question never arises there. If the services behind the proxy are mostly PostgreSQL and Redis, meaning protocols other than HTTP, you need a TCP router with a HostSNI rule rather than a plain HTTP router.
Common mistakes
Leaving exposedByDefault at its default value. The effect is a public address for every container on the network, including those that were never meant to leave the host.
Mounting /var/run/docker.sock writable. Access to the daemon socket is equivalent to administrator rights on the host, so a proxy exposed to the internet with such a volume is a serious risk. The minimum is the :ro suffix, better still a broker limiting the range of calls.
Two replicas on one acme.json file. The result is duplicated certificate orders and exhaustion of the authority's weekly limit, after which the domain sits without a valid certificate until the limit window closes.
The container and Traefik on different Docker networks. The route is created, the dashboard shows it, and the answer is a gateway error because the proxy has no way to reach the target address. The traefik.docker.network label or the providers.docker.network field settles the matter.
Omitting loadbalancer.server.port when the image exposes more than one port. Traefik then guesses the port and guesses wrong.
Exposing the dashboard with api.insecure=true on a public entry point. The dashboard shows the full routing table and the middleware configuration, meaning a map of your infrastructure.
Porting configuration from the second branch to the third without reading the migration guide. Provider names, rule syntax and some fields went through backwards incompatible changes, and an old file can come up with part of the routes quietly skipped.
FAQ
Is the npm package named traefik the official Traefik?
No. Version 1.0.0 from 2 August 2021 comes from the hello-seam/node-traefik repository and is one person's unofficial wrapper. It contains working code, but it has Traefik 2.4.9 hard coded and fetches it through an unauthenticated request to the GitHub interface. The official distribution is a binary from the releases section or the traefik image.
What is Traefik's license and where do I find it?
MIT, in the LICENSE.md file at the root of the repository, with a copyright notice for Containous SAS and Traefik Labs. The file is also in the release archive, but it is absent from the official container image, because the Dockerfile extracts only the binary from that archive.
Can Traefik handle automatic certificates across several replicas?
Not in the open version. The acme.json store is a file and there is no coordination between instances inside it. The Distributed Let's Encrypt feature belongs to the paid Traefik Hub. With multiple replicas it makes more sense to issue certificates outside the proxy, for example with cert-manager.
How much does Traefik Hub cost?
The price list is not public. The pricing page shows only a "Get pricing" button for both Hub variants, with no rate, no tier and no billing unit. A quote has to come from the sales department.
Traefik or Caddy for one server with a few services?
With a fixed set of services Caddy usually comes out ahead, because the entire configuration fits in one short file. Traefik starts paying off when services come up and go down on their own and the list of routes changes on every deployment.
Will Traefik pass traffic other than HTTP?
Yes. Besides HTTP routers there are TCP routers with rules based on HostSNI and UDP routers. That lets you put database services behind a single entry point too, although without a name in SNI you cannot distinguish several services on one port.
Sources: the project repository, the pricing page, the package entry in the npm registry.