SSD Nodes Learn
How to do am Matt ConnorBy Matt Connor · Updated 2026-07-24

wetin change for Traefik v2 to v3 migration

If Traefik v3 no wan start because of swarmMode or pilot for static config, fix that incompatible deprecated static option error and migrate your rules.

Wetin change for between Traefik v2 and v3

Traefik v2 to v3 migration na mostly renaming work. One big change na when dem change ipWhiteList middleware to ipAllowList. Aside from that, v3 tighten how router rule syntax dey work ( PathPrefix no get regex features again, some matchers dem rename or dem remove dem completely), dem drop some providers and options, but dem keep everything else working: entrypoints, ACME certificate setup, Docker labels workflow, and your acme.json all go still work. v3 also carry compatibility mode wey dey make v2 rule syntax still work, so you fit upgrade the binary first and rewrite rules one service at a time instead of to do everything for one risky evening.

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

The renames and removals

  • ipWhiteList don change to ipAllowList, for both the HTTP and the TCP middleware. The options inside no change, so sourcerange still mean the same thing. Current v3 releases, including v3.5, still dey accept the old name as a deprecated alias and dem still dey use the list, so this rename no go break anything for now. Rename am anyway: dem plan to remove the alias, and e go just disappear from the deprecation list without any noise.
  • providers.docker.swarmMode=true don go. Swarm get its own provider, wey dem configure as providers.swarm.endpoint.
  • The pilot section don disappear altogether.
  • experimental.http3 don go. HTTP/3 dey enable for the entrypoint directly.
  • tls.caOptional don go from the providers and from the forwardAuth middleware.
  • The InfluxDB v1 metrics provider, the Rancher provider, and the Marathon provider don go.
  • Tracing don move to OpenTelemetry. The dedicated tracing backends, including Jaeger and Zipkin integrations, don go, and v3 dey export OTLP (the OpenTelemetry protocol) instead.
  • The deprecated ssl* options inside the headers middleware (sslRedirect, sslHost, and the rest) don go. Entrypoint redirections and the redirectScheme middleware don take their place.

These removals important pass how e look, because Traefik go refuse to start if its static configuration get option wey e no know. If you leave pilot or swarmMode line, the container go stop for boot with an incompatible deprecated static option found message wey go name the leftover; if you use option wey Traefik never hear of before (like typo, or tls.caOptional), e go stop with field not found instead. Clean your static configuration before you change the image tag.

If Traefik really no know a middleware name (like typo, or name wey dem remove instead of make alias), e go fail differently: the router wey use 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 no ever start. Note say ipwhitelist NO be for this category for current v3: e still dey work as a deprecated alias, so if you no rename the label, e go still dey work quietly.

The rule syntax changes

Rules na where real rewriting dey happen. Dem change things for v3:

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

The good news: any plain Host(app.example.com) rule wey you write with backticks na already valid v3 syntax. Most small Compose setups dey use that one, so most labels go migrate without any rule edits.

Check your labels before you start

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

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

Every hit na one line wey you need to edit. ipwhitelist go become ipallowlist. HostHeader go become Host. Headers go become Header. One {...} placeholder inside PathPrefix go become PathRegexp matcher. One comma inside Host() go become two Host() matchers wey joined by ||. If zero hits, e mean say your labels don already follow valid v3 syntax, and the migration go small because na only static configuration plus the image tag remain.

Wetin no go change

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 dey work for v3 just as dem dey work for v2. Your certificates go carry over too, because v3 still dey read the acme.json wey v2 write. Still, make you back up the file before you start, because if you rollback and you lose am, you go hit the Let's Encrypt duplicate-certificate rate limit:

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

The migration path

Step 1: pin 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, and commit the whole compose directory to git. Every next step go easy to undo with a checkout. If you never sabi how to recreate single service with docker compose up -d <service>, the Docker Compose basics guide go explain the operations wey this migration dey use.

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

core:
  defaultRuleSyntax: v2

Or as a flag for the compose command: list: --core.defaultRuleSyntax=v2. Compatibility mode na for rule syntax only. E no go bring back removed options, and e no go 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 dey exist for v2. These edits go follow the flip for the next step. (If one slip pass, current v3 still dey respect the old name as a deprecated alias, so the list go still dey enforce; fix am for the next pass instead of to dey work for 2am.)

Step 4: flip the image tag. Set the Traefik image to the current v3 release, traefik:v3.5 at the time of writing, then:

