Skip to content

Setup Traefik

Traefik is a modern HTTP reverse proxy and load balancer designed for microservices and Containerized environments. Key advantages over alternatives:

FeatureTraefikNginx Proxy ManagerCaddy
Auto-discovery from Docker labelsYes (native)No (manual UI)Yes (via plugin)
TLS certificate managementBuilt-in ACMEBuilt-in (UI)Built-in
Configuration methodLabels, file, CLIWeb UICaddyfile
Dynamic routingAutomatic on container startManual per proxy hostAutomatic
Learning curveMediumLowLow
DashboardBuilt-inBuilt-inNo
Docker Compose integrationExcellent (labels on each service)Separate containerGood

Traefik shines in a homelab because it eliminates manual proxy configuration. When you start a new Container with the right labels, Traefik picks it up and routes traffic immediately — no restart, no Config file editing, no UI clicking.

  • TrueNAS SCALE with Apps/Docker support enabled
  • A DDNS domain pointing to your public IP (Cloudflare, DuckDNS, No-IP, etc.)
  • ISP ports 80 and 443 forwarded to your TrueNAS server (or the machine running Traefik)
  • A Docker runtime (TrueNAS SCALE Apps, or Docker Compose via Portainer/Dockge)

Before deploying, understand the core routing model:

  • Routers: Define how requests reach your services (host-based, path-based, etc.)
  • Middlewares: Modify requests before they reach services (authentication, headers, rate limiting, compression)
  • Services: Define the backend (container IP + port)
  • EntryPoints: Listening ports (80 for HTTP, 443 for HTTPS, 8080 for dashboard)
  • Providers: Sources of configuration (Docker labels, file provider, etc.)
  • TLS Stores/Certificates: Managed by ACME (Let”s Encrypt) with automatic renewal

This is the recommended way to run Traefik on TrueNAS SCALE. Store this compose file in a dataset Accessible to your Docker runtime.

Create the directory structure on your TrueNAS:

Terminal window
mkdir -p /mnt/tank/apps/traefik/{config,dynamic,certs}
services:
traefik:
image: traefik:v3.2
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80'
- '443:443'
- '8080:8080'
command:
- '--api.insecure=true'
- '--api.dashboard=true'
- '--providers.docker=true'
- '--providers.docker.exposedbydefault=false'
- '--providers.docker.network=proxy-network'
- '--providers.file.filename=/dynamic-config.yml'
- '--entrypoints.web.address=:80'
- '--entrypoints.websecure.address=:443'
- '--entrypoints.traefik.address=:8080'
- '--certificatesresolvers.letsencrypt.acme.tlschallenge=true'
- '--certificatesresolvers.letsencrypt.acme.email=your-email@example.com'
- '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
- '--log.level=INFO'
- '--accesslog=true'
- '--accesslog.filepath=/var/log/traefik/access.log'
- '--serversTransport.insecureSkipVerify=true'
networks:
- proxy-network
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-certs:/letsencrypt
- ./dynamic:/dynamic-config.yml:ro
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)'
- 'traefik.http.routers.traefik.entrypoints=traefik'
- 'traefik.http.routers.traefik.service=api@internal'
volumes:
traefik-certs:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/tank/apps/traefik/certs
networks:
proxy-network:
external: true
name: proxy-network

The ACME certificate store file requires restricted permissions. After starting Traefik for the First time, set permissions:

Terminal window
chmod 600 /mnt/tank/apps/traefik/certs/acme.json

If Traefik fails to start, check the logs for permission errors on acme.json.

Create the external Docker network before starting Traefik:

Terminal window
docker network create proxy-network

All containers that need to be routed through Traefik must be on this network.

The most common routing method. Each service gets its own subdomain:

services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
networks:
- proxy-network
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.jellyfin.rule=Host(`jellyfin.yourdomain.com`)'
- 'traefik.http.routers.jellyfin.entrypoints=websecure'
- 'traefik.http.routers.jellyfin.tls=true'
- 'traefik.http.routers.jellyfin.tls.certresolver=letsencrypt'
- 'traefik.http.services.jellyfin.loadbalancer.server.port=8096'

Route multiple services under a single domain based on URL path:

labels:
- 'traefik.enable=true'
- 'traefik.http.routers.app.rule=Host(`yourdomain.com`) && PathPrefix(`/app`)'
- 'traefik.http.routers.app.entrypoints=websecure'
- 'traefik.http.routers.app.tls.certresolver=letsencrypt'
- 'traefik.http.middlewares.app-strip.stripprefix.prefixes=/app'
- 'traefik.http.routers.app.middlewares=app-strip'
- 'traefik.http.services.app.loadbalancer.server.port=8080'

