2026-03-23 · TraefikDockerReverse ProxyMigration

Traefik v2 to v3 Migration: What Actually Broke and How to Fix It

Traefik v3 didn't rewrite everything — but what it did change breaks quietly. Routes stop working, no obvious error, and the label you've had in your compose file for two years just silently does nothing now.

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 →

Related Glossary Terms