docker compose up -d
docker compose logs -f traefik

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

Be honest with yourself about the window wey this step dey open. Router wey dey point to middleware name wey v3 no know (typo, or removed option) go down from the moment the new Traefik start until its app container dey recreate, wey for one box na the few seconds wey docker compose up -d dey take work through the list. If one route no fit blink at all, remove the renamed middleware from that router's middlewares label before the flip and re-add am after, and decide before you start if that route fit live without its IP allow list for the minute wey dey between.

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

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 dey show green for dashboard. No stay with compatibility mode on: Traefik don deprecate both options for v3.4 and dem go remove dem for the next major version, so dem na bridge, dem no be destination.

Before and after: one service's labels

See one app wey carry all the big changes at once: a multi-value Host, a PathPrefix placeholder, and an 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 wey dem migrate 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 split its multi-value Host into two matchers wey || join, and dem swap the placeholder for PathRegexp. Also, the middleware label swap ipwhitelist for ipallowlist. The entrypoint, the certificate resolver, the router-to-middleware wiring, and the service port no change.

Test each service with the dashboard

After every flip, open the dashboard HTTP routers page. Every router must dey green. If router get error badge, e go tell you the exact problem. Usually, na middleware wey no dey under new name or rule wey v3 no fit parse. After that, confirm from outside, one hostname at a time:

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

If you see 200 or your app's normal redirect, e mean say routing and TLS still dey work well. If you see 404 from Traefik, e mean say router no come up; go back to the dashboard and read the error. Keep docker compose logs -f traefik open for another terminal while you work, because any parsing failure go land there immediately container restart.

Rollback honesty

Keep the v2 compose file, its static configuration, and the acme.json backup until every service dey route for v3 and dem don test am well well. To rollback, you must check out the pre-migration commit and run docker compose up -d. You must run the whole file, no be just the image tag, because v3-only labels go fail for v2 exactly how v2 labels fail for v3: ipallowlist no dey for v2, and PathRegexp matcher no go work for there either. If acme.json lost or spoil during the process, restore the backup copy before you start v2, so that rollback no go waste your Let's Encrypt rate limit to re-issue five certificates at once.

FAQ

I must rewrite every router rule for Traefik v3?

No. If you use backticks for a plain Host(app.example.com) rule, eya go work for both versions, and dat one cover most Compose setups. You only need to rewrite if your rule use v2-only features: regex or placeholders inside Path and PathPrefix, many hostnames inside one Host(), quotes instead of backticks, or if you use Headers, HeadersRegexp, and HostHeader matchers wey dem don remove.

Wetin happen to ipWhiteList for Traefik v3?

Dem rename am to ipAllowList, but di configuration inside no change, so v2 label like traefik.http.middlewares.office.ipwhitelist.sourcerange=10.0.0.0/24 go just become di same line with ipallowlist inside. Current v3 releases, including v3.5, still dey accept di old name as deprecated alias, so if you no change di label, di allowlist go still work quietly. No use dat as excuse to skip di rename: dem plan to remove dat alias soon, and if you use middleware name wey Traefik no know, e go fail loudly with router error and 404. Di dashboard go show di error, and requests to dat hostname go return 404.

Traefik v3 fit still read v2 rule syntax?

Yes. Set core.defaultRuleSyntax: v2 for di static configuration if you wan keep v2 syntax as default while you dey migrate, and use di per-router ruleSyntax=v2 label for any single stragglers after you don change di default back. Treat both as temporary: Traefik deprecate dem for v3.4 and dem go remove dem for di next major version.

My Let's Encrypt certificates go survive di upgrade?

Yes. Traefik v3 go still read di acme.json file wey v2 write, so certificates no go re-issue just because di binary change. Even so, copy di file go safe place before you start, because if you do rollback or if volume wey hold acme.json get delete, e go force re-issue every certificate at once, and Let's Encrypt only dey allow five duplicate certificates per week for di same set of hostnames.

Why Traefik v3 dey fail to start after di upgrade?

E dey happen almost always because di static configuration still get option wey v3 don remove, and Traefik no go start if e see options wey e no recognize. For di well-known leftovers (pilot, providers.docker.swarmMode, experimental.http3), di log go say incompatible deprecated static option found and name di culprit; for anything wey v3 never hear before, like tls.caOptional, e go say field not found with di node. Delete or replace every single one, then start di container again.