Force all HTTP traffic to HTTPS by adding a redirect middleware:

services:
traefik:
# ... (other config)
labels:
- 'traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'
- 'traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true'
- 'traefik.http.routers.http-catchall.rule=HostRegexp(`{any:.+}`)'
- 'traefik.http.routers.http-catchall.entrypoints=web'
- 'traefik.http.routers.http-catchall.middlewares=redirect-to-https'

Or add it in a dynamic configuration file (see below).

For middlewares and routing rules that apply globally or are shared across services, use a File-based dynamic provider.

Create dynamic-config.yml in your Traefik config directory:

http:
middlewares:
redirect-to-https:
redirectScheme:
scheme: https
permanent: true
default-headers:
headers:
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
compression:
compress: {}
rate-limit:
rateLimit:
average: 100
burst: 50
auth-basic:
basicAuth:
users:
- 'admin:$apr1$hashedpassword'
auth-forward:
forwardAuth:
address: "http://authelia:9091/api/verify?rd=https://auth.yourdomain.com/''
trustForwardHeader: true
authResponseHeaders:
- "X-Forwarded-User'
routers:
http-catchall:
rule: "HostRegexp(`{any:.+}`)''
entrypoints:
- web
middlewares:
- redirect-to-https
service: noop@internal

Traefik handles Let’s Encrypt certificate provisioning and renewal automatically. Two challenge Types are commonly used:

Section titled “TLS Challenge (Recommended for most setups)”

The default in the compose file above. Traefik proves domain ownership by responding to an ACME Challenge on port 443. Only port 443 needs to be forwarded.

If your ISP blocks port 443 or you have issues with the TLS challenge, switch to HTTP:

command:
- '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
- '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
# ... rest of config

This requires port 80 to be forwarded.

For wildcard certificates (*.yourdomain.com), you need the DNS challenge. Example with Cloudflare:

command:
- '--certificatesresolvers.letsencrypt.acme.dnschallenge=true'
- '--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare'
- '--certificatesresolvers.letsencrypt.acme.email=your-email@example.com'
- '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
environment:
- 'CF_DNS_API_TOKEN=your-cloudflare-api-token'

Then on your routers, use:

- 'traefik.http.routers.myapp.tls.certresolver=letsencrypt'
- 'traefik.http.routers.myapp.tls.domains[0].main=yourdomain.com'
- 'traefik.http.routers.myapp.tls.domains[0].sans=*.yourdomain.com'

Traefik automatically renews certificates before expiry (default: 30 days before). No manual Intervention needed. Check certificate status:

Terminal window
# View stored certificates
curl -s http://localhost:8080/api/http/routers | jq '.[].tls'

Generate a hashed password:

Terminal window
# Using htpasswd
apt install apache2-utils
htpasswd -nb admin yourpassword
# Using Docker
docker run --rm httpd:alpine htpasswd -nb admin yourpassword

Apply to a service:

labels:
- 'traefik.http.middlewares.my-auth.basicauth.users=admin:$apr1$hashhere'
- 'traefik.http.routers.myapp.middlewares=my-auth'
Section titled “Authentication with Authelia (Recommended)”

For SSO across all your services, use Authelia as a forward auth middleware:

labels:
- 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.yourdomain.com'
- 'traefik.http.middlewares.authelia.forwardauth.trustforwardheader=true'
- 'traefik.http.routers.myapp.middlewares=authelia'

This redirects unauthenticated users to an Authelia login page. After login, users are authenticated Across all services without re-entering credentials.

Protect services from abuse:

labels:
- 'traefik.http.middlewares.my-ratelimit.ratelimit.average=100'
- 'traefik.http.middlewares.my-ratelimit.ratelimit.burst=50'
- 'traefik.http.routers.myapp.middlewares=my-ratelimit'

Enable gzip compression for responses:

labels:
- 'traefik.http.middlewares.compress.compress=true'
- 'traefik.http.routers.myapp.middlewares=compress'

Add common security headers:

labels:
- 'traefik.http.middlewares.security-headers.headers.frameDeny=true'
- 'traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true'
- 'traefik.http.middlewares.security-headers.headers.browserXssFilter=true'
- 'traefik.http.middlewares.security-headers.headers.stsSeconds=31536000'
- 'traefik.http.routers.myapp.middlewares=security-headers'

Apply multiple middlewares in order (processed left to right):

labels:
- 'traefik.http.routers.myapp.middlewares=security-headers,compress,authelia'

The Traefik dashboard is available at:

  • http://192.168.1.10:8080 (insecure, internal only)
  • https://traefik.yourdomain.com (if you configured the label in the compose file)

