SSD Nodes Learn Hosting plans →
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-29

Traefik v2 to v3: wetin go break?

Traefik v3 no go start with swarmMode or pilot for static config. Fix “incompatible deprecated static option found”, then migrate router rules.

Wetin change between Traefik v2 and v3

Migration from Traefik v2 go v3 mostly na renaming work. The famous rename na ipWhiteList middleware wey become ipAllowList. Apart from that, v3 make router rule syntax stricter. PathPrefix no longer get regex features, and dem rename or remove some matchers. E remove some providers and options completely too. Other things still dey work: entrypoints, the ACME certificate setup, the Docker labels workflow, and your acme.json all carry over. v3 also get compatibility mode wey keep v2 rule syntax working. So you fit upgrade the binary first, then rewrite rules one service at a time instead of doing everything for one risky evening.

This guide assume say you dey use the label-based Docker Compose setup from the Traefik reverse proxy guide. That page na v3-native; this one na for the box wey still dey run a traefik:v2 tag.

The renames and removals

  • ipWhiteList don change to ipAllowList, for both the HTTP and TCP middleware. The options wey dey inside remain the same, so sourcerange still get the exact same meaning. Current v3 releases, including v3.5, still accept the old name as deprecated alias and continue to enforce the list, so this rename alone no go bring anything down during the switch. Rename am anyway: dem schedule to remove the alias, and e go disappear from the deprecation list quietly, not with any loud warning.
  • providers.docker.swarmMode=true don comot. Swarm get im own provider now, wey you configure as providers.swarm.endpoint.
  • The pilot section don comot completely.
  • experimental.http3 don comot. You enable HTTP/3 directly for the entrypoint.
  • tls.caOptional don comot from the providers and from the forwardAuth middleware. If that middleware dey front self-hosted Authentik SSO, to delete the caOptional line na the complete migration for am, because the forwardAuth address, the trusted headers, and the outpost behind dem all still behave the same for v3.
  • The InfluxDB v1 metrics provider, the Rancher provider, and the Marathon provider don comot.
  • Tracing don move go OpenTelemetry. The dedicated tracing backends, including the Jaeger and Zipkin integrations, don comot, and v3 dey export OTLP (the OpenTelemetry protocol) instead.
  • The deprecated ssl* options inside the headers middleware (sslRedirect, sslHost, and the remaining ones) don comot. Entrypoint redirections and the redirectScheme middleware don replace dem.

These removals matter pass as e first look, because Traefik no go start when its static configuration get option wey e no recognize. Any leftover pilot or swarmMode line go stop the container during boot with an incompatible deprecated static option found message wey name the leftover; an option wey Traefik never hear of before (a typo, or tls.caOptional) go stop am with field not found instead. Clean the static configuration before you change the image tag.

If Traefik genuinely no know a middleware name (a typo, or a name wey dem remove instead of alias), the router wey reference am go load with error instead of route, the dashboard go mark am, and the API go report middleware "offce@docker" does not exist. Requests to that hostname go get 404 because the router never come up. Note say ipwhitelist no dey this category for current v3: e remain as deprecated alias, so a label wey you never rename go continue to work quietly.

Rule syntax don change

Na rules you go use do the real rewriting. These na the changes for v3:

  • You must put backticks around values inside matchers. v2 also accept double quotes, but v3 no accept am. So Host("app.example.com") must change to Host(app.example.com).
  • PathPrefix no longer understand regular expressions or {id}-style placeholders. A v2 rule like PathPrefix(/api/{version:v[0-9]+}) must change to a PathRegexp matcher wey use Go regular expression syntax.
  • Matchers now dey take one value. v2 allow Host(app.example.com,www.example.com); v3 want Host(app.example.com) || Host(www.example.com). The exceptions na Header, HeaderRegexp, Query, and QueryRegexp. Dem still dey take one name plus one value.
  • Headers and HeadersRegexp don change name to Header and HeaderRegexp.
  • HostHeader don comot. Use Host instead; e match the same thing for v3.
  • Two matchers don new: QueryRegexp, and ClientIP to match client address inside a rule.

The good news be say plain Host(app.example.com) rule wey use backticks already valid for v3 syntax. Most small Compose setups dey use exactly that, so most labels fit migrate without changing any rule.

Audit your labels before you start

