What is Traefik?
Traefik is a cloud-native reverse proxy and load balancer. It watches Docker for containers, reads routing rules from their labels, and requests and renews Let's Encrypt certificates automatically. Add a few labels to any service, run docker compose up -d, and it is live on its own HTTPS hostname. Traefik is open source under the MIT license.
Requirements
- A Linux server with Docker Engine and Docker Compose v2. Traefik is light: 1 vCPU and 512 MB of RAM handle a busy homelab.
- A domain, with an A record for each hostname (or a wildcard
*.example.com) pointing at the server's public IP. - Ports 80 and 443 open to the internet so Let's Encrypt can validate the domain.
Step 1: Prepare the server
This guide assumes Ubuntu 24.04 with Docker installed from the official Docker Engine guide. Make sure nothing else (Apache, Nginx, Caddy) is already bound to ports 80 or 443.
mkdir -p ~/traefik/letsencrypt && cd ~/traefik
Create a bcrypt password hash for the dashboard. htpasswd comes from the apache2-utils package:
sudo apt install -y apache2-utils
htpasswd -nbB admin 'CHANGE_ME' | sed -e 's/\$/\$\$/g'
Replace CHANGE_ME with a strong password (for example the output of openssl rand -hex 32). The sed step doubles every $, which Docker Compose needs so it does not treat the hash as a variable. Copy the output for the next step.
Step 2: Create the Docker Compose file
This follows the production setup in the official Traefik docs: HTTP redirects to HTTPS, certificates come from Let's Encrypt over the HTTP challenge, and only containers with traefik.enable=true are exposed. Save it as docker-compose.yml, replacing the email, hostnames and the hash:
services:
traefik:
image: traefik:v3.7
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
command:
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http.tls.certresolver=le"
- "--certificatesresolvers.le.acme.email=you@example.com"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--api.dashboard=true"
- "--log.level=INFO"
- "--accesslog=true"
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth"
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$05$$PASTE_YOUR_HASH"
whoami:
image: traefik/whoami:v1.12
container_name: whoami
restart: unless-stopped
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
networks:
proxy:
name: proxy
The v3.7 tag tracks patch releases of Traefik 3.7. The Docker socket is mounted read-only, but read access still reveals every container on the host, so never expose the dashboard without authentication.
Step 3: Start and open the app
docker compose up -d
docker compose logs -f traefik
Watch the logs for certificate errors. After a few seconds, open https://whoami.example.com: the whoami test container prints the request headers it received, which proves routing and HTTPS work. Then open https://traefik.example.com and sign in with the dashboard user to see routers, services and middlewares.
Step 4: Put it behind HTTPS
Traefik is the HTTPS layer, so this step is about routing your other apps through it. In any other Compose project, join the proxy network and add labels:
services:
app:
image: your/app
networks: [proxy]
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.services.app.loadbalancer.server.port=8080"
networks:
proxy:
external: true
Remove the ports: section from apps routed through Traefik. Docker-published ports bypass ufw, so a published app port stays reachable over plain HTTP even with the firewall on.
Backups and upgrades
Back up docker-compose.yml and the letsencrypt folder. acme.json holds your certificates and the ACME account key, and losing it forces fresh certificates for every domain. To upgrade within 3.7:
docker compose pull && docker compose up -d
For a new minor version, change the tag and read the migration notes in the Traefik docs first.
Troubleshooting
- 404 page not found: the router rule does not match, the container lacks
traefik.enable=true, or it is not on theproxynetwork. - Bad Gateway: Traefik picked the wrong port. Set
loadbalancer.server.portexplicitly. - Certificate stays "TRAEFIK DEFAULT CERT": the HTTP challenge failed. Check that DNS points at this server and port 80 is open, then read the ACME error in the logs.
- Dashboard login always fails: the
$signs in the hash were not doubled in the Compose label.
Next steps
Add middlewares for security headers and rate limiting, switch to the DNS challenge for wildcard certificates or servers behind NAT, and put Authentik in front of private apps with a forward-auth middleware.
Spotted something out of date? Tell us and we will update the guide.