Traefik Reverse Proxy Setup with Automatic HTTPS for Multiple Applications
We earn commissions when you shop through the links on this page, at no additional cost to you.
When I started running multiple self-hosted applications on my homelab, I quickly realized that managing SSL certificates and routing traffic was becoming a nightmare. Nginx and Caddy are great, but I wanted something that could dynamically detect new containers and automatically provision HTTPS certificates without manual intervention. That's where Traefik changed everything for me.
Traefik is a modern reverse proxy and load balancer built for containerized environments. Unlike traditional reverse proxies that require you to edit config files and reload services, Traefik watches your Docker daemon, reads labels from running containers, and automatically updates routing rules in real-time. Combined with Let's Encrypt integration, you get free SSL certificates provisioned automatically. In this guide, I'll walk you through a production-ready Traefik setup that serves multiple applications with zero manual certificate management.
Why I Chose Traefik Over Nginx Proxy Manager
I spent three months running Nginx Proxy Manager (NPM) before switching to Traefik. NPM was simple and had a nice web UI, but every time I spun up a new Docker service, I had to manually add a proxy host, configure SSL, and restart the service. With a homelab that changes constantly, this became tedious. Traefik eliminated that friction entirely—I just add labels to my docker-compose files and Traefik handles everything automatically.
The learning curve was steeper initially, but the payoff has been massive. I now manage 12+ self-hosted applications (Nextcloud, Jellyfin, Gitea, Vaultwarden, AdGuard Home, Open WebUI, and more) all behind one Traefik instance with zero manual intervention.
Prerequisites and Architecture
You'll need:
- A Linux server or VPS running Docker and Docker Compose (I recommend a 2GB+ RAM setup from RackNerd KVM VPS)
- A registered domain name with DNS control
- Port 80 and 443 accessible from the internet (or behind a reverse tunnel if using Cloudflare Tunnel)
- Basic familiarity with Docker Compose and DNS
The architecture I use is straightforward: Traefik listens on ports 80 and 443, accepts incoming requests, reads container labels to determine routing rules, and forwards traffic to backend services. Let's Encrypt certificates are stored on disk and renewed automatically before expiration.
Core Traefik Docker Compose Setup
Here's the complete docker-compose.yml file I use in production. This creates the Traefik container with Let's Encrypt support and proper configuration:
version: '3.8'
services:
traefik:
image: traefik:v3.1.0
container_name: traefik
restart: always
ports:
- "80:80"
- "443:443"
- "8080:8080"
environment:
- CLOUDFLARE_EMAIL=${CLOUDFLARE_EMAIL}
- CLOUDFLARE_API_KEY=${CLOUDFLARE_API_KEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/traefik.yml:ro
- ./acme.json:/acme.json
- ./config.yml:/config.yml:ro
networks:
- traefik-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.middlewares=auth@file"
- "traefik.http.services.traefik.loadbalancer.server.port=8080"
networks:
traefik-network:
driver: bridge
Before running this, create the acme.json file and set correct permissions (Let's Encrypt certificate storage):
touch acme.json
chmod 600 acme.json
Main Traefik Configuration File
Create traefik.yml in the same directory. This file defines entry points, certificate resolvers, and core behavior:
global:
checkNewVersion: true
sendAnonymousUsage: false
entryPoints:
web:
address: ":80"
http:
redirections:
entrypoint:
regex: "^http://(.*)"
replacement: "https://${1}"
permanent: true
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
domains:
- main: yourdomain.com
sans:
- "*.yourdomain.com"
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: acme.json
httpChallenge:
entryPoint: web
# Uncomment for Cloudflare DNS challenge (better for wildcards)
# dnsChallenge:
# provider: cloudflare
# resolvers:
# - "1.1.1.1:53"
# - "8.8.8.8:53"
api:
dashboard: true
insecure: false
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik-network
file:
filename: /config.yml
watch: true
log:
level: INFO
filePath: /var/log/traefik/traefik.log
accessLog:
filePath: /var/log/traefik/access.log
Basic HTTP Authentication for Dashboard
The dashboard is exposed on port 8080, but we should protect it. Create config.yml with basic auth middleware:
http:
middlewares:
auth:
basicAuth:
users:
- "admin:$apr1$abc123def456$xxxxxxxxxxxx"
routers:
http-catchall:
rule: "HostRegexp(`{host:.+}`)"
entryPoints:
- web
middlewares:
- redirect-to-https
service: noop
services:
noop:
loadBalancer:
servers:
- url: "http://localhost"
middlewares:
redirect-to-https:
redirectScheme:
scheme: https
permanent: true
Generate the basicAuth hash using htpasswd (install with apt-get install apache2-utils):
htpasswd -c /dev/stdout admin | cut -d: -f2
# Enter password when prompted, copy the hash to config.yml
Deploying Applications Behind Traefik
Now the magic happens. When you deploy any Docker container, just add labels and Traefik automatically routes traffic to it. Here's a real example—deploying Nextcloud behind Traefik:
version: '3.8'
services:
nextcloud-db:
image: postgres:15
container_name: nextcloud-db
environment:
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=secure_password_here
volumes:
- nextcloud-db:/var/lib/postgresql/data
networks:
- traefik-network
restart: always
nextcloud:
image: nextcloud:latest
container_name: nextcloud
depends_on:
- nextcloud-db
environment:
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=secure_password_here
- POSTGRES_HOST=nextcloud-db
- TRUSTED_PROXIES=traefik
- OVERWRITEPROTOCOL=https
- OVERWRITEHOST=nextcloud.yourdomain.com
volumes:
- nextcloud-data:/var/www/html
networks:
- traefik-network
restart: always
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.rule=Host(`nextcloud.yourdomain.com`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
- "traefik.http.middlewares.nextcloud-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.nextcloud.middlewares=nextcloud-headers@docker"
volumes:
nextcloud-db:
nextcloud-data:
networks:
traefik-network:
external: true
Notice the key labels: traefik.enable=true tells Traefik to manage this container, the rule defines the hostname, and the entrypoints/tls/certresolver settings handle HTTPS automatically. When you deploy this, Traefik instantly provisions an SSL certificate for nextcloud.yourdomain.com and routes all traffic there.
Advanced Features I Use Daily
Path-Based Routing
Not every app needs its own domain. I route multiple applications through subpaths instead. For example, serving Vaultwarden at yourdomain.com/vault:
labels:
- "traefik.http.routers.vaultwarden.rule=Host(`yourdomain.com`) && Path(`/vault`)"
- "traefik.http.routers.vaultwarden.middlewares=strip-vault@docker"
- "traefik.http.middlewares.strip-vault.stripprefix.prefixes=/vault"
Rate Limiting
Protect services from brute force attacks using Traefik rate limiting middleware:
labels:
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"
- "traefik.http.routers.myapp.middlewares=ratelimit@docker"
Monitoring with Metrics
Traefik exposes Prometheus metrics on the dashboard. I scrape these with my monitoring stack to track request counts, latency, and error rates across all proxied services.
metrics: prometheus: addEntryPointsLabels: true addServicesLabels: trueSecurity Best Practices I Implement
Never expose the Docker socket directly without sandboxing. I run Traefik in a dedicated container with minimal privileges. Always use strong basicAuth passwords for the dashboard—use a password manager to generate complex credentials. Enable HTTP/2 and TLS 1.3 by default (Traefik does this automatically with modern Let's Encrypt certs).
For sensitive applications, I add an extra authentication layer using Authelia middleware, which provides SSO and TOTP support across all proxied services.
Troubleshooting Common Issues
If certificates aren't provisioning, check the logs: docker logs traefik. Most commonly, DNS isn't resolving your domain correctly, or ports 80/443 aren't accessible from the internet. Verify with: curl -I http://yourdomain.com
If Traefik can't reach your backend service, confirm the container is on the same Docker network (traefik-network in our setup) and the service port is correct in the labels.
Rate limiting issues? Traefik counts requests per IP, so if you're behind a CDN like Cloudflare, configure X-Forwarded-For headers properly.
Next Steps
Start with the basic setup above and add applications one by one using the Nextcloud example as a template. Once you're comfortable, explore Traefik's middleware ecosystem—compression, circuit breakers, custom headers, and more are available through simple labels. Consider setting up monitoring alerts for certificate renewal failures, and test your wildcard certificate setup before relying on it for production services.
If you're running this on a budget, I recommend RackNerd's KVM VPS plans, which offer excellent performance for homelab and self-hosted workloads at a fraction of mainstream provider costs.