Traefik Reverse Proxy Guide: Automatic HTTPS for Every Service You Run
Running multiple services on a single server — n8n, Keycloak, Dify, Portainer — means you need something intelligent sitting in front of them, routing traffic to the right place and handling HTTPS automatically. Traefik is that something. It's a modern reverse proxy built specifically for containerized environments. It watches your Docker labels, provisions Let's Encrypt certificates on demand, and routes requests to the right service without you touching a config file every time you add something new. This Traefik reverse proxy guide walks you through the full setup from scratch.
Prerequisites
- A Linux server (Ubuntu 20.04+ recommended) with a public IP
- Docker Engine and Docker Compose v2 installed
- A domain name with DNS access — you'll need to point subdomains at your server
- Ports 80 and 443 open and not in use by anything else (Apache, Nginx, etc.)
- Basic familiarity with Docker Compose
Check that ports 80 and 443 are free before starting:
sudo ss -tlnp | grep -E ':80|:443'
# Should return nothing — if something shows up, stop it first
sudo systemctl stop nginx apache2 2>/dev/null; true
What Is Traefik and Why Use It?
Traefik is an edge router and reverse proxy designed for dynamic infrastructure. Unlike Nginx or HAProxy — where you edit config files and reload — Traefik watches your Docker daemon in real time and automatically picks up new services based on container labels. Add a container with the right labels and Traefik starts routing to it. Remove the container and the route disappears. No reloads, no manual config.
What Makes Traefik Stand Out
- Automatic HTTPS — built-in Let's Encrypt integration with zero manual cert management
- Docker-native — reads routing rules directly from container labels
- Dynamic configuration — routes update live without restarts
- Middleware system — auth, rate limiting, headers, redirects — composable and reusable
- Built-in dashboard — visual overview of all routes, services, and middlewares
- Multi-provider — works with Docker, Kubernetes, Consul, file-based config, and more
For self-hosted stacks running 5+ services on a single VPS, Traefik is the cleanest solution available. Every service you add just needs a few labels — Traefik handles the rest.
Project Structure and Initial Setup
Directory Layout
Keep Traefik's config isolated in its own directory. This is what we'll build:
mkdir -p ~/traefik/config
cd ~/traefik
# Create the ACME (Let's Encrypt) storage file
touch config/acme.json
chmod 600 config/acme.json
# Create the external Docker network all services will share
docker network create traefik_proxy
The chmod 600 on acme.json is mandatory — Traefik refuses to start if the permissions are too open, as a security guard on your private key storage.
Static Configuration File
Traefik has two layers of config: static (entrypoints, providers, certificate resolvers — set at startup) and dynamic (routes, services, middlewares — updated live). Create the static config:
# config/traefik.yml
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: /etc/traefik/acme.json
httpChallenge:
entryPoint: web
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik_proxy
file:
directory: /etc/traefik/dynamic
watch: true
log:
level: INFO
accessLog: {}
Key decisions baked in here: HTTP always redirects to HTTPS, Let's Encrypt uses the HTTP-01 challenge, and exposedByDefault: false means containers are not routed unless they explicitly opt in with labels. That's the secure default.
Deploying Traefik with Docker Compose
The Core Compose File
# docker-compose.yml
version: '3.8'
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./config/traefik.yml:/etc/traefik/traefik.yml:ro
- ./config/acme.json:/etc/traefik/acme.json
- ./config/dynamic:/etc/traefik/dynamic:ro
networks:
- traefik_proxy
labels:
- "traefik.enable=true"
# Dashboard route
- "traefik.http.routers.dashboard.rule=Host(`traefik.yourdomain.com`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth"
# Basic auth for dashboard (generated below)
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$10$$hashedpasswordhere"
networks:
traefik_proxy:
external: true
Generating a Dashboard Password
Generate a bcrypt-hashed password for the dashboard basic auth. Note the double $$ in the Compose label — that's required to escape the $ in Docker Compose:
# Install htpasswd if needed
sudo apt install apache2-utils -y
# Generate hashed password
htpasswd -nB admin
# Enter your password when prompted
# Output: admin:$2y$05$...
# In the Compose label, replace every $ with $$ :
# admin:$$2y$$05$$...
Start Traefik
docker compose up -d
docker compose logs -f traefik
Watch the logs for Configuration loaded from file and the first certificate request going out. Within 30 seconds, https://traefik.yourdomain.com should be live with a valid cert and the dashboard behind basic auth.
Routing Services Through Traefik
Adding a Service with Labels
Every service you want Traefik to route just needs to be on the traefik_proxy network and have the right labels. Here's a minimal example deploying Whoami (a test container that echoes request info):
# whoami/docker-compose.yml
version: '3.8'
services:
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- traefik_proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.yourdomain.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
networks:
traefik_proxy:
external: true
Deploy it and Traefik picks it up instantly — no restart needed:
docker compose -f whoami/docker-compose.yml up -d
curl https://whoami.yourdomain.com
Routing to a Non-Docker Service
If you have a service running directly on the host (not in Docker) — like Ollama on port 11434 — use file-based dynamic config. Create a file in config/dynamic/:
# config/dynamic/ollama.yml
http:
routers:
ollama:
rule: "Host(`ollama.yourdomain.com`)"
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: ollama
middlewares:
- ollama-auth
services:
ollama:
loadBalancer:
servers:
- url: "http://172.17.0.1:11434"
middlewares:
ollama-auth:
basicAuth:
users:
- "admin:$2y$05$hashedpasswordhere"
Traefik watches the dynamic/ directory and picks up new files automatically — no restart, no reload.
Middlewares: Auth, Headers, and Rate Limiting
Reusable Middleware via File Config
Defining middlewares in a shared dynamic config file lets you reuse them across multiple routers. Create config/dynamic/middlewares.yml:
# config/dynamic/middlewares.yml
http:
middlewares:
# Secure headers for all services
secure-headers:
headers:
sslRedirect: true
forceSTSHeader: true
stsSeconds: 31536000
stsIncludeSubdomains: true
contentTypeNosniff: true
browserXssFilter: true
referrerPolicy: "strict-origin-when-cross-origin"
# Rate limiting
rate-limit:
rateLimit:
average: 100
burst: 50
# IP allowlist (internal only)
internal-only:
ipAllowList:
sourceRange:
- "10.0.0.0/8"
- "192.168.0.0/16"
- "172.16.0.0/12"
Reference any of these by name in your service labels or router configs:
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`app.yourdomain.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
- "traefik.http.routers.myapp.middlewares=secure-headers@file,rate-limit@file"
The @file suffix tells Traefik the middleware is defined in a file provider rather than a Docker label — this distinction matters when mixing both config sources.
Tips, Gotchas, and Troubleshooting
Certificate Not Provisioning
Let's Encrypt HTTP-01 challenge requires that http://yourdomain.com/.well-known/acme-challenge/ is reachable from the internet. The most common blockers:
- Port 80 is blocked by a firewall rule —
sudo ufw allow 80/tcp - DNS hasn't propagated yet —
dig +short yoursubdomain.yourdomain.comshould return your server IP acme.jsonpermissions are wrong — must be exactly600- You hit the Let's Encrypt rate limit (5 failed validations per hour) — check Traefik logs for the exact error
# Fix permissions
chmod 600 ~/traefik/config/acme.json
# Check Traefik logs for ACME errors
docker logs traefik 2>&1 | grep -i acme
docker logs traefik 2>&1 | grep -i error
Service Not Showing Up in Dashboard
If a container isn't appearing in Traefik's routing table:
- Confirm
traefik.enable=trueis set on the container - Confirm the container is on the
traefik_proxynetwork - Check that
exposedByDefault: falseis in your static config — if it'strue, all containers are exposed even without labels
# Verify container is on the right network
docker inspect your-container-name | jq '.[0].NetworkSettings.Networks'
# Manually attach if missing
docker network connect traefik_proxy your-container-name
502 Bad Gateway After Routing
Traefik found the container but can't reach it. Usually because the container's internal port isn't exposed correctly. If Traefik can't auto-detect the port, specify it explicitly in a label:
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`app.yourdomain.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
# Explicitly set the container port
- "traefik.http.services.myapp.loadbalancer.server.port=8080"
Using DNS Challenge for Wildcard Certs
If you want a wildcard cert (*.yourdomain.com) instead of per-subdomain certs, switch to the DNS-01 challenge. This requires a DNS provider plugin. Update your static config:
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: /etc/traefik/acme.json
dnsChallenge:
provider: cloudflare # or digitalocean, route53, etc.
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
Add your DNS provider credentials as environment variables in the Traefik container (e.g., CF_DNS_API_TOKEN for Cloudflare). Traefik's ACME provider docs list the required env vars for each provider.
Pro Tips
- Never expose the Docker socket as read-write in production without a socket proxy — use docker-socket-proxy to limit which Docker API calls Traefik can make, reducing attack surface if Traefik is ever compromised.
- Use Traefik's staging ACME server while testing to avoid hitting Let's Encrypt rate limits: add
caServer: https://acme-staging-v02.api.letsencrypt.org/directoryunderacme:while you're getting the config right. - Label-based middleware applies per-router — if you have multiple routers for the same service (e.g., HTTP and HTTPS), apply the middleware to both or use a catch-all entrypoint redirect.
- Traefik's dashboard is your best debugging tool — it shows exactly which routers, services, and middlewares are active and whether they're healthy. Keep it accessible but always behind auth.
Wrapping Up
A solid Traefik reverse proxy setup is the foundation of a well-run self-hosted stack. Once it's running, adding a new service is as simple as deploying a container with three or four labels — no Nginx configs, no cert renewals, no reload scripts. Traefik handles the routing, the HTTPS, and the middleware automatically.
Start with the core setup here, wire up your first service with Whoami to confirm everything works end-to-end, then roll out the same pattern to every other service on your server. The dashboard will give you a clean real-time view of your entire routing layer as it grows.
Running a Multi-Service Stack in Production?
If you're managing a self-hosted platform with dozens of services, need high-availability Traefik with failover, or want your entire infrastructure reviewed and hardened — the sysbrix team can help. We design and operate production-grade self-hosted infrastructure for teams that care about reliability and control.
Talk to Us →