Skip to Content

Traefik Reverse Proxy Guide: Route HTTPS Traffic to Docker Services Without Losing Your Mind

Set up Traefik with Docker Compose, auto-SSL via Let's Encrypt, and intelligent routing—so your services just work.

Nginx is great until you have to write your 47th server block. Traefik flips the script: you add a few Docker labels, and it discovers, routes, and secures your services automatically. This Traefik reverse proxy guide shows you how to go from zero to production-grade HTTPS routing in under 30 minutes—no manual certificate management, no config file archaeology.

Want deeper dives? Check our related guides: Traefik Reverse Proxy Guide: Route HTTPS Traffic to Docker Services Automatically and The Complete Traefik Reverse Proxy Guide: HTTPS, Docker, and Let's Encrypt in Production. For a real-world deployment example, see Deploy Actual Budget with Docker Compose and Traefik.

What You'll Build

By the end of this guide, you'll have:

  • Traefik running as a Docker container with automatic service discovery
  • Let's Encrypt SSL certificates provisioned and renewed automatically
  • HTTP-to-HTTPS redirection enforced globally
  • Multiple backend services routed by hostname and path
  • Basic middleware (compression, rate limiting, security headers) applied
  • A working setup you can drop new services into without touching Traefik's config

Prerequisites

Before we start, make sure you have:

  • Docker 24.0+ and Docker Compose v2 installed
  • A Linux server (Ubuntu 22.04/24.04 LTS recommended) with ports 80 and 443 open
  • A domain name with DNS A records pointing to your server (e.g., *.yourdomain.com or individual subdomains)
  • At least 1GB RAM and 1 CPU core (Traefik is lightweight; your apps are the hungry ones)
  • curl and jq installed for testing endpoints

Firewall note: Traefik needs ports 80 (HTTP challenge + redirect) and 443 (HTTPS) accessible. If you're using UFW:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload

Step 1: Understand Traefik's Architecture

Traefik has three moving parts. You need to know them before the configs make sense:

  • EntryPoints: The ports Traefik listens on. Typically web (port 80) and websecure (port 443).
  • Routers: Match incoming requests (by Host, Path, headers) and decide which service handles them.
  • Services: The actual backend—usually a Docker container, but also file, Kubernetes, or external URL.
  • Middlewares: Modify requests/responses—compression, auth, rate limits, redirects, headers.

The magic: Traefik watches Docker events. When you spin up a container with labels, Traefik creates routers and services dynamically. No reloads. No restarts. It just works.

Step 2: Create the Traefik Base Configuration

Traefik uses two config layers: static (startup config, rarely changes) and dynamic (routing rules, changes constantly). We'll use a file for static config and Docker labels for dynamic.

2.1 Static Configuration File

Create traefik.yml in your project directory:

global:
  checkNewVersion: false
  sendAnonymousUsage: false

api:
  dashboard: true
  insecure: false  # We'll secure this later

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true

  websecure:
    address: ":443"

providers:
  docker:
    exposedByDefault: false
    network: proxy

certificatesResolvers:
  letsencrypt:
    acme:
      email: [email protected]
      storage: /letsencrypt/acme.json
      tlsChallenge: {}

What's happening:

  • entryPoints.web handles HTTP and redirects everything to HTTPS
  • entryPoints.websecure handles HTTPS with auto-managed certificates
  • providers.docker.exposedByDefault: false means containers need explicit traefik.enable=true labels
  • tlsChallenge uses the TLS-ALPN-01 challenge (works on port 443, no need for HTTP-01)

2.2 Docker Compose for Traefik

Create docker-compose.yml:

version: "3.8"

networks:
  proxy:
    external: false

services:
  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - proxy
    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.services.traefik.loadbalancer.server.port=8080"

Create the Let's Encrypt storage file with strict permissions:

mkdir -p letsencrypt
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json

Launch Traefik:

docker compose up -d

Verify it's running:

curl -s http://localhost:80 | head -n 5
curl -s -o /dev/null -w "%{http_code}" https://traefik.yourdomain.com --insecure

You should get a 301 redirect from HTTP and a 200 (or 401) from HTTPS. The dashboard at https://traefik.yourdomain.com should show a valid Let's Encrypt certificate.

Step 3: Route Your First Service

Here's where Traefik shines. Add a new service to the same docker-compose.yml—no changes to Traefik needed.

3.1 Add a Whoami Test Service

  whoami:
    image: traefik/whoami
    container_name: whoami
    restart: unless-stopped
    networks:
      - 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"
      - "traefik.http.services.whoami.loadbalancer.server.port=80"

Deploy and test:

docker compose up -d whoami
curl -s https://whoami.yourdomain.com

You should see JSON with your request headers, hostname, and IP. Traefik automatically created the router, service, and requested an SSL certificate.

3.2 Path-Based Routing

Host-based routing is clean, but sometimes you need paths. Add an API service:

  api:
    image: traefik/whoami
    container_name: api
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`app.yourdomain.com`) && PathPrefix(`/api`)"
      - "traefik.http.routers.api.entrypoints=websecure"
      - "traefik.http.routers.api.tls.certresolver=letsencrypt"
      - "traefik.http.services.api.loadbalancer.server.port=80"
      # Strip the /api prefix before forwarding
      - "traefik.http.middlewares.api-stripprefix.stripprefix.prefixes=/api"
      - "traefik.http.routers.api.middlewares=api-stripprefix"

