Traefik v3 shipped with breaking changes to its label syntax. If you upgraded without reading the migration guide — and many people did — your routes stopped working and the error messages weren't particularly helpful about why.
Here's a practical guide to what changed and what to update.
What actually broke
The core routing label syntax stayed the same. What changed was a set of specific features, middleware names, and some default behaviours. The two things that break the most setups:
1. Docker provider changes — In v3, the Docker provider requires explicit network configuration if Traefik and your containers are on different Docker networks. In v2 it would figure this out automatically more often. In v3 it doesn't.
2. allowEmptyServices removed — In v2 you could set allowEmptyServices: true to let Traefik start even when backends were down. This option was removed in v3. If you had it in your static config, Traefik v3 silently ignores it and may behave differently.
The label syntax that still works
Good news first — the core routing labels are identical between v2 and v3:
# These work in both v2 and v3: traefik.enable=true traefik.http.routers.myapp.rule=Host(`app.example.com`) traefik.http.routers.myapp.tls.certresolver=letsencrypt traefik.http.services.myapp.loadbalancer.server.port=3000
If your setup only uses basic routing with TLS, you might not have anything to update.
Old labels that no longer work
These are Traefik v1 labels that some people were still using in v2 (where they were deprecated but worked). In v3 they do nothing:
# These are dead in v3 — remove them: traefik.frontend.rule=Host:app.example.com # v1 style traefik.backend=myapp # v1 style traefik.port=3000 # v1 style traefik.frontend.entryPoints=https # v1 style
If you see these in your compose files, they're silently doing nothing. Replace with the v2/v3 style:
# Equivalent v3 labels: traefik.enable=true traefik.http.routers.myapp.rule=Host(`app.example.com`) traefik.http.routers.myapp.entrypoints=websecure traefik.http.services.myapp.loadbalancer.server.port=3000
Middleware names changed
Several built-in middleware types were renamed in v3. The most commonly used one:
# v2 — redirect to HTTPS: traefik.http.middlewares.redirect-https.redirectscheme.scheme=https traefik.http.middlewares.redirect-https.redirectscheme.permanent=true # v3 — same thing, same syntax (this one didn't change): traefik.http.middlewares.redirect-https.redirectscheme.scheme=https traefik.http.middlewares.redirect-https.redirectscheme.permanent=true
The middleware syntax itself is mostly unchanged. What changed is that some middleware options that were implicit in v2 need to be explicit in v3. Check the Traefik v3 migration guide for the full list if you're using rate limiting, circuit breakers, or IP allowlisting middleware.
The Docker network problem
This is what actually breaks most setups. In v2, if Traefik and your container were both on the default bridge network, Traefik could usually reach the container. In v3 the Docker provider is stricter about network attachment.
The fix is to put Traefik and all routed containers on a shared external network:
# docker-compose.yml (Traefik service):
services:
traefik:
image: traefik:v3
networks:
- traefik-public
ports:
- "80:80"
- "443:443"
networks:
traefik-public:
external: true
# docker-compose.yml (application service):
services:
myapp:
image: myapp:latest
networks:
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
- "traefik.docker.network=traefik-public" # Tell Traefik which network to use
networks:
traefik-public:
external: true
Create the network once:
docker network create traefik-public
Checking your labels before you upgrade
Paste your docker-compose.yml into the ConfigClarity Reverse Proxy Mapper. It detects Traefik v1 label patterns and generates the exact v3 replacements — including the network configuration fix.
The static config changes
If you're using a traefik.yml static config file rather than CLI flags, a few options moved:
# v2 static config:
providers:
docker:
exposedByDefault: false
swarmMode: false
# v3 static config (swarmMode moved to swarm provider):
providers:
docker:
exposedByDefault: false
swarm: # separate provider now
exposedByDefault: false
Most single-node setups don't use Swarm, so this doesn't apply. But if you had swarmMode: false in your Docker provider config, remove it — it'll cause a startup error in v3.
Paste your docker-compose.yml or nginx.conf to detect Traefik v1 label patterns and get exact v3 replacements.
Open Reverse Proxy Mapper →