$linuxjunkies
>

How to Install Traefik as a Reverse Proxy

Install Traefik v3 as a Docker-based reverse proxy with automatic Let's Encrypt TLS, static and dynamic config, and label-driven service routing.

IntermediateUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

Before you start

  • Docker Engine 24+ and Docker Compose v2 installed
  • A domain name with DNS A records pointing to your server
  • Ports 80 and 443 open in your firewall
  • Root or sudo access on the host

Traefik is a cloud-native reverse proxy and load balancer that integrates directly with Docker, Kubernetes, and other orchestrators to discover services automatically. Unlike Nginx or Caddy configs you hand-write for every service, Traefik reads labels on your containers and updates its routing table on the fly. This guide walks through a production-ready Traefik v3 setup: static config, dynamic routing, Let's Encrypt TLS via ACME, and exposing Docker-based services.

Architecture Overview

Traefik separates configuration into two layers:

  • Static config – entrypoints, providers, certificate resolvers. Set once at startup via traefik.yml or CLI flags.
  • Dynamic config – routers, services, middlewares. Updated at runtime from providers (Docker labels, file provider, etc.).

All examples below run Traefik itself as a Docker container managed by docker compose. The host runs a modern systemd-based distro with Docker Engine installed.

Prerequisites

  • Docker Engine 24+ and Docker Compose v2 (docker compose, not docker-compose).
  • A domain name with DNS A records pointing to your server's public IP.
  • Ports 80 and 443 open in your firewall.

Open firewall ports

Pick the tool matching your distro.

ufw (Ubuntu/Debian):

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

firewalld (Fedora/RHEL/Rocky):

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

nftables (Arch or manual setup):

sudo nft add rule inet filter input tcp dport { 80, 443 } accept

Step 1 – Create the Project Directory

Keep all Traefik config in one place:

mkdir -p ~/traefik/{config,certs}
cd ~/traefik

The certs/ directory will hold the ACME JSON file. It must be locked down:

touch certs/acme.json
chmod 600 certs/acme.json

Step 2 – Write the Static Configuration

Create config/traefik.yml. This file is read once at container startup.

cat > config/traefik.yml << 'EOF'
api:
  dashboard: true
  insecure: false

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

certificatesResolvers:
  letsencrypt:
    acme:
      email: [email protected]
      storage: /certs/acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    exposedByDefault: false
    network: traefik_proxy
  file:
    directory: /config/dynamic
    watch: true

log:
  level: INFO
EOF

Key decisions here:

  • exposedByDefault: false means containers are ignored until you add a label — safer for production.
  • httpChallenge works for most setups. Switch to dnsChallenge if your server isn't publicly reachable on port 80 (see Troubleshooting).
  • The file provider watches /config/dynamic/ for hot-reloaded dynamic config files.

Step 3 – Create the Docker Network

All proxied containers must share a network with Traefik:

docker network create traefik_proxy

Step 4 – Write the Docker Compose File

cat > docker-compose.yml << 'EOF'
services:
  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./config/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./config/dynamic:/config/dynamic:ro
      - ./certs:/certs
    networks:
      - traefik_proxy
    labels:
      - "traefik.enable=true"
      - "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"
      - "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$xyz$$hashedpasswordhere"

networks:
  traefik_proxy:
    external: true
EOF

Replace traefik.example.com with your actual subdomain. Generate a bcrypt/apr1 password hash for the dashboard basic auth:

# Install htpasswd if needed:
# Ubuntu/Debian: sudo apt install apache2-utils
# Fedora/RHEL:   sudo dnf install httpd-tools
# Arch:          sudo pacman -S apache
htpasswd -nb admin yourpassword

Copy the output (escape any $ signs as $$ in the Compose label).

Step 5 – Add Dynamic Configuration

Create the dynamic directory and an optional middleware snippet for extra security headers:

mkdir -p config/dynamic
cat > config/dynamic/middlewares.yml << 'EOF'
http:
  middlewares:
    secure-headers:
      headers:
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        contentTypeNosniff: true
        browserXssFilter: true
        referrerPolicy: "strict-origin-when-cross-origin"
