Traefik v3 on a VPS: Automatic SSL, Docker Service Discovery, and Middleware Configuration

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.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!