How to run Traefik v3 as a Docker reverse proxy with Let’s Encrypt
Run Traefik v3 with Docker Compose, route containers by labels, get Let’s Encrypt certificates automatically and keep the dashboard and Docker socket safe.
- Intermediate
- 40 min read
- Updated
Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13
This guide is not available in your language yet, so it is shown in English.
On this page
- Prerequisites
- Step 1 — Create the shared proxy network
- Step 2 — Create the Traefik project
- Step 3 — Start Traefik and read the logs
- Step 4 — Allow web traffic in the firewall
- Step 5 — Publish an app with labels
- Step 6 — Attach apps from other guides
- Step 7 — Protect the dashboard (optional)
- Step 8 — Understand the Docker socket risk
- Back up and restore
- Update Traefik
- Troubleshooting
- 404 page not found
- The browser warns about a TRAEFIK DEFAULT CERT certificate
- permissions 644 for /letsencrypt/acme.json are too open, please use 600
- Bad Gateway or Gateway Timeout
- client version 1.24 is too old
- Bind for 0.0.0.0:80 failed: port is already allocated
- Next steps
Traefik is a reverse proxy built for containers. Instead of a configuration file listing every site, it watches Docker and reads labels on your containers: a container with the right labels gets a route, an HTTPS certificate from Let's Encrypt and traffic, and the route disappears when the container stops. This guide runs Traefik v3 with Docker Compose on a Linux server, redirects HTTP to HTTPS, obtains certificates with the HTTP challenge, publishes an example app with labels, shows how to attach apps from other guides, optionally protects the dashboard, and explains the Docker socket risk, logs, backups and updates.
Prerequisites
- A server running Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12 or Debian 13.
- A non-root user with
sudorights and SSH key login: Secure a new Linux server and Set up SSH keys. - Docker Engine with the Compose plugin: Install Docker on Ubuntu or Install Docker on Debian. If Compose is new to you, read Docker Compose basics first.
- A domain with A (and, with working IPv6, AAAA) records for each hostname, for example
whoami.example.com, pointing at the server. - Ports 80 and 443 free on the host.
Traefik Labs does not publish minimum hardware requirements for Traefik Proxy. The figures below are a conservative starting point for Traefik alone; add what your apps need.
| Resource | Minimum (official) | Suggested starting point |
|---|---|---|
| CPU | Not published | 1 vCPU, shared with your apps |
| RAM | Not published | 256 MB free for Traefik |
| Disk | Not published | 1 GB free for the image, certificates and logs |
Step 1 — Create the shared proxy network
Traefik reaches your apps over a Docker network that it shares with them. Create it once; every app that Traefik should publish joins it:
docker network create proxy
docker network lsStep 2 — Create the Traefik project
Create the project directory and a folder for the certificate store:
sudo mkdir -p /opt/traefik
sudo chown $USER:$USER /opt/traefik
cd /opt/traefik
mkdir letsencryptPut the email address for your Let's Encrypt account in .env:
# /opt/traefik/.env
ACME_EMAIL[email protected]chmod 600 /opt/traefik/.envCreate /opt/traefik/compose.yaml. The current major version is Traefik v3; the v3.7 tag follows the 3.7 patch releases:
services:
traefik:
image: traefik:v3.7
restart: unless-stopped
security_opt:
- no-new-privileges:true
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.le.acme.email=${ACME_EMAIL:?set ACME_EMAIL in .env}"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
- "--log.level=INFO"
- "--accesslog=true"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- proxy
networks:
proxy:
name: proxy
external: trueWhat the flags do:
- Docker provider —
providers.dockerlets Traefik read container labels.exposedbydefault=falsemeans a container is ignored unless it has the labeltraefik.enable=true, so nothing is published by accident.providers.docker.network=proxytells Traefik which network to use when a container is on several. - Entry points —
weblistens on port 80 and permanently redirects every request towebsecureon port 443. - Certificate resolver —
lerequests certificates from Let's Encrypt with the HTTP-01 challenge on thewebentry point and stores them in/letsencrypt/acme.json, which is the./letsencryptfolder on the host. - Ports — Traefik is the one container that publishes ports to the internet, on 80 and 443.
Step 3 — Start Traefik and read the logs
docker compose up -d
docker compose ps
docker compose logs -f traefikdocker compose ps should show the container as running with 0.0.0.0:80->80/tcp and 0.0.0.0:443->443/tcp. The log shows the configuration being loaded and the Docker provider connecting. Stop following the log with Ctrl+C. Test the entry points from the server:
curl -I http://203.0.113.10
curl -kI https://203.0.113.10The HTTP request returns a permanent redirect to HTTPS. The HTTPS request (with -k, because no real certificate exists yet) returns 404, Traefik's answer when no route matches. That is expected until you add an app.
Step 4 — Allow web traffic in the firewall
Allow SSH, HTTP and HTTPS in ufw (install it first on Debian with sudo apt install ufw):
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableBe aware that Docker's documentation states that published container ports bypass ufw, because Docker routes the traffic before ufw's rules apply. Ports 80 and 443 of Traefik are reachable even without these rules, and a rule cannot close them. That is why apps behind Traefik publish no ports at all.
Step 5 — Publish an app with labels
The traefik/whoami image is a tiny web server that prints request details, ideal for a first test. Create /opt/whoami/compose.yaml:
sudo mkdir -p /opt/whoami
sudo chown $USER:$USER /opt/whoami
cd /opt/whoamiservices:
whoami:
image: traefik/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=le"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
networks:
proxy:
name: proxy
external: trueThe labels define a router named whoami that matches the hostname, listens on the HTTPS entry point and gets its certificate from the le resolver, and a service that sends traffic to port 80 inside the container. Start it and test:
docker compose up -d
curl -I https://whoami.example.com
curl https://whoami.example.comThe first certificate request takes a few seconds. The HTTPS request then returns HTTP/2 200, and the second command prints the request headers as the container sees them, including X-Forwarded-For and X-Forwarded-Proto: https. Follow docker compose logs -f traefik in /opt/traefik if the certificate does not arrive.
Step 6 — Attach apps from other guides
Most app guides in this library publish their app on a 127.0.0.1 port for Caddy or Nginx. With Traefik, make three changes to the app's compose.yaml: remove the ports: entry, add the proxy network, and add labels with a unique router name and the app's container port. For example, for an app listening on port 5678 inside its container:
services:
app:
# keep image, environment and volumes from the app's guide
networks:
- default
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=le"
- "traefik.http.services.myapp.loadbalancer.server.port=5678"
networks:
proxy:
name: proxy
external: trueList default as well: once a service names its networks, Compose no longer attaches it to the project's default network, and the app would lose its connection to its own database. Only the web-facing service needs the proxy network and labels; databases stay on default. Router and service names must be unique across all projects on the server. WebSocket connections work through Traefik without extra settings.
Step 7 — Protect the dashboard (optional)
The API and dashboard are disabled unless you enable them. If you want the dashboard, publish it on its own hostname with basic authentication; never use api.insecure, which opens it without a login. Generate a bcrypt password hash and double every $, so that Compose does not treat it as a variable:
sudo apt install apache2-utils
htpasswd -nB admin | sed -e 's/\$/\$\$/g'Then edit the traefik service in /opt/traefik/compose.yaml. Add - "--api.dashboard=true" as a new line at the end of the command list, and add a labels: key at the same level as command, ports and volumes. Paste the output of the htpasswd command into the last label:
services:
traefik:
# command, ports and volumes stay as in Step 2
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=le"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth"
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$05$$replace-with-your-hash"Run docker compose up -d, add a DNS record for traefik.example.com and open https://traefik.example.com/dashboard/. To limit access further, add an IP allow list middleware such as traefik.http.middlewares.dashboard-ip.ipallowlist.sourcerange=198.51.100.7/32 and list both middlewares, separated by a comma.
Step 8 — Understand the Docker socket risk
Traefik reads labels through the Docker API, which is why /var/run/docker.sock is mounted. Traefik's documentation warns that unrestricted access to the Docker API is a security concern: an attacker who takes over Traefik could control Docker and, through it, the host. The :ro flag does not change that; it only stops the container from replacing the socket file.
Reduce the risk in layers:
- keep Traefik on a current v3 release (see the update section),
- keep
no-new-privilegesand do not add other privileges to the Traefik container, - for stronger isolation, run a socket proxy such as the one Traefik's documentation mentions, allow it only read requests for containers, and point Traefik at it with
--providers.docker.endpoint=tcp://socket-proxy:2375instead of mounting the socket.
Back up and restore
All state lives in /opt/traefik: compose.yaml, .env and letsencrypt/acme.json with your certificates and ACME account. The app projects keep their own labels, so back them up with the apps. Because acme.json belongs to root with mode 600, use sudo:
sudo mkdir -p /opt/backups
sudo tar czf /opt/backups/traefik-$(date +%F).tar.gz -C /opt traefikTo restore on a new server with Docker installed, recreate the network, unpack the archive and start Traefik:
docker network create proxy
sudo tar xzf /opt/backups/traefik-2026-10-09.tar.gz -C /opt
cd /opt/traefik
docker compose up -dKeep the archive private, because it contains private keys, and copy it off the server.
Update Traefik
Read the release notes on Traefik's GitHub releases page first, take a backup, then pull and recreate:
cd /opt/traefik
docker compose pull
docker compose up -d
docker compose logs --tail 50 traefikThe v3.7 tag receives patch releases. To move to a newer minor version, change the tag (for example to the next v3.x listed on the releases page) after reading the migration notes in Traefik's documentation. Apps keep running during the update; only the proxy restarts for a moment.
Troubleshooting
404 page not found
Traefik answered, but no router matched. Check that the container has traefik.enable=true (required because exposedbydefault=false), that the Host rule contains exactly the hostname you requested, and that the container runs and is on the proxy network: docker network inspect proxy lists the attached containers.
The browser warns about a TRAEFIK DEFAULT CERT certificate
Traefik could not get a Let's Encrypt certificate and serves its built-in self-signed one. Read docker compose logs traefik in /opt/traefik for the ACME error. Usual causes are DNS records that do not point at the server, an AAAA record without working IPv6, or port 80 blocked by an external firewall. Repeated failures count against Let's Encrypt's limit of 5 failed validations per hostname per hour.
permissions 644 for /letsencrypt/acme.json are too open, please use 600
The certificate store has the wrong mode. Fix it with sudo chmod 600 /opt/traefik/letsencrypt/acme.json and restart Traefik with docker compose restart traefik.
Bad Gateway or Gateway Timeout
Traefik matched the router but cannot reach the container. Make sure the container is on the proxy network and that loadbalancer.server.port is the port the app listens on inside the container, not a host port. If the app is on several networks, the providers.docker.network=proxy flag, or the label traefik.docker.network=proxy, tells Traefik which one to use.
client version 1.24 is too old
An older Traefik release cannot negotiate the API version with a current Docker Engine. Traefik 3.6.1 added automatic API version negotiation; update to a current v3 tag as described above.
Bind for 0.0.0.0:80 failed: port is already allocated
Another web server, such as Nginx, Apache or Caddy, already uses port 80 or 443 on the host. Find it with sudo ss -tlpn 'sport = :80', then stop and disable it before starting Traefik.
Next steps
- Learn the Compose features used here in Docker Compose basics.
- Manage your stacks in a web UI with Portainer.
- Prefer a click-based proxy? See Nginx Proxy Manager.
- Find a server for container workloads on the Docker hosting page.
- Explore routers, middlewares and the DNS challenge in the Traefik documentation.
Frequently asked questions
Why use Traefik instead of Caddy or Nginx?
Traefik reads container labels from Docker and updates its routes on its own when containers start or stop, so you never edit a proxy config file. If most of your apps run as containers and you like that model, Traefik fits well; for a few apps, Caddy on the host is simpler.
Do my apps still need published ports with Traefik?
No. Apps join a shared Docker network with Traefik, and Traefik connects to their container port over that network. Only Traefik publishes ports 80 and 443, which keeps everything else off the internet.
Is mounting the Docker socket read-only enough?
No. The :ro flag only stops the container from replacing the socket file; API calls through it can still control Docker and therefore the host. Keep Traefik updated and, for stronger isolation, put a socket proxy in front of the Docker API that allows only read requests.
Should I enable the Traefik dashboard?
Only if you use it. The API and dashboard are off by default. If you turn the dashboard on, publish it through its own HTTPS router with authentication, never with api.insecure on an open port.
Can Traefik get wildcard certificates?
Yes, but only with the DNS challenge, which needs your DNS provider’s API credentials. This guide uses the HTTP challenge, which issues one certificate per hostname and needs port 80 reachable.
Sources
- github.com/traefik/traefik/releases
- github.com/traefik/traefik/releases/tag/v3.6.1
- doc.traefik.io/traefik/setup/docker
- doc.traefik.io/traefik/expose/docker/basic
- doc.traefik.io/traefik/reference/install-configuration/providers/do…
- doc.traefik.io/traefik/reference/install-configuration/entrypoints
- doc.traefik.io/traefik/reference/install-configuration/tls/certific…
- doc.traefik.io/traefik/reference/install-configuration/api-dashboard
- doc.traefik.io/traefik/reference/routing-configuration/other-provid…
- doc.traefik.io/traefik/reference/routing-configuration/http/middlew…
- doc.traefik.io/traefik/reference/routing-configuration/http/middlew…
- raw.githubusercontent.com/traefik/traefik/master/pkg/provider/acme/…