The dashboard shows all routers, services, middlewares, and entrypoints in real time. It is Invaluable for debugging routing issues.

Securing the dashboard

Never expose the dashboard on port 8080 to the public internet without authentication. Options:

  1. Authelia/Basic Auth: Add an auth middleware to the dashboard router
  2. Firewall: Block port 8080 at the router level
  3. IP allowlist: Use Traefik’s ipwhitelist middleware
- 'traefik.http.middlewares.ip-whitelist.ipwhitelist.sourcerange=192.168.1.0/24'
- 'traefik.http.routers.traefik.middlewares=ip-whitelist'

Here is a complete example showing a Jellyfin media server behind Traefik with TLS, security Headers, and compression:

services:
jellyfin:
image: lscr.io/linuxserver/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- /mnt/tank/apps/jellyfin/config:/config
- /mnt/tank/media/tv:/data/tvshows
- /mnt/tank/media/movies:/data/movies
networks:
- proxy-network
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.jellyfin.rule=Host(`jellyfin.yourdomain.com`)'
- 'traefik.http.routers.jellyfin.entrypoints=websecure'
- 'traefik.http.routers.jellyfin.tls=true'
- 'traefik.http.routers.jellyfin.tls.certresolver=letsencrypt'
- 'traefik.http.routers.jellyfin.middlewares=security-headers,compress'
- 'traefik.http.services.jellyfin.loadbalancer.server.port=8096'
networks:
proxy-network:
external: true
name: proxy-network

When a service is not reachable through Traefik:

  1. Check Traefik logs:
Terminal window
docker logs traefik --tail 100
  1. Verify labels are applied:
Terminal window
docker inspect jellyfin | jq '.[0].Config.Labels'
  1. Check the dashboard. All routers should show a green status. Red or yellow means a configuration error.

  2. Common issues:

  • Container not on the proxy-network → Traefik cannot reach it
  • traefik.enable=true label missing or misspelled
  • Port mismatch: loadbalancer.server.port does not match the container’s internal port
  • DNS not resolving: the subdomain must point to your public IP
  • Certificate pending: Let’s Encrypt has rate limits. Check acme.json for error messages
  • exposedbydefault=false means you must explicitly enable each service with labels
  1. Increase log level temporarily:
command:
- '--log.level=DEBUG'
Docker socket permissions

Traefik needs read-only access to the Docker socket (/var/run/docker.sock). On TrueNAS SCALE, the Apps system may run Docker in a different context. If Traefik cannot detect containers, verify:

Terminal window
# Check if Traefik can read the socket
docker exec traefik ls -la /var/run/docker.sock
# The socket should show as readable (srw-rw---- or similar)

On TrueNAS SCALE, you may need to use the built-in Apps catalog for Traefik rather than a custom Compose, or ensure the Docker socket path is correct for the SCALE Kubernetes-based Docker runtime.

Let's Encrypt rate limits

Let’s Encrypt enforces strict rate limits: 50 certificates per registered domain per week, 5 Duplicate certificates per week. If you are experimenting and restarting containers frequently, you Can hit this limit. Use the Let’s Encrypt staging environment during development:

command:
- '--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory'

Staging certificates will show as invalid (red padlock) in browsers, but the flow is identical. Switch back to the production server once everything works.

Container restarts break routing

When a container restarts and gets a new IP, Traefik should automatically detect the change via Docker events. If it does not, check:

  • The Docker socket is mounted correctly
  • providers.docker is set to true
  • The container is on the same network Traefik is watching

You can force a provider refresh by restarting Traefik:

Terminal window
docker restart traefik
CORS issues with WebSocket services

Some applications (Jellyfin, code-server, WebTop) use WebSocket connections. Traefik supports WebSockets natively, but you may need to adjust timeouts:

labels:
- 'traefik.http.routers.myapp.middlewares=ws-timeout'
- 'traefik.http.middlewares.ws-timeout.headers.customResponseHeaders.X-Content-Type-Options='

If WebSocket connections drop after 30 seconds, check if your reverse proxy or ISP is terminating Idle connections.

TLS-ALPN-01 challenge fails behind CGNAT

If your ISP uses Carrier-Grade NAT (CGNAT), you cannot receive incoming connections on port 443. The TLS challenge will fail. Switch to the DNS challenge with your DNS provider (Cloudflare, Route53, Etc.) which requires no inbound ports.

This topic covers the essential concepts and techniques related to setup traefik, including key principles and practical applications.

Key concepts include:

  • core concepts and definitions
  • key principles and frameworks
  • practical applications
  • common techniques and methods
  • evaluation and critical analysis

A thorough understanding of these concepts, combined with regular practice and review, is essential for mastery of this topic.

Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.