Set Up Traefik as a Docker Reverse Proxy With Automatic HTTPS
Put Traefik in front of Docker services, let labels drive routing, and issue TLS certificates automatically without turning the setup into a giant reverse-proxy theory project.
How to run Traefik, expose only labeled containers, redirect HTTP to HTTPS, and verify routing with a sample service.
Operators already using Docker Compose who want a cleaner path than hand-editing Nginx vhosts for every new app.
The fastest way to make a mess is letting every container auto-publish or exposing the dashboard publicly without a reason.
Before you begin
- A Linux host with Docker Engine and the Compose plugin already working.
- A DNS name such as
apps.example.comorwhoami.example.compointed at the host. - Ports
80and443reachable from the internet if you want automatic public certificates. - No other service already bound to
80or443.
Traefik's Docker guide centers on the Docker provider, named entrypoints, and label-based routing. The safest small-operator pattern is to set
providers.docker.exposedbydefault=false so a new container stays dark until you label it on purpose.
80/443 ports, and new apps should become
routable only when you deliberately join the shared proxy network and add labels.
Step 1: Prepare the host and DNS
Create a dedicated application directory and the ACME storage file:
mkdir -p ~/apps/traefik/letsencrypt
cd ~/apps/traefik
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json
Create a shared Docker network that future app stacks can join:
docker network create proxy
Point your chosen DNS name at the host before you continue. If DNS is wrong, Traefik can start normally while certificate issuance quietly fails later.
Step 2: Create the Traefik Compose stack
Save this as docker-compose.yml inside ~/apps/traefik:
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
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --certificatesresolvers.letsencrypt.acme.email=admin@example.com
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.letsencrypt.acme.httpchallenge=true
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
- --api.dashboard=true
- --accesslog=true
ports:
- 80:80
- 443:443
- 127.0.0.1:8080:8080
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- proxy
networks:
proxy:
external: true
The local-only 127.0.0.1:8080:8080 bind keeps the Traefik dashboard off the public internet by default. Reach it later through SSH
port forwarding or another private admin path you already trust.
docker compose up -d
docker compose ps
Step 3: Route a sample app with labels
Add a simple demo service to prove the routing model before you front a real app:
services:
whoami:
image: traefik/whoami:v1.10
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
- traefik.http.routers.whoami.tls.certresolver=letsencrypt
- traefik.http.services.whoami.loadbalancer.server.port=80
networks:
proxy:
external: true
The important pattern is not the demo container itself. It is that routing lives with the application service. That keeps app-specific exposure rules next to the app instead of buried in a central proxy file that grows into a junk drawer.
Step 4: Verify HTTPS and the private dashboard path
Check the public route first:
curl -I https://whoami.example.com
docker logs traefik --tail 100
Then inspect the dashboard privately from the host:
curl http://127.0.0.1:8080/dashboard/
What you want to confirm:
- HTTP redirects to HTTPS.
- The certificate is issued successfully.
- Only labeled containers appear in the Traefik dashboard.
- The dashboard is reachable locally but not published on the open internet.
Step 5: Operate it safely
Join future app stacks to the external proxy network and add labels only to the services meant for inbound traffic. Internal databases,
queues, and admin-only helpers should stay off the proxy network unless you have a specific reason otherwise.
Good day-two checks:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
docker logs traefik --tail 50
docker network inspect proxy
Rollback and update notes
If a new app label breaks routing, remove that app from the proxy network or disable its labels first instead of tearing down the proxy that every other service depends on.
docker compose down
docker compose pull
docker compose up -d
Keep the letsencrypt/acme.json file backed up. Losing it is not fatal, but restoring it avoids unnecessary certificate churn during
rebuilds.
Troubleshooting
The container starts but certificates never appear.
Check DNS first, then verify ports 80 and 443 are not blocked upstream.
The app works on HTTP but not HTTPS.
Confirm the router uses the websecure entrypoint and has a TLS certresolver label.
The dashboard is reachable publicly.
Remove any public router for the dashboard and keep the host port bound to 127.0.0.1 only.
Every new container keeps showing up unexpectedly.
Recheck providers.docker.exposedbydefault=false and make sure old test labels are not lingering in other stacks.