Every self-hosted stack needs a reverse proxy. It's the piece that sits in front of everything else — terminating TLS, routing requests to the right container, handling certificates. Get it wrong and you're either manually editing config files every time you add a service, or running a separate tool just to manage certificates.
Traefik takes a different approach. Instead of writing routing rules and reloading configs, you put labels on your Docker containers and Traefik figures out the rest. New container starts, route appears. Container stops, route disappears. Certificates get provisioned automatically via Let's Encrypt. The dashboard shows you what's routed where in real time.
It's the reverse proxy that makes the most sense for self-hosted stacks running multiple services on a single server — which is exactly the kind of setup DevOpsPack readers are running.
What Traefik is
Traefik is an open-source cloud-native reverse proxy and load balancer created by Traefik Labs. The current version is v3, with v3.5 being the latest stable release as of mid-2025. It's written in Go, MIT licensed, and has 55,000+ GitHub stars.
The core philosophy: dynamic configuration over static configuration. Traditional reverse proxies like Nginx use static config files — you define routes, reload the daemon, routes are active. Traefik polls the Docker socket (or Kubernetes API, or Consul, or several other providers) and reconfigures routing in real time as containers start and stop. No reload required, no config file to edit.
Three concepts you need to understand to use Traefik:
- Entrypoints — the ports Traefik listens on. Typically port 80 (HTTP) and port 443 (HTTPS).
- Routers — rules that match incoming requests (by hostname, path, headers) and send them to a service.
- Services — the backend that receives traffic, usually a container on a specific port.
- Middleware — processing between router and service: redirect HTTP to HTTPS, add auth headers, rate limit, strip path prefixes.
In Docker mode, routers and services are configured via container labels. Traefik reads these labels and builds its routing table automatically.
Basic setup with Docker Compose
A minimal Traefik setup that handles HTTPS with Let's Encrypt:
services:
traefik:
image: traefik:v3
container_name: traefik
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--api.dashboard=true"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.service=api@internal"Two things to note: exposedbydefault=false means containers are not routed unless they have traefik.enable=true — important for security. The Docker socket is mounted read-only.
Now adding any service is just labels:
ghost:
image: ghost:5
restart: unless-stopped
environment:
url: https://devopspack.com
labels:
- "traefik.enable=true"
- "traefik.http.routers.ghost.rule=Host(`devopspack.com`)"
- "traefik.http.routers.ghost.tls.certresolver=letsencrypt"
- "traefik.http.routers.ghost.entrypoints=websecure"Traefik reads these labels, provisions a certificate for devopspack.com, and starts routing HTTPS traffic to the Ghost container. No config file edit. No reload.
HTTP to HTTPS redirect
Add a global middleware to redirect all HTTP traffic to HTTPS — define it once on the Traefik container and it applies everywhere:
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"Wildcard certificates with DNS-01
HTTP-01 challenge requires the domain to be publicly accessible on port 80. For internal services — Grafana, Uptime Kuma, Authentik, Gitea — that aren't exposed to the internet but still need HTTPS, you want DNS-01 challenge instead. This proves domain ownership through a DNS TXT record rather than an HTTP request, so the service doesn't need to be publicly reachable.
Traefik supports DNS-01 with most major DNS providers. With Cloudflare:
traefik:
environment:
CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN}
command:
- "--certificatesresolvers.cloudflare.acme.dnschallenge=true"
- "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.cloudflare.acme.email=you@example.com"
- "--certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme.json"Then on any internal service:
labels:
- "traefik.http.routers.grafana.rule=Host(`grafana.internal.example.com`)"
- "traefik.http.routers.grafana.tls.certresolver=cloudflare"One DNS API token, wildcard certificate provisioned automatically, HTTPS working on an internal service with no public exposure.
Middleware: security headers, auth, rate limiting
Middleware is where Traefik gets powerful. Define a middleware once, reference it on any router.
Security headers — apply to all routers:
labels:
- "traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.secure-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.secure-headers.headers.browserXssFilter=true"
- "traefik.http.middlewares.secure-headers.headers.referrerPolicy=strict-origin-when-cross-origin"Basic auth for the Traefik dashboard:
# Generate with: echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g
- "traefik.http.middlewares.auth.basicauth.users=user:$$apr1$$..."
- "traefik.http.routers.traefik.middlewares=auth"Rate limiting:
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"IP allowlist — restrict a service to internal IPs only:
- "traefik.http.middlewares.internal-only.ipallowlist.sourcerange=192.168.0.0/16,10.0.0.0/8"HTTP/3 and QUIC
Traefik v3 added native HTTP/3 (QUIC) support. Enable it per entrypoint:
- "--entrypoints.websecure.http3=true"
- "--entrypoints.websecure.http3.advertisedPort=443"You'll also need to expose UDP 443:
ports:
- "443:443/tcp"
- "443:443/udp"HTTP/3 improves performance for clients on high-latency or unreliable connections by using QUIC's connection migration and 0-RTT handshakes. For most self-hosted setups it's a minor improvement; for high-traffic sites it matters more.
The Traefik dashboard
Traefik ships with a built-in dashboard showing your active routers, services, middleware, and entrypoints in real time. It's read-only — you can't make changes from it — but it's invaluable for debugging routing issues and understanding what Traefik has discovered.
Keep it behind auth (basicauth middleware or Authentik forward auth) and either IP allowlist or a private DNS entry. The dashboard exposes your infrastructure map — don't leave it open to the internet.
File-based configuration for non-Docker services
Not everything runs in Docker. Legacy apps, services on other servers, or anything that doesn't support container labels can be routed through Traefik using the file provider:
# traefik/dynamic/legacy.yml
http:
routers:
legacy-app:
rule: "Host(`legacy.example.com`)"
service: legacy-app
tls:
certResolver: letsencrypt
services:
legacy-app:
loadBalancer:
servers:
- url: "http://192.168.1.50:8080"Enable the file provider in your Traefik command:
- "--providers.file.directory=/traefik/dynamic"
- "--providers.file.watch=true"Traefik watches the directory and picks up changes without a restart. This is how you route to services outside Docker — other servers on your LAN, bare metal apps, VMs.
Traefik vs Nginx vs Caddy
Nginx — the performance king. Lowest latency, highest throughput per CPU core, battle-tested at every scale. Static config files, manual certificate management (or certbot), no automatic service discovery. Right choice for static-heavy workloads, caching, and when you need the finest-grained control over request handling.
Caddy — the simplest path to automatic HTTPS. Caddyfile syntax is minimal and readable. Certificates are automatic without any configuration at all. No dashboard, no middleware ecosystem as rich as Traefik's, no Docker label-based auto-discovery. Right choice for simple setups where you want HTTPS to just work without thinking about it.
Traefik — the dynamic proxy for multi-service Docker environments. Auto-discovery, rich middleware, dashboard, DNS-01 wildcard certs, HTTP/3. Higher resource usage than Nginx, more initial complexity than Caddy. Right choice when you're running 5+ services in Docker and don't want to edit a proxy config every time you add one.
The practical rule: choose Traefik when your services churn; choose Nginx when they don't. A server running 10 self-hosted tools in Docker — Ghost, Gitea, Grafana, Uptime Kuma, Authentik, Postiz, NetBird, Cal.com — is exactly the environment Traefik is designed for.
Traefik with NetBird and Authentik
Two tools we've covered on DevOpsPack integrate particularly well with Traefik:
NetBird requires Traefik as the external reverse proxy for its built-in reverse proxy feature. TLS passthrough support, which Traefik provides, is a hard requirement for the NetBird proxy to work correctly.
Authentik integrates with Traefik via forward authentication — Traefik sends each request to Authentik for verification before forwarding it to the backend. This gives you SSO protection on any service, even ones that don't support OIDC natively:
labels:
- "traefik.http.middlewares.authentik.forwardauth.address=http://authentik:9000/outpost.goauthentik.io/auth/traefik"
- "traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups"
# Apply to any service:
- "traefik.http.routers.myservice.middlewares=authentik"My take
Traefik is the reverse proxy I run on every multi-service self-hosted server. The label-based Docker integration removes an entire category of operational work — I don't think about certificate renewal, I don't edit proxy configs when adding services, and the dashboard makes it immediately obvious when something isn't routing correctly.
The initial setup has a learning curve: the entrypoint/router/service/middleware model takes a bit to internalize, and Docker socket security deserves attention. But once it's running, it's one of those pieces of infrastructure you configure once and then genuinely forget about.
For anyone running a self-hosted stack — Ghost, Gitea, Grafana, Authentik, Uptime Kuma, or any combination of the tools covered on this blog — Traefik is the routing layer that ties it all together cleanly.
PIPOLINE · DEVOPS CONSULTING
Need help setting up Traefik?
Getting Traefik right — DNS-01 wildcard certificates, Authentik forward auth, file provider for non-Docker services, security middleware — takes time to get right the first time. I can set up a production-ready Traefik stack for your server, wired into your existing services. You get automatic HTTPS and proper routing without spending a day reading documentation.
Get in touch at pipoline.com →
Member discussion