You fit measure how big your migration go be with one search, because every label change wey fit break things dey leave pattern wey grep fit find:

grep -rnE 'ipwhitelist|HostHeader|Headers\(|PathPrefix\(`[^`]*\{|Host\(`[^`]*`,' docker-compose*.yml

Every hit na one line to edit. ipwhitelist dey become ipallowlist. HostHeader dey become Host. Headers dey become Header. A {...} placeholder inside PathPrefix dey become PathRegexp matcher. A comma inside Host() dey become two Host() matchers wey || join together. Zero hits mean say your labels don already use valid v3 syntax, so the migration go remain the static configuration plus the image tag. If hits full one screen, na good time to ask whether this still be the correct proxy for the machine, and how Traefik dey compare with Nginx and Caddy dey compare the rewriting work with wetin the other two require from you for each app.

Wetín remain the same

Entrypoints and their HTTP-to-HTTPS redirect, the ACME resolvers with both challenge types, exposedByDefault, the router and service labels, loadbalancer.server.port, and the dashboard all still work for v3 as dem work for v2. Your certificates go carry over too, because v3 still dey read the acme.json wey v2 write. Still back up the file before you start, because rollback wey lose am go enter Let's Encrypt duplicate-certificate rate limit directly:

cp ./letsencrypt/acme.json ./letsencrypt/acme.json.v2-backup

Di migration path

Step 1: lock wetin you dey run today. Change any traefik:latest or traefik:v2 tag to the exact release wey you dey use, for example traefik:v2.11, then commit the whole compose directory to git. Every later step fit reverse with checkout. If recreating one service with docker compose up -d <service> never become familiar to you yet, Docker Compose basics guide cover the operations wey this migration dey use.

Step 2: clean the static configuration and turn on compatibility mode. Remove every option wey v3 drop (pilot, swarmMode, tls.caOptional, experimental.http3), then tell v3 make e treat rules as v2 syntax by default. For traefik.yml:

core:
  defaultRuleSyntax: v2

Or use am as flag for the compose command: list: --core.defaultRuleSyntax=v2. Compatibility mode cover rule syntax only. E no restore options wey dem remove, and e no rename middlewares for you.

Step 3: prepare the middleware renames. Search your compose files for the old names: grep -rn ipwhitelist docker-compose*.yml. Edit every ipwhitelist label to ipallowlist, but no apply the change yet, because the new name no exist for v2. These edits go ship together with the flip for the next step. (If one escape, current v3 still honour the old name as deprecated alias, so the list continue to enforce; fix am for the next pass instead of 2am.)

Step 4: flip the image tag. Set the Traefik image to the current v3 release, traefik:v3.5 when this write-up dey made, then:

docker compose up -d
docker compose logs -f traefik

Because compatibility mode dey on, your v2 rules continue to match, and because up -d also recreate the services wey get the middleware labels you rename, those routers come up clean. Healthy log no get field not found line and no does not exist line.

Be honest with yourself about the window wey this step open. A router wey reference middleware name wey v3 genuinely no know (whether na typo or option wey dem remove) go down from the moment new Traefik start until dem recreate its app container. For one box, na the few seconds wey docker compose up -d need to work through the list. If one route truly no fit blink, remove the renamed middleware from that router's middlewares label before the flip and add am back after. Decide ahead whether that route fit work without its IP allow list for the minute in between.

Step 5: migrate rules service by service. Work through one app at a time: rewrite its rule to v3 syntax, recreate only that service with docker compose up -d app, and test am before you continue. If one service get rule wey you never fit rewrite yet, give that single router the escape hatch label traefik.http.routers.app.ruleSyntax=v2 and continue.

Step 6: turn compatibility mode off. When every rule don become v3 syntax, delete defaultRuleSyntax and any ruleSyntax labels, restart Traefik, and confirm say every router still show green for the dashboard. No settle with compatibility mode on: Traefik deprecated both options for v3.4 and go remove dem for the next major version, so dem na bridge, no be destination.

Before and after: labels for one service

See one app wey carry every famous change at once: one multi-value Host, one PathPrefix placeholder, and one ipWhiteList middleware. The v2 block:

  app:
    image: app:1.4
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.app.rule=Host(`app.example.com`,`www.example.com`) && PathPrefix(`/api/{version:v[0-9]+}`)
      - traefik.http.routers.app.entrypoints=websecure
      - traefik.http.routers.app.tls.certresolver=le
      - traefik.http.routers.app.middlewares=office
      - traefik.http.middlewares.office.ipwhitelist.sourcerange=10.0.0.0/24
      - traefik.http.services.app.loadbalancer.server.port=8080