Now https://app.yourdomain.com/api routes to the API container, with the /api prefix stripped before it hits the backend.

Step 4: Apply Middleware for Production Hardness

Routing is step one. Step two is not getting pwned. Traefik middlewares handle this elegantly.

4.1 Security Headers

Add a reusable security headers middleware to traefik.yml:

http:
  middlewares:
    security-headers:
      headers:
        frameDeny: true
        contentTypeNosniff: true
        browserXssFilter: true
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        stsPreload: true
        customFrameOptionsValue: "SAMEORIGIN"

Apply it to any service:

      - "traefik.http.routers.whoami.middlewares=security-headers@file"

4.2 Rate Limiting

Protect against brute force and scraping:

http:
  middlewares:
    rate-limit:
      rateLimit:
        average: 100
        burst: 50

4.3 Compression

Enable gzip for text responses:

http:
  middlewares:
    compress:
      compress: {}

Chain multiple middlewares on a router:

      - "traefik.http.routers.whoami.middlewares=security-headers@file,rate-limit@file,compress@file"

Step 5: Secure the Dashboard

The Traefik dashboard is powerful—and dangerous if exposed. Lock it down with basic auth.

5.1 Generate a Password Hash

# Install htpasswd if you don't have it
sudo apt-get install -y apache2-utils

# Generate bcrypt hash (Traefik prefers bcrypt)
htpasswd -nbB admin "your_secure_password" | cut -d ":" -f 2

5.2 Add Basic Auth Middleware

Add to traefik.yml:

http:
  middlewares:
    dashboard-auth:
      basicAuth:
        users:
          - "admin:$2y$10$...your_bcrypt_hash_here..."

Update the Traefik router labels in docker-compose.yml:

      - "traefik.http.routers.traefik.middlewares=dashboard-auth@file,security-headers@file"

Restart Traefik:

docker compose restart traefik

Now https://traefik.yourdomain.com prompts for credentials. The dashboard shows your routers, services, middlewares, and certificate status in real time.

Step 6: Tips, Troubleshooting, and Production Gotchas

6.1 Certificate Not Issuing

If Let's Encrypt fails, check these in order:

  • Port 443 must be open to the internet. TLS-ALPN-01 challenge happens on 443.
  • DNS must resolve to your server before Traefik starts. Let's Encrypt validates the domain.
  • acme.json permissions must be 600. Traefik refuses to write otherwise.
  • Rate limits: Let's Encrypt allows 50 certificates per domain per week. Don't hammer it.

Debug with Traefik's logs:

docker compose logs -f traefik | grep -i "certificate\|acme\|error"

6.2 Service Not Appearing in Dashboard

Common causes:

  • Container is not on the proxy network. All services and Traefik must share the same Docker network.
  • Missing traefik.enable=true label. With exposedByDefault: false, this is mandatory.
  • Router rule typo. Host(`whoami.yourdomain.com`) needs backticks, not quotes.

6.3 502 Bad Gateway

Traefik can't reach the backend. Check:

  • Is the backend container healthy? docker compose ps
  • Is the port correct? loadbalancer.server.port must match what the container exposes.
  • Is the backend listening on 0.0.0.0 or just 127.0.0.1? Docker containers need to bind to all interfaces.

6.4 Performance Tuning

For high-traffic setups:

  • Enable --serversTransport.insecureSkipVerify=false only if you trust your internal network
  • Use --providers.docker.watch for faster container event polling
  • Consider a redis or consul provider for multi-node Traefik clusters
  • Enable access logs and ship them to your SIEM for security monitoring

6.5 Backup Your ACME State

Your certificates live in letsencrypt/acme.json. Back it up:

cp letsencrypt/acme.json letsencrypt/acme.json.backup.$(date +%F)

Losing this file means re-requesting all certificates. Let's Encrypt rate limits make this painful.

Wrapping Up

You now have a fully automated reverse proxy that discovers services, routes traffic by hostname and path, terminates SSL with free Let's Encrypt certificates, and applies security middleware—all without writing a single Nginx config file.

Traefik's label-based configuration means adding a new service is literally just adding labels to your Docker Compose. No restarts. No reloads. No certificate management. It scales from a single VPS to a multi-node Kubernetes cluster with the same mental model.

That said, production edge cases—multi-node clustering, custom certificate authorities, TCP routing, canary deployments, and observability integration—deserve careful planning.

Need help architecting a production Traefik setup, migrating from Nginx, or integrating advanced middleware for your specific stack? Talk to our team—we've deployed Traefik at scale for SaaS platforms, fintech infrastructure, and healthcare compliance environments. We'll get your routing right.

Keycloak Docker Setup Guide: From Zero to Self-Hosted SSO in 30 Minutes
Deploy a production-ready Keycloak identity server with Docker Compose, PostgreSQL, and Traefik—no auth PhD required.