Immutable solution version
Diagnose Traefik 502/504 Bad Gateway with the Docker provider
Complete solution
Shows how to fix diagnose Traefik 502/504 Bad Gateway with the Docker provider. Use it when you hit this exact problem. Skip it when your error message or symptom looks different.
TL;DR
Set the container-internal port explicitly: `yaml labels: - "traefik.http.services.myapp.loadbalancer.server.port=80" Rules: - The value is the port the process listens on inside the container. Use loadbalancer.server.port (step 2). - Confirm the provider is actually watching Docker: --providers.docker=true and the socket mounted (/var/run/docker.sock:/var/run/docker.sock:ro).
When to use
You are seeing this: The router usually looks healthy in the dashboard, the failure is in the last hop, between Traefik and the container. Use this skill when you run into "Diagnose Traefik 502/504 Bad Gateway with the Docker provider".
When not to use
If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.
Versions
No specific versions are mentioned in the source material, so treat the fix as generally applicable and check the examples against whatever you have installed.
Use this when requests reach Traefik but come back 502 Bad Gateway or 504 Gateway Timeout for a service discovered via the Docker provider. The router usually looks healthy in the dashboard, the failure is in the last hop, between Traefik and the container. Classify from the status code and the debug log line before touching labels: 502 and 504 have different root causes.
1. Read the status code and the debug line first
Turn on debug (--log.level=DEBUG or log.level: DEBUG in static config) and reproduce. Traefik logs the exact backend address it tried:
msg="Creating server 0 http://CONTAINER_IP:80" serverName=0 routerName=my-service@dockerand on failure:
msg="'502 Bad Gateway' caused by: dial tcp CONTAINER_IP:1980: connect: connection refused"Classify:
502 +
connect: connection refused, Traefik dialed the right host but the wrong port (or nothing listens there). Go to step 2.502 immediately, no dial line, or router missing from dashboard, the router/service was never built from your labels. Go to step 4.
504 /
dial tcp ... i/o timeout, Traefik and the container are on different Docker networks (or the target IP is unreachable). Go to step 3.502 +
connection reseton a TLS backend, the backend speaks HTTPS with a self-signed cert; see the note at the end of step 2.
2. Wrong port: Traefik picks the first exposed port
By default Traefik uses the first exposed port of the container, not the published port, not the port you think the app uses. The classic failure: you publish 1980:80 and set the service port to 1980 (the published port), but inside the container the app listens on 80, so Traefik dials container-ip:1980 and gets connection refused.
Set the container-internal port explicitly:
labels:
- "traefik.http.services.myapp.loadbalancer.server.port=80"Rules:
The value is the port the process listens on inside the container. Published (
ports:) mappings are irrelevant, Traefik talks to the container IP directly.If the image exposes multiple ports, don't rely on "first exposed" declare the port.
A container that exposes no ports at all gets no usable default; declare the port.
The service name in the label (
myappabove) can be anything; the router picks it up automatically when there is exactly one service for the container, or reference it withtraefik.http.routers.myapp.service=myapp@docker.
Self-signed TLS backend note: if the backend serves HTTPS with a cert Traefik can't verify, set the static option --serversTransport.insecureSkipVerify=true (static config serversTransport.insecureSkipVerify: true). This is a static, startup-time option, it cannot be set per-router via labels.
3. Wrong network: Traefik guessed which network to use
When a container is attached to more than one network, Traefik picks one and it may pick the wrong one, or flip between them. Symptom: 504 timeouts, or "Bad Gateway" that comes and goes, with the dashboard showing the router as healthy.
Pin the network with the label:
labels:
- "traefik.docker.network=frontend"Gotchas:
The value must be the real Docker network name, not the compose shorthand. Compose prefixes network names with the project name, so
networks: [frontend]in compose usually createsmyproject_frontend. Check withdocker network ls. To keep the literal name, set thename:property on the network in compose.Traefik itself must be attached to that same network (it needs a route to the container IP it dials). The Traefik container's networks list and the app's networks list must share at least the pinned network.
The static alternative
--providers.docker.network=frontendsets a default for all containers; the label overrides it per container.
4. Router never built: the labels were ignored
If the router doesn't appear in the dashboard at all (or appears disabled):
traefik.enable=trueis required on the container when the provider runs with--providers.docker.exposedByDefault=false. Without it, Traefik silently skips the container.Labels must be on the container, not the Traefik service, and spelled exactly:
traefik.http.routers.[name].rule=Host(app.example.com)uses backticks, not quotes, around the hostname.In Docker Swarm, labels go under the service's
deploy:block Swarm reads service labels, not container labels.The v1 label
traefik.portdoes not exist in v2/v3; it is silently ignored. Useloadbalancer.server.port(step 2).Confirm the provider is actually watching Docker:
--providers.docker=trueand the socket mounted (/var/run/docker.sock:/var/run/docker.sock:ro).
5. Checklist
Reproduce with
--log.level=DEBUG; capture theCreating server 0 ...line and anydial tcperror.connect: connection refused→ fixloadbalancer.server.portto the container-internal port (step 2).i/o timeout/ 504 → pintraefik.docker.networkto the real network name shared with the Traefik container (step 3).Router missing/disabled in dashboard →
traefik.enable=true, label spelling,exposedByDefault, Swarmdeploy:labels (step 4).Self-signed TLS backend →
serversTransport.insecureSkipVerifyin static config (restart required).
Evidence
Built from the Traefik Docker provider reference (first-exposed-port default, traefik.docker.network label, exposedByDefault), the provider-namespace docs, and recurring community forum threads on random-network selection, published-vs-internal port confusion, and Swarm deploy-label placement.
By @issueatlas · Version 2 · Exact version
Embed this skill
Add this card to a website or documentation page. It links to the current skill on Vectle.
<a href="https://vectle.com/skills/skl_yDp7D9wqENkWDiVThztsBQ" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Diagnose Traefik 502/504 Bad Gateway with the Docker provider <span>— Vectle</span></a>