Why Traefik v3 over nginx for your Docker stack
nginx is an excellent HTTP server, but its configuration is static: every new service requires a new vhost file, a process reload and often a manual update to ACME renewal. Traefik was built for containerized environments where topology changes constantly. In version 3, it takes this further with native HTTP/3 (QUIC) support, a simplified middleware syntax, and a stricter runtime that rejects ambiguous configurations.
- Automatic container discovery via Docker labels — no manual reload when you add a service.
- Automatic HTTPS with Let's Encrypt (TLS-ALPN-01 or HTTP-01 challenge): certificates obtained, stored and renewed without intervention.
- Native middlewares declarable by label: BasicAuth, rate-limit, security headers, redirects — no nginx module to write.
- Built-in real-time dashboard: view all routes, middlewares and certificate status from a browser.
- HTTP/3 (QUIC) support in a single config line: improves performance on high-latency connections.
- Routing by Host, PathPrefix, Headers or any combination — rules are written in expressive Go template syntax.
- Internal REST API: query router state, inspect active backends, automate CI/CD integrations.
- Compatible with Docker Swarm, Kubernetes and Nomad — migrate without changing your labels.
Technical prerequisites before you start
This installation requires a VPS running Debian 12 or Ubuntu 22.04/24.04 (commands are identical), Docker Engine 25+ and Docker Compose v2.24+ (the docker compose command without a hyphen). Check that ports 80 and 443 are open inbound from the Internet in your firewall. You need a domain name whose DNS you control: point an A record to your VPS IP before starting, otherwise Let's Encrypt cannot issue the certificate. A valid email address is required for ACME expiry notifications.
Complete Traefik v3 installation step by step
Create the shared Docker network. Traefik must be on the same network as the services it proxies. Create it once:
docker network create traefik-public. This network will be referenced in every servicedocker-compose.yml.Create the
acme.jsonfile for certificates. Let's Encrypt will write its certificates into this file. It must be owned by root with strictly 600 permissions:touch /opt/traefik/acme.json && chmod 600 /opt/traefik/acme.json. If permissions are wrong, Traefik will refuse to start.Create the Traefik
docker-compose.yml. Place this file at/opt/traefik/docker-compose.ymlwith the full configuration including TLS entrypoints, the Let's Encrypt ACME resolver, and the protected dashboard labels.Generate the BasicAuth hash for the dashboard. Replace the hardcoded string with a real bcrypt hash:
echo $(htpasswd -nB admin) | sed -e 's/\$/\$\$/g'. Thehtpasswdcommand is in theapache2-utilspackage on Debian/Ubuntu. Dollar signs must be doubled ($$) in Docker Compose label values.Start Traefik. From
/opt/traefik/:docker compose up -d. Check logs:docker compose logs -f traefik. You should see"Starting provider" providerType=Dockerthen"Attaching router" routerName=dashboard. An error onacme.jsonmeans wrong permissions.Add a service behind Traefik. In your application's
docker-compose.yml, join thetraefik-publicnetwork (as external) and add the four key labels:traefik.enable=true, theHost()rule, thewebsecureentrypoint, theletsencryptcertresolver, and the backend port. Traefik detects the container immediately afterdocker compose up -d.Verify Let's Encrypt certificates. Inspect
acme.json:cat /opt/traefik/acme.json | python3 -m json.tool | grep -A3 'domain'. You should see your domains listed with an expiry 90 days out. Alternatively, the Traefik dashboard lists all active certificates under the *Routers* tab.Validate the HTTP→HTTPS redirect. Test with curl:
curl -v http://app.your-domain.com. You must receive a301 Moved Permanentlytohttps://. A connection refused on port 80 means firewall rules are blocking the port.Enable HTTP/3 on the browser side. Add the
Alt-Svcheader via a global middleware in your Traefik labels or a statictraefik.ymlconfig file. The headerAlt-Svc: h3=":443"; ma=86400tells the browser your server supports QUIC. Verify withcurl --http3 https://app.your-domain.com -v.
Essential middlewares: BasicAuth, rate-limit and security headers
Traefik middlewares are applied via Docker labels directly on the service router. Here are the three most useful middlewares in production, declared on the service itself without touching Traefik's central configuration.
- BasicAuth on a private route — to protect an admin tool (Adminer, Flower, etc.): add
traefik.http.middlewares.adminauth.basicauth.users=admin:$$hash$$andtraefik.http.routers.myadmin.middlewares=adminauthlabels on the service. - Rate-limit against abuse — protects an API or contact form: set
ratelimit.average=100(100 req/s average) andratelimit.burst=50(burst of 50 tolerated), then apply the middleware on the router. - HTTP security headers —
X-Frame-Options,X-Content-Type-Options,Strict-Transport-Security: Traefik'sheadersmiddleware accepts all these values directly as labels, no separate nginx config file needed. - Redirect Regex — to redirect
www.your-domain.comtoyour-domain.com: use theredirectregexmiddleware withpermanent=trueand a regex^https://www\.(.+)→https://${1}. - Compress — enable gzip/brotli compression on all text responses with a single label:
traefik.http.middlewares.compress.compress=true. Apply globally by adding it as a default middleware in the static configuration.
HTTP/3 (QUIC) in two lines. In the command section of Traefik's docker-compose.yml, add --entrypoints.websecure.http3=true and expose UDP port 443 (443:443/udp). QUIC establishes connections up to 30% faster on lossy networks and eliminates TLS-over-TCP head-of-line blocking. Modern browsers (Chrome 87+, Firefox 88+, Safari 16.4+) activate it automatically after the first exchange via the Alt-Svc header. No application-side changes are needed.
Migrating from Traefik v2: breaking changes
Traefik v3 introduces intentional breaks with v2 to clean up accumulated ambiguities. The good news: migration is progressive — v2 and v3 configurations can coexist during the transition. Here are the most frequently breaking points.
docker.swarmModerenamed todocker.swarmmode(all lowercase). If your v2 config uses the old casing, Traefik v3 silently ignores it and Swarm discovery doesn't work.PathPrefixrule without trailing slash: in v3,PathPrefix('/api')is equivalent toPathPrefix('/api/')— the trailing slash is normalized. If you had rules that depended on this behavior to distinguish/apifrom/apiv2, revise them.tls.certificatesin dynamic configuration: in v2, you could define manual certificates directly in a file provider's dynamic config. In v3, this section must be in the static configuration (traefik.yml) or via a dedicated provider.errorsmiddleware: thequeryparameter is renamed tourl. Any config usingerrors.querymust be updated toerrors.url.- Removal of
spiffeprotocol support: if you used SPIFFE authentication in a Consul infrastructure, migrate to a standard mTLS solution before upgrading to v3. - Docker
constraintsconfiguration: the constraints syntax has changed to align with the standard Docker tag format. Check your constraint list if you were filtering services by tag.
Traefik v3 vs nginx vs Caddy: which reverse proxy for Docker?
Scroll the table
| Criterion | Traefik v3 | nginx | Caddy v2 |
|---|---|---|---|
| Configuration | Docker labels, TOML or YAML — automatic hot reload | Static vhost files — manual reload (`nginx -s reload`) | Declarative Caddyfile — automatic hot reload |
| Automatic HTTPS | Natively built-in (ACME v2, TLS-ALPN or HTTP-01 challenge) | Requires certbot + cron + reload script | Natively built-in, simplest to configure (one line) |
| Dynamic Docker routing | Native: real-time container discovery via Docker socket | Not native: requires nginx-proxy or manual modification | Native via caddy-docker-proxy plugin (unofficial) |
| HTTP/3 (QUIC) | Natively supported since v3 (2024), one-line activation | Supported since nginx 1.25 (2023), extra configuration needed | Natively supported since Caddy 2.6 (2022) |
| Raw performance | Slightly below nginx at very high throughput (>50k req/s) | Industry reference for high throughput and concurrency | Comparable to nginx for most standard web workloads |
| Learning curve | Moderate — router/service/middleware concepts to understand | High for HTTPS + Docker — fragmented documentation | Low — most concise configuration for simple cases |
Troubleshooting: the five most common errors
The majority of Traefik production issues come from three sources: acme.json file permissions, missing Docker network, and incorrectly escaped labels in Compose. Here are the errors with their actual messages.
level=error msg="Unable to obtain ACME certificate" error="acme: error 403"orunauthorized— Let's Encrypt can't verify your domain. Check that port 80 is open from the Internet and that your domain's DNS points to your VPS IP.level=error msg="The acme.json file has bad file permissions"then Traefik refuses to start — file permissions are not 600. Fix:chmod 600 /opt/traefik/acme.json. This check is intentionally blocking: a world-readable file would expose your private keys.- Service visible in Traefik dashboard but 502 Bad Gateway — the service container is not on the
traefik-publicnetwork. Check withdocker inspect <container> | grep -A20 Networks. Add the network in the service'sdocker-compose.ymland recreate the container. - Labels silently ignored, route absent from dashboard — you forgot
traefik.enable=trueon the service, or--providers.docker.exposedbydefault=falseis active (recommended) and the label is missing. Also check that the router name intraefik.http.routers.<name>.ruleis unique across all your services. invalid character '$' in label value— dollar signs in BasicAuth hashes are not doubled in the Compose file. The correct syntax is$$2y$$10$$...(each$becomes$$) when the value is in adocker-compose.yml. If you pass the value via an environment variable, a single$is enough.
Next step: harden your Docker stack for production
Traefik v3 handles routing and TLS, but a production-ready Docker stack requires more: healthchecks on containers, restart policy, centralized logs, secret rotation, volume backups. See our docker-compose-production-checklist guide for a complete checklist to apply before going live with any containerized service on your VPS.