Traefik v3 on a VPS: Automatic SSL, Docker Service Discovery, and Middleware Configuration
Traefik v3 is a cloud-native reverse proxy that automatically discovers Docker containers via labels and configures routing and SSL certificates without manual Nginx config files. For Docker-heavy VPS deployments, Traefik eliminates the cycle of “deploy container → write Nginx config → run Certbot” — add labels to your Docker Compose services and Traefik routes traffic and provisions Let’s Encrypt SSL automatically.
Traefik vs Nginx: When to Choose Each
- Choose Traefik: Primarily Docker-based deployments, frequently adding/removing services, want zero-touch SSL provisioning, need service discovery
- Choose Nginx: Mix of Docker and non-Docker services, need advanced caching (microcache), heavy SSL configuration customization, team already knows Nginx
Step 1: Traefik Docker Compose Setup
<code">mkdir -p /opt/traefik && cd /opt/traefik # Create shared Docker network for all services Traefik routes docker network create traefik-public mkdir -p letsencrypt touch letsencrypt/acme.json chmod 600 letsencrypt/acme.json # Traefik requires exactly 600 nano docker-compose.yml
<code">version: '3.8'
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: always
command:
# API and dashboard
- "--api.dashboard=true"
- "--api.insecure=false"
# Docker provider — auto-discover services
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false" # Must opt-in with label
- "--providers.docker.network=traefik-public"
# Entrypoints
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# HTTP → HTTPS redirect
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
# Let's Encrypt (ACME) — TLS challenge
- "--certificatesresolvers.letsencrypt.acme.email=admin@yourdomain.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
# Logging
- "--log.level=INFO"
- "--accesslog=true"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro # Read Docker socket
- ./letsencrypt:/letsencrypt
networks:
- traefik-public
labels:
# Expose Traefik dashboard
- "traefik.enable=true"
- "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"
# Protect dashboard with basic auth
- "traefik.http.routers.dashboard.middlewares=dashboard-auth"
# Generate: htpasswd -nb admin password | sed -e s/\\$/\\$\\$/g
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$HASH"
networks:
traefik-public:
external: true
<code">docker compose up -d docker compose logs -f traefik
Step 2: Add a Service with Traefik Labels
<code"># Any Docker Compose service — add these labels:
services:
myapp:
image: myapp:latest
networks:
- traefik-public # Must be on the same network as Traefik
labels:
- "traefik.enable=true"
# Routing rule
- "traefik.http.routers.myapp.rule=Host(`app.yourdomain.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
# SSL certificate
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
# Port (needed if container exposes multiple ports)
- "traefik.http.services.myapp.loadbalancer.server.port=3000"
networks:
traefik-public:
external: true
Step 3: Middleware Configuration
Rate Limiting
<code"># In docker-compose.yml labels: - "traefik.http.middlewares.api-ratelimit.ratelimit.average=100" # 100 req/second - "traefik.http.middlewares.api-ratelimit.ratelimit.burst=50" # Apply to a router: - "traefik.http.routers.myapi.middlewares=api-ratelimit"
Basic Authentication
<code"># Generate credential: htpasswd -nb user password | sed -e s/\\$/\\$\\$/g - "traefik.http.middlewares.my-auth.basicauth.users=admin:$$apr1$$HASH" - "traefik.http.routers.myadmin.middlewares=my-auth"
IP Whitelist
<code"># Allow only specific IPs - "traefik.http.middlewares.office-only.ipallowlist.sourcerange=203.0.113.0/24,10.0.0.0/8" - "traefik.http.routers.internal.middlewares=office-only"
Security Headers
<code"># Add security headers to responses - "traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000" - "traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true" - "traefik.http.middlewares.secure-headers.headers.frameDeny=true" - "traefik.http.middlewares.secure-headers.headers.contentTypeNosniff=true" - "traefik.http.middlewares.secure-headers.headers.browserXssFilter=true" - "traefik.http.middlewares.secure-headers.headers.referrerPolicy=strict-origin-when-cross-origin"
Path Stripping
<code"># Route /api/* to a service, strip the /api prefix - "traefik.http.routers.api.rule=Host(`yourdomain.com`) && PathPrefix(`/api`)" - "traefik.http.middlewares.strip-api.stripprefix.prefixes=/api" - "traefik.http.routers.api.middlewares=strip-api"
Step 4: Static File Configuration (Alternative to Labels)
<code">nano /opt/traefik/traefik.yml
<code">api:
dashboard: true
providers:
docker:
exposedByDefault: false
network: traefik-public
file:
filename: /etc/traefik/dynamic.yml # For non-Docker services
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: admin@yourdomain.com
storage: /letsencrypt/acme.json
tlsChallenge: {}
log:
level: INFO
<code">nano /opt/traefik/dynamic.yml
<code"># For non-Docker services (e.g., a VM or bare-metal host)
http:
routers:
legacy-service:
rule: "Host(`legacy.yourdomain.com`)"
service: legacy-backend
tls:
certResolver: letsencrypt
services:
legacy-backend:
loadBalancer:
servers:
- url: "http://192.168.1.50:8080"
Monitoring Traefik
<code"># View active routers and services curl http://localhost:8080/api/http/routers | python3 -m json.tool curl http://localhost:8080/api/http/services | python3 -m json.tool # Check certificate status curl http://localhost:8080/api/overview | python3 -m json.tool # Prometheus metrics endpoint (if enabled) curl http://localhost:8080/metrics
Getting Started
Traefik uses 30–80 MB RAM — it runs alongside any services on a KVM VPS at VPS.DO. The zero-configuration SSL provisioning is Traefik’s killer feature for Docker deployments — adding a new service with a domain takes adding four labels to a Docker Compose file, with no Certbot commands, no Nginx configuration, and no service reload. Traefik detects the new container and provisions the certificate automatically.
Conclusion
Traefik v3 replaces the Nginx-config-per-service workflow for Docker-heavy VPS deployments with label-based automatic routing and SSL provisioning. Middleware (rate limiting, basic auth, IP whitelist, security headers) applies via labels without separate config files. For teams deploying 5+ Docker services on a VPS, Traefik’s automation saves significant operational time and eliminates the common mistake of forgetting to configure SSL for a new service.