And the same service after dem migrate am to v3:

  app:
    image: app:1.4
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.app.rule=(Host(`app.example.com`) || Host(`www.example.com`)) && PathRegexp(`^/api/v[0-9]+`)
      - traefik.http.routers.app.entrypoints=websecure
      - traefik.http.routers.app.tls.certresolver=le
      - traefik.http.routers.app.middlewares=office
      - traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.0/24
      - traefik.http.services.app.loadbalancer.server.port=8080

Two labels change. The rule divide its multi-value Host into two matchers wey || join together, and e replace the placeholder with PathRegexp. The middleware label replace ipwhitelist with ipallowlist. The entrypoint, certificate resolver, router-to-middleware wiring, and service port no change.

Test each service with the dashboard

After every flip, open the dashboard HTTP routers page. All routers suppose dey green. Router wey get error badge go show the exact problem. Most times, na middleware wey no dey under the new name, or rule v3 wey no fit parse. Then confirm from outside, one hostname at a time:

curl -sI https://app.example.com/api/v1/status

A 200 or your app normal redirect mean say routing and TLS both still work. 404 from Traefik mean say the router no start; go back to the dashboard and read the error wey e show. Keep docker compose logs -f traefik open for another terminal as you dey work, because every parsing failure go land there immediately when container restart.

Rollback honesty

Keep the v2 compose file, the static configuration, and the acme.json backup until every service dey route through v3 and you don test dem properly. To roll back, check out the commit from before the migration and run docker compose up -d. You must restore the whole file, no be only the image tag, because v3-only labels dey wrong under v2 just as v2 labels dey wrong under v3: ipallowlist no dey exist for v2, and a PathRegexp matcher no go parse there too. If acme.json get lost or damage during the process, restore the backup copy before you start v2. This one go stop the rollback from using your Let's Encrypt rate limit to issue five certificates again at once.

FAQ

I need rewrite every router rule for Traefik v3?

No. Plain Host(app.example.com) rule wey you write with backticks valid for both versions, and e cover most Compose setups. You only need rewrite rule where e use v2-only features: regex or placeholders inside Path and PathPrefix, multiple hostnames inside one Host(), quotes instead of backticks, or the removed Headers, HeadersRegexp, and HostHeader matchers.

Wetin happen to ipWhiteList for Traefik v3?

Dem rename am to ipAllowList, but dem leave the configuration inside as e be. So v2 label like traefik.http.middlewares.office.ipwhitelist.sourcerange=10.0.0.0/24 become the same line with ipallowlist inside am. Current v3 releases, including v3.5, still accept the old name as deprecated alias. So unrenamed label still enforce the allowlist quietly. Treat this as borrowed time, no be reason to skip the rename. Dem plan to remove the alias, and middleware name wey Traefik no know go fail loudly instead, with router error and 404. Dashboard go show the error, and requests to that hostname go return 404.

Traefik v3 still fit read v2 rule syntax?

Yes. Set core.defaultRuleSyntax: v2 for the static configuration to keep v2 syntax as the default while you migrate. After you switch the default back, use the per-router ruleSyntax=v2 label for individual ones wey remain. Treat both as temporary. Traefik deprecated dem for v3.4 and go remove dem for the next major version.

My Let's Encrypt certificates go survive the upgrade?

Yes. Traefik v3 still dey read the acme.json file wey v2 write, so certificates no go re-issue just because the binary change. Still copy the file go safe place before you start, because rollback or deleted volume wey lose acme.json go force re-issuing every certificate at once. Let's Encrypt allows only five duplicate certificates per week for the same set of hostnames.

Why Traefik v3 no fit start after the upgrade?

Almost every time, na because static configuration still get option wey v3 remove, and Traefik no gree start when option no dey recognise. For the known leftover options (pilot, providers.docker.swarmMode, experimental.http3), log go talk incompatible deprecated static option found and name the problem. For anything wey v3 never hear about, like tls.caOptional, e go talk field not found with the node. Delete or replace each one, then start the container again.