EOF

Traefik picks this up live — no restart needed.

Step 6 – Start Traefik

docker compose up -d
docker compose logs -f traefik

Watch the logs. You should see Traefik start, contact Let's Encrypt, and complete the HTTP-01 challenge. Logs will include lines like Obtaining ACME certificate and Certificate obtained successfully.

Step 7 – Route a Docker Service

Add labels to any container to expose it through Traefik. Here's a minimal example using Whoami as a test service:

cat > whoami-compose.yml << 'EOF'
services:
  whoami:
    image: traefik/whoami
    container_name: whoami
    restart: unless-stopped
    networks:
      - traefik_proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
      - "traefik.http.middlewares.whoami.headers.customrequestheaders.X-Forwarded-Proto=https"

networks:
  traefik_proxy:
    external: true
EOF
docker compose -f whoami-compose.yml up -d

Traefik discovers the container immediately and begins serving https://whoami.example.com with a fresh TLS certificate.

Verification

Check the Traefik dashboard at https://traefik.example.com — you should see the whoami router listed as active. From the command line:

curl -I https://whoami.example.com

Expected: HTTP/2 200 with valid TLS. Also verify the HTTP→HTTPS redirect:

curl -I http://whoami.example.com

Expected: 301 Moved Permanently pointing to the HTTPS URL.

Inspect currently active certificates:

cat certs/acme.json | python3 -m json.tool | grep '"domain"' 

Troubleshooting

ACME certificate never issues

  • Confirm port 80 is reachable from the internet: curl http://yourdomain.com from an outside machine.
  • Check acme.json permissions are exactly 600; Traefik refuses to write certificates otherwise.
  • If the server is behind NAT or a CDN, switch to dnsChallenge in traefik.yml and configure your DNS provider's API credentials as environment variables.

Container not appearing in Traefik

  • Confirm the container is on the traefik_proxy network: docker inspect <container> | grep -A5 Networks.
  • Ensure traefik.enable=true label is set — remember exposedByDefault: false.
  • Verify the network name in traefik.yml matches exactly: traefik_proxy.

Dashboard returns 404

The service=api@internal label is required for the dashboard router. Without it Traefik tries to find a container service named dashboard and fails.

Stale certificates after domain change

Delete and recreate acme.json, then restart Traefik. Keep a backup first — losing the file mid-renewal causes a short outage.

tested on:Ubuntu 24.04Debian 12Fedora 40Arch rolling

Frequently asked questions

Can I use Traefik without Docker — with plain systemd services?
Yes. Use the file provider exclusively: define routers and services in YAML files under config/dynamic pointing to localhost ports. Install the Traefik binary directly and run it as a systemd service using an ExecStart pointing to your traefik.yml.
How do I use a wildcard certificate instead of per-domain certificates?
Switch from httpChallenge to dnsChallenge in the ACME config and set the router rule to HostRegexp or use a wildcard Host(`*.example.com`). Wildcard certs require DNS-01 challenge; configure your DNS provider's API token as an environment variable per Traefik's provider docs.
Is mounting the Docker socket a security risk?
Yes — access to the Docker socket is effectively root on the host. Mitigate it by mounting the socket read-only (:ro), setting exposedByDefault: false, and running Traefik behind a dedicated user namespace. For stricter setups consider using the Traefik Proxy provider via TCP socket proxy (docker-socket-proxy).
How do I add path-based routing instead of host-based?
Change the router rule label to use PathPrefix: traefik.http.routers.myapp.rule=Host(`example.com`) && PathPrefix(`/app`). Add a StripPrefix middleware if your backend doesn't expect the path prefix.
What happens when Let's Encrypt rate-limits me during testing?
Use Let's Encrypt's staging server by adding caServer: https://acme-staging-v02.api.letsencrypt.org/directory to the acme block in traefik.yml. Staging certificates are invalid for browsers but let you test the full ACME flow without hitting production rate limits.

Related guides