Traefik v3 has been out for months. The migration from v2 should have been straightforward — most of the core label syntax is identical. Instead, r/selfhosted and r/docker are full of threads from people whose routes silently stopped working after the upgrade.
Here's what's actually breaking and why.
The silent failure problem
Traefik v3 does not error on invalid or deprecated configuration. It starts successfully. It logs nothing obvious. Your containers are running. The routes just don't exist.
This is why the migration catches people off guard. There's no clear failure signal — just 404s from routes that used to work.
What broke for most people
1. Docker network not explicitly configured
This is the most common failure. In v2, Traefik could usually figure out which Docker network to use for routing. In v3, if Traefik and your containers are on different networks, the route is created but has no healthy backends — returns 404.
# The fix — create a shared external network:
docker network create traefik-public
# Traefik service:
services:
traefik:
networks:
- traefik-public
# Every routed service:
services:
myapp:
networks:
- traefik-public
labels:
- "traefik.docker.network=traefik-public"
networks:
traefik-public:
external: true
The traefik.docker.network label is now required when multiple networks are involved. In v2 it was optional.
2. Old v1 labels still in compose files
Traefik v1 labels (traefik.frontend.*, traefik.backend, traefik.port) were deprecated in v2 but silently accepted. In v3 they are completely ignored — no warning, no error, no route.
# These do nothing in v3 — remove them:
traefik.frontend.rule=Host:app.example.com
traefik.backend=myapp
traefik.port=3000
traefik.frontend.entryPoints=https
# Replace with:
traefik.enable=true
traefik.http.routers.myapp.rule=Host("app.example.com")
traefik.http.routers.myapp.entrypoints=websecure
traefik.http.routers.myapp.tls.certresolver=letsencrypt
traefik.http.services.myapp.loadbalancer.server.port=3000
3. swarmMode in static config
If your traefik.yml has swarmMode: false inside the Docker provider block, Traefik v3 throws a startup error because Swarm configuration moved to a separate provider.
# v2 static config (breaks in v3):
providers:
docker:
swarmMode: false # Remove this line
# v3 — Swarm is now a separate provider:
providers:
docker:
exposedByDefault: false
# swarm: (only add if actually using Swarm)
4. allowEmptyServices removed
If you had allowEmptyServices: true in your Docker provider config to allow Traefik to start when backends are down — it's gone. Remove it from your config. Traefik v3 handles empty services differently and the option is no longer needed.
How to diagnose what broke
# Check Traefik's view of your routers: curl http://localhost:8080/api/http/routers | python3 -m json.tool | grep -A5 "status" # Look for routers with no services or "error" status # Check which containers Traefik can see: curl http://localhost:8080/api/http/services | python3 -m json.tool
Enable the Traefik dashboard if you haven't already — it shows you exactly which routes exist and whether their backends are healthy. A router with 0 healthy servers is your problem.
The labels that still work identically
To be clear — the core v2 label syntax is unchanged in v3. These still work:
traefik.enable=true
traefik.http.routers.NAME.rule=Host("domain.com")
traefik.http.routers.NAME.tls.certresolver=letsencrypt
traefik.http.services.NAME.loadbalancer.server.port=3000
traefik.http.middlewares.NAME.redirectscheme.scheme=https
If your setup only uses basic routing with TLS, you may not need to change anything except add the explicit network configuration.
Quick migration checklist
- Create explicit external Docker network and attach Traefik + all services to it
- Add traefik.docker.network label to every routed service
- Remove any traefik.frontend.*, traefik.backend, traefik.port labels
- Remove swarmMode: false from Docker provider static config
- Remove allowEmptyServices from static config
- Check dashboard or API for routers with 0 healthy backends
Paste your docker-compose.yml or nginx.conf to detect Traefik v1 label patterns and get exact v3 replacements.