How to Set Up Traefik with Let's Encrypt

A hands-on walkthrough: deploy Traefik v3 with Docker, get automatic browser-trusted HTTPS from Let's Encrypt, redirect all HTTP to HTTPS, and route your first service to a clean domain using labels.

On this page
  1. What you’ll build
  2. Prerequisites
  3. Step 1 — Configure Traefik’s entrypoints and resolver
  4. Step 2 — Prepare the ACME storage and start Traefik
  5. Step 3 — Route your first service
  6. Internal-only: the DNS challenge
  7. What’s next

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.

Make these values your own

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:

Let’s EncryptACME challengeTraefik:80 → :443terminates TLSwhoami.example.comcontainer, no TLSissues certYour browserhttps://buildahomelab.dev

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:

The Traefik service (excerpt)

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):

Create acme.json and launch

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
Test against staging first

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:

whoami service labels

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.

1Add any other app the same way

Copy those four labels onto the next container, change the router name (whoamigrafana) 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:

Sources: Traefik documentation, Let’s Encrypt documentation.

Comments

Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.