Running multiple Docker containers on a single VPS raises an immediate architectural question: how do you efficiently route incoming traffic to the correct container based on hostname or path rules? Traefik is a modern, cloud-native reverse proxy engineered specifically for container environments. It automatically discovers and configures routing from Docker labels alone, provisions SSL certificates from Let's Encrypt without external tools, and manages middleware security policies — all without requiring a single config file reload when you add a new service.
Why Choose Traefik Over Traditional Nginx?
Nginx is a battle-tested workhorse and excellent choice for static reverse proxy setups. However, managing multiple containers introduces operational overhead with Nginx. Each time you add a new service, you must manually edit the Nginx config, syntax-check it, and reload the process. This manual workflow doesn't scale well in containerized environments where services are frequently added, updated, or removed.
Traefik solves this by embracing container orchestration natively. It reads configuration from Docker labels on running containers, detects changes in real-time, and applies routing rules instantly without restarts. This philosophy makes Traefik the better choice for:
- Auto-discovery — New containers are recognized and routed immediately via Docker labels, no config edits
- Built-in SSL automation — Traefik handles Let's Encrypt certificate requests, renewals, and storage automatically
- Middleware as code — Rate limiting, basic auth, IP whitelisting, and request routing are all defined as Docker labels
- Real-time dashboard — Visual representation of all routes, services, and certificate status
- Cloud-native scaling — Works seamlessly with Docker Swarm, Kubernetes, and other orchestrators
What You'll Need Before Starting
- VPS with Ubuntu 20.04+ running Docker Engine 20.x and Docker Compose v2+
- A registered domain with DNS records pointing to your VPS IP address (e.g.,
example.com → 1.2.3.4and*.example.com → 1.2.3.4for wildcard routing) - Ports 80 (HTTP) and 443 (HTTPS) open in your server's firewall and security group
- An email address for Let's Encrypt certificate notifications (used in ACME config)
Folder Structure and Organization
Organize your Traefik setup in a dedicated directory for clarity and future maintenance:
~/traefik/
├── docker-compose.yml # Docker Compose service definitions
├── traefik.yml # Traefik static configuration
├── dynamic.yml # Optional: dynamic config (routes, middleware)
└── acme.json # Let's Encrypt certificate storage (auto-created)
Step 1: Create a Shared Docker Network
Traefik and all containers it routes must be on the same Docker network so they can communicate. Create a custom bridge network (external to any single compose file) to allow multiple compose stacks to share Traefik:
docker network create traefik-net
Step 2: Configure Static Settings (traefik.yml)
Static configuration in Traefik defines entry points, certificate providers, and service discovery. Create the configuration file in your working directory:
mkdir -p ~/traefik && cd ~/traefik
cat > traefik.yml << 'EOF'
# Traefik v3 Static Configuration
api:
dashboard: true
insecure: false # Disable insecure API (port 8080)
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
domains:
- main: example.com
sans:
- "*.example.com"
certificatesResolvers:
letsencrypt:
acme:
email: [email protected] # ← Change this to your email
storage: /acme.json
httpChallenge:
entryPoint: web
keyType: RSA4096
providers:
docker:
exposedByDefault: false # Only expose containers with label traefik.enable=true
network: traefik-net
file:
filename: /dynamic.yml # Optional: load dynamic routes from file
watch: true
log:
level: INFO
accessLog: {} # Enable access logs for debugging
EOF
Step 3: Create docker-compose.yml for Traefik
Define the Traefik service with volume mounts for config files and certificate persistence:
cat > docker-compose.yml << 'EOF'
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
- ./traefik.yml:/traefik.yml:ro
- ./dynamic.yml:/dynamic.yml:ro
- ./acme.json:/acme.json
networks:
- traefik-net
labels:
# Enable Traefik's own routing
- "traefik.enable=true"
# Dashboard route
- "traefik.http.routers.dashboard.rule=Host(\`traefik.example.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=auth"
# Basic authentication for dashboard
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$xyz$$hashedpassword"
networks:
traefik-net:
external: true
EOF
Generate a Secure Dashboard Password
Protect your dashboard with Basic Authentication. First install Apache utilities and generate a password hash:
sudo apt install apache2-utils -y
# Generate hash (replace 'yourpassword' with your actual password)
htpasswd -nb admin yourpassword
# Output example: admin:$apr1$r8bIGvVz$l0s75fN2pVe4VvKeIqZF1/
# Important: In docker-compose.yml, escape each $ with another $
# So $apr1$ becomes $$apr1$$, and $l0s... becomes $$l0s...
# Final label: traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$r8bIGvVz$$l0s75fN2pVe4VvKeIqZF1/
Step 4: Initialize Certificate Storage
Let's Encrypt requires a persistent file to store issued certificates. Create it with restricted permissions (Traefik refuses to use acme.json with loose permissions):
touch acme.json
chmod 600 acme.json
Step 5: Start Traefik and Verify
docker compose up -d
docker logs traefik -f # Follow logs to watch Traefik start
Watch the logs for these key messages:
Traefik version v3.0.x starting— Service is runningmsg="Obtaining certificate"— Traefik is requesting SSL from Let's Encrypt (takes 30–60 seconds)msg="Certificate added to acme.json"— Certificate was successfully obtained
Troubleshooting SSL Delays: First-time SSL provisioning can take 1–2 minutes while Let's Encrypt validates your domain. If the process stalls, check that port 80 is truly open to the internet with curl -I http://your-domain.com from an external network. DNS propagation issues or port forwarding misconfiguration are the most common causes.
Routing Your First Application
Once Traefik is running, deploying a new containerized application is simple. Traefik detects Docker labels and applies routing rules instantly. Here's a real-world example with WordPress:
version: "3.8"
services:
wordpress:
image: wordpress:latest
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: secretpassword
networks:
- traefik-net
labels:
# Tell Traefik to route this container
- "traefik.enable=true"
# Route HTTP requests to blog.example.com to this container
- "traefik.http.routers.wp.rule=Host(\`blog.example.com\`)"
# Use HTTPS only
- "traefik.http.routers.wp.entrypoints=websecure"
# Use the Let's Encrypt resolver for SSL
- "traefik.http.routers.wp.tls.certresolver=letsencrypt"
# Define where to find the backend service
- "traefik.http.services.wp.loadbalancer.server.port=80"
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: secretpassword
volumes:
- db_data:/var/lib/mysql
networks:
- traefik-net
volumes:
db_data:
networks:
traefik-net:
external: true
Deploy with docker compose up -d. Traefik automatically detects the WordPress container within seconds and begins routing traffic to blog.example.com with a valid SSL certificate — no Traefik restart required.
Essential Middleware for Production
Middleware intercepts requests and applies security or routing transformations. These are invaluable for hardening your services:
Rate Limiting to Prevent Abuse
Protect APIs and login pages from brute-force attacks:
labels:
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"
- "traefik.http.middlewares.ratelimit.ratelimit.period=1m"
- "traefik.http.routers.myapp.middlewares=ratelimit"
IP Whitelisting for Admin Panels
Restrict access to management dashboards to trusted IPs only:
labels:
- "traefik.http.middlewares.ipwhitelist.ipallowlist.sourcerange=203.0.113.0/24,10.0.0.0/8"
- "traefik.http.routers.admin.rule=Host(\`admin.example.com\`)"
- "traefik.http.routers.admin.middlewares=ipwhitelist"
Redirect www to Non-www
Enforce canonical URLs by automatically redirecting www variants:
labels:
- "traefik.http.middlewares.wwwredirect.redirectregex.regex=^https?://www\\.(.+)"
- "traefik.http.middlewares.wwwredirect.redirectregex.replacement=https://$${1}"
- "traefik.http.middlewares.wwwredirect.redirectregex.permanent=true"
- "traefik.http.routers.web.middlewares=wwwredirect"
Add Security Headers
Inject critical security headers into all responses:
labels:
- "traefik.http.middlewares.secheaders.headers.accesscontrolalloworiginlist=https://example.com"
- "traefik.http.middlewares.secheaders.headers.accesscontrolmaxage=100"
- "traefik.http.middlewares.secheaders.headers.sslhost=example.com"
- "traefik.http.middlewares.secheaders.headers.sslforcehost=true"
- "traefik.http.middlewares.secheaders.headers.sslredirect=true"
- "traefik.http.middlewares.secheaders.headers.referrerpolicy=no-referrer"
- "traefik.http.routers.myapp.middlewares=secheaders"
Advanced Topics
Load Balancing Across Multiple Container Instances
Run multiple replicas of a service and let Traefik distribute load automatically:
services:
web:
image: myapp:latest
deploy:
replicas: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.web.rule=Host(\`app.example.com\`)"
- "traefik.http.services.web.loadbalancer.server.port=3000"
- "traefik.http.services.web.loadbalancer.sticky=true" # Session affinity
Using Path-Based Routing
Route requests to different services based on URL path instead of hostname:
labels:
- "traefik.http.routers.api.rule=Host(\`example.com\`) && Path(\`/api\`)"
- "traefik.http.services.api.loadbalancer.server.port=8080"
Wildcard Certificate Coverage
For subdomains beyond simple routing, request a wildcard certificate:
# In traefik.yml
certificatesResolvers:
letsencrypt:
acme:
storage: /acme.json
dnsChallenge: # Use DNS challenge for wildcards
provider: cloudflare # Your DNS provider
propagationWaitTime: 5m
propagationTimeout: 24h
Comparing Traefik and Nginx Proxy Manager
| Feature | Traefik v3 | Nginx Proxy Manager |
|---|---|---|
| Auto-discovery | ✅ Docker labels | ❌ Manual setup |
| SSL Auto-renewal | ✅ Built-in | ✅ Built-in |
| Admin UI | Dashboard (read-only) | Full web UI |
| Middleware | ✅ Extensive | Limited |
| Kubernetes | ✅ Native Ingress | ❌ No support |
| Best for | DevOps/Developers | Beginners seeking UI |
Troubleshooting Common Issues
SSL Certificate Not Appearing
Check Traefik logs for ACME errors:
docker logs traefik 2>&1 | grep -i "acme\|certificate\|error"
Common causes and solutions:
- Port 80 blocked: Let's Encrypt's HTTP-01 challenge requires open port 80. Verify with
curl -I http://your-domain.comfrom a different network. - DNS not propagating: Wait a few minutes for DNS to resolve globally. Use
nslookup your-domain.comto verify. - Rate limiting: Let's Encrypt limits certificate requests. If you test frequently, you'll hit the rate limit. Wait 1 hour and retry, or use the staging endpoint for testing.
Container Routes Are Not Working
Verify container configuration:
# Check if container is on the traefik-net network
docker inspect container_name | grep -A5 "Networks"
# Verify labels are present
docker inspect container_name | grep -i traefik
# Check if the service port is correct (e.g., port 80, 3000, 8080)
docker ps | grep container_name
# View Traefik logs for routing errors
docker logs traefik | grep container_name
Dashboard Password Not Working
Ensure the hash is properly escaped in docker-compose.yml. Each $ must become $$ (YAML escaping). Test locally first:
echo 'admin:$apr1$r8bIGvVz$l0s75fN2pVe4VvKeIqZF1/' | sed 's/\$/\$\$/g'
# Output: admin:$$apr1$$r8bIGvVz$$l0s75fN2pVe4VvKeIqZF1/
Ready to Deploy Traefik on a VPS?
AsiaGB provides high-performance VPS hosting with full root access on Linux, perfect for Docker and Traefik deployments. With SSD storage, 99% uptime, and 24/7 support, you can focus on your applications while we handle the infrastructure.
Explore VPS PlansFrequently Asked Questions
traefik:v3.0 to traefik:v3.1), then run docker compose down && docker compose up -d. Traefik will start with the new version while preserving your acme.json certificate storage and all container routes.http://localhost:8080/metrics (if you enable the metrics API in configuration). Scrape this endpoint with Prometheus and visualize in Grafana for real-time performance monitoring, request latency, and backend health.