On this page
What Is Traefik? covered the why — one HTTPS entry point instead of a spreadsheet of IP:port addresses. This post is the hands-on half: a working Traefik v3 deployment with automatic Let’s Encrypt certificates, an HTTP-to-HTTPS redirect, and your first service routed to a real domain. The copy-paste version lives in the Traefik + Let’s Encrypt playbook; this post explains each piece as you build it.
Throughout, replace example.com with a domain you actually control, and you@example.com with your real email. Traefik needs a domain to get public certificates — a cheap one is fine, and your services can still stay private.
What you’ll build
Three moving parts, all inside one Docker Compose file:
Traefik listens on 80 and 443, redirects HTTP to HTTPS, asks Let’s Encrypt for a certificate the first time each hostname is hit, and forwards decrypted traffic to the backend container — which never has to know anything about TLS.
Prerequisites
You need Docker and Compose installed, a domain you control, and an A record for your first hostname (whoami.example.com) pointing at the host’s public IP. For the certificate step, port 80 must be reachable from the internet so Let’s Encrypt can validate the domain. (Prefer no open ports? The DNS challenge below skips them.)
Step 1 — Configure Traefik’s entrypoints and resolver
Everything is passed to Traefik as command: flags in the Compose file — no separate static config. The important lines:
command:
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# redirect every HTTP request to HTTPS
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
# discover containers from Docker, but only opted-in ones
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
# Let's Encrypt via the HTTP-01 challenge
- "--certificatesresolvers.le.acme.httpchallenge=true"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.le.acme.email=you@example.com"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
web is plain HTTP, websecure is HTTPS, and the redirection lines mean nobody ever stays on an unencrypted connection. exposedbydefault=false is the safe default: a container is only routed if it explicitly opts in. The le resolver is the ACME client that will talk to Let’s Encrypt.
Step 2 — Prepare the ACME storage and start Traefik
Traefik stores issued certificates in acme.json. It refuses to use the file unless it’s owner-only (mode 600):
cd /opt/traefik
mkdir -p letsencrypt && touch letsencrypt/acme.json && chmod 600 letsencrypt/acme.json
sudo docker compose up -d
sudo docker compose logs -f traefik
Let’s Encrypt enforces rate limits on the production CA. While you’re getting the setup right, point the resolver at the staging server by adding --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory. Staging certs aren’t trusted by browsers, but they prove the flow works. Once it does, remove that line, delete acme.json, and restart to fetch a real certificate.
Step 3 — Route your first service
A backend service opts in with four labels. Here’s the bundled whoami test container:
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=le"
That’s the whole integration: enable routing, match a hostname, serve it on HTTPS, and use the le resolver for the certificate. Visit https://whoami.example.com and you should see a valid padlock and the container’s response — no certificate warning.
Copy those four labels onto the next container, change the router name (whoami → grafana) and the Host() rule, and Traefik discovers it and requests its certificate automatically. No file edits, no reloads.
Internal-only: the DNS challenge
If you don’t want port 80 open to the world, switch from HTTP-01 to the DNS-01 challenge. Traefik proves domain control by creating a TXT record through your DNS provider’s API — no inbound ports — and it can issue a single wildcard certificate (*.example.com) that covers every internal service at once. The playbook has the exact command swap and provider token setup. Pair it with Pi-hole local DNS so the names resolve on your LAN.
What’s next
You now have real HTTPS on a clean domain and a repeatable pattern for every future service. Sensible next steps:
- Put a real app behind it — Vaultwarden is a great first candidate.
- Add middleware — basic auth, IP allow-lists, or rate limiting — in front of any router.
- Reach your proxied services from anywhere without exposing them publicly using a Tailscale subnet router.
The official Traefik documentation is the reference for going deeper.
Related posts:
- What Is Traefik? Reverse Proxies Explained — the concepts behind this walkthrough.
- Traefik + Let’s Encrypt playbook — the copy-paste compose file.
- Docker on Proxmox: LXC vs VM — the host Traefik runs on.
- Pi-hole on Proxmox LXC — the local DNS names Traefik routes.
- Tailscale Subnet Router — reach proxied services securely from anywhere.
Sources: Traefik documentation, Let’s Encrypt documentation.
Comments
Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.