Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Traefik Ubuntu
GUIDEInstall Guides

How to Install Traefik on Ubuntu 24.04 VPS: Modern Reverse Proxy with Automatic Let's Encrypt

27 min read

How to Install Traefik on Ubuntu 24.04 VPS: Modern Reverse Proxy with Automatic Let's Encrypt

Traefik has become the default ingress controller for thousands of Docker and Kubernetes deployments because it eliminates the friction of managing config files and TLS certificates by hand. Point it at your Docker socket, add a few labels to your containers, and Traefik discovers them automatically, provisions Let's Encrypt certificates on demand, and routes traffic with zero downtime reloads. This guide walks through installing Traefik v3 on an Ubuntu 24.04 VPS, configuring HTTP-01 and Cloudflare DNS-01 challenges, hardening it with middlewares, and wiring up Authelia for single sign-on.

Looking for a simpler setup? If you prefer a GUI-driven workflow, see our Nginx Proxy Manager guide. For a classical static-config proxy, try Nginx or the batteries-included Caddy server.

Table of Contents

  • What is Traefik?
  • Traefik vs Nginx: When to Choose Which
  • Prerequisites
  • Step 1: Prepare the Server
  • Step 2: Install Docker and Docker Compose
  • Step 3: Create the Directory Structure
  • Step 4: Write the Static Configuration (traefik.yml)
  • Step 5: Write the Dynamic Configuration (dynamic.yml)
  • Step 6: Configure Cloudflare DNS-01 for Wildcard Certificates
  • Step 7: Launch Traefik with docker-compose
  • Step 8: Verify the Installation
  • Step 9: Route Your First Service with Docker Labels
  • Step 10: Add Forward-Auth with Authelia
  • Alternative: Traefik as a Binary with systemd
  • Access Logs and Monitoring
  • Troubleshooting
  • FAQ
  • Next Steps
  • What is Traefik?

    Traefik is an open-source edge router and reverse proxy written in Go. Unlike traditional proxies that rely on static configuration files, Traefik watches the infrastructure it sits in front of -- Docker, Kubernetes, Consul, Nomad, or plain files -- and updates its routing table in real time as services come and go. Add a container with the right labels and Traefik immediately provisions a route, fetches a Let's Encrypt certificate, and starts forwarding traffic. Remove the container and the route disappears.

    Version 3, released in 2024, brought several significant upgrades: native HTTP/3 (QUIC) support, a redesigned plugin system, Kubernetes Gateway API v1 compatibility, WebAssembly middleware, and improved observability through OpenTelemetry. The core promise remains unchanged: zero-restart configuration, automatic TLS, and first-class support for modern cloud-native platforms.

    Traefik's routing model is built around four concepts. EntryPoints are the listening ports (:80, :443, :8080). Routers match incoming requests by host, path, headers, or query parameters and dispatch them to services (the backends). Middlewares sit between a router and its service and can rewrite requests, add authentication, enforce rate limits, compress responses, or redirect schemes. Everything is composable: one request can pass through a chain of middlewares before it reaches the upstream.

    The use cases are broad. Self-hosters run Traefik in front of their *arr media stack, Nextcloud, and Vaultwarden. Small SaaS companies use it as the single ingress for their microservices, with automatic wildcard certificates for customer subdomains. Kubernetes operators deploy it as the IngressRoute controller for entire clusters. And development teams use it in local Docker Compose setups to get real HTTPS URLs during development.

    Traefik vs Nginx: When to Choose Which

    Both Traefik and Nginx are production-grade reverse proxies, but they occupy different niches.

    AspectTraefik v3Nginx
    Configuration styleDynamic (labels, CRDs, files)Static .conf files
    Config reloadsZero-downtime, automaticManual nginx -s reload
    Service discoveryDocker, Kubernetes, Consul, etc.None (manual)
    Let's EncryptBuilt-in ACME clientExternal (certbot, acme.sh)
    Default routing modelHost/path/label-basedServer blocks
    DashboardBuilt-in, real-timeNone (Nginx Plus only)
    Raw HTTP performanceSlightly lower at 100k+ rpsIndustry-leading
    CachingBasic (plugin ecosystem)Mature, tunable proxy_cache
    HTTP/3 (QUIC)Native in v3Requires build-time flag
    Ideal fitContainer/Kubernetes environmentsStatic config, caching CDNs, high-throughput edge
    Choose Traefik when you run Docker Compose or Kubernetes, want automatic TLS with no extra tooling, deploy services frequently, or need wildcard certificates via DNS-01. Adding a new backend is one label on a container.

    Choose Nginx when you need fine-grained caching (proxy_cache_path), you run bare-metal services without a container runtime, you want absolute maximum raw throughput, or you already have a team fluent in Nginx syntax. Our Nginx install guide covers that setup in detail.

    For most self-hosted Docker stacks in 2026, Traefik is the more ergonomic choice -- which is why this guide exists.

    Prerequisites

    Before you begin, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access
    • SSH access to your server
    • A registered domain name with DNS pointing to the server's public IP (required for Let's Encrypt)
    • Ports 80 and 443 open in your firewall and your provider's security groups
    • At least 1 GB of RAM (Traefik itself uses ~50-100 MB; the rest is for your services)
    • A Cloudflare account (only if you want DNS-01 wildcard certificates -- optional)
    Recommended Plan: CloudCore Starter
    >
    For a reverse proxy fronting a handful of self-hosted services, the CloudCore Starter plan is more than enough:
    >
    - 4 vCPU cores
    - 8 GB RAM
    - 200 GB NVMe SSD
    - Unmetered bandwidth
    >
    Traefik is lightweight -- the Starter plan leaves plenty of headroom for your backend containers, databases, and a monitoring stack.

    Connect to your server via SSH to get started:

    bash
    ssh root@your-server-ip

    Step 1: Prepare the Server

    Update the package index and install the tools you will need throughout the guide:

    bash
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y curl wget ca-certificates gnupg lsb-release apache2-utils ufw

    The apache2-utils package provides htpasswd, which we use later to generate basic-auth credentials.

    Open the required firewall ports. Port 22 is SSH; 80 and 443 are HTTP and HTTPS for Traefik.

    bash
    sudo ufw allow 22/tcp
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw allow 443/udp   # HTTP/3 QUIC
    sudo ufw --force enable
    sudo ufw status

    Expected output:

    text
    Status: active

    To Action From -- ------ ---- 22/tcp ALLOW Anywhere 80/tcp ALLOW Anywhere 443/tcp ALLOW Anywhere 443/udp ALLOW Anywhere

    Point a DNS A record for at least one subdomain at your server. For the rest of this guide we assume:

    • traefik.example.com -- the Traefik dashboard
    • whoami.example.com -- a sample backend
    • example.com and *.example.com -- for the wildcard cert test
    Replace example.com with your real domain throughout.

    Step 2: Install Docker and Docker Compose

    Install Docker Engine from the official Docker apt repository. The version shipped in Ubuntu's default repos is usually several releases behind.

    bash
    # Add Docker's official GPG key
    sudo install -m 0755 -d /etc/apt/keyrings
    sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    sudo chmod a+r /etc/apt/keyrings/docker.asc

    Add the repository

    echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

    Install Docker Engine, CLI, and Compose plugin

    sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

    Verify

    docker --version docker compose version

    Expected output:

    text
    Docker version 27.5.1, build 9f9e405
    Docker Compose version v2.32.4

    Enable the service so Docker starts on boot:

    bash
    sudo systemctl enable --now docker

    If you want to run docker commands without sudo, add your user to the docker group (log out and back in afterwards):

    bash
    sudo usermod -aG docker $USER

    For a deeper walkthrough of Docker installation and tuning, see our dedicated Docker Compose guide.

    Step 3: Create the Directory Structure

    A clean directory layout makes Traefik configuration easier to reason about and back up. Create the following tree under /opt/traefik:

    bash
    sudo mkdir -p /opt/traefik/{config,config/dynamic,logs,letsencrypt}
    sudo touch /opt/traefik/letsencrypt/acme.json
    sudo chmod 600 /opt/traefik/letsencrypt/acme.json
    cd /opt/traefik

    The acme.json file is where Traefik persists its Let's Encrypt account key and issued certificates. Traefik refuses to start if its permissions are wider than 600.

    Create a dedicated Docker network that all proxied services will join. Keeping Traefik and its backends on a named external network decouples their compose files.

    bash
    docker network create traefik_proxy

    Step 4: Write the Static Configuration (traefik.yml)

    Traefik separates static config (loaded at startup: entrypoints, providers, certificate resolvers, logging) from dynamic config (routers, services, middlewares -- can change at runtime). The static file lives at /opt/traefik/config/traefik.yml.

    bash
    sudo nano /opt/traefik/config/traefik.yml

    Paste the following, replacing [email protected] with the email address Let's Encrypt should use for expiry notices:

    yaml
    # /opt/traefik/config/traefik.yml

    global: checkNewVersion: true sendAnonymousUsage: false

    ----- API & Dashboard -----

    api: dashboard: true insecure: false # never expose on :8080 in production debug: false

    ----- EntryPoints -----

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

    websecure: address: ":443" http: tls: certResolver: letsencrypt http3: {} # enable HTTP/3 / QUIC

    metrics: address: ":8082" # internal-only, for Prometheus scraping

    ----- Providers -----

    providers: docker: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false # require explicit traefik.enable=true network: traefik_proxy watch: true

    file: directory: /etc/traefik/dynamic watch: true

    ----- Certificate Resolvers -----

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

    letsencrypt-dns: acme: email: [email protected] storage: /letsencrypt/acme.json keyType: EC256 dnsChallenge: provider: cloudflare resolvers: - "1.1.1.1:53" - "1.0.0.1:53"

    ----- Logs -----

    log: level: INFO # DEBUG for first-time setup, then INFO filePath: /logs/traefik.log format: json

    accessLog: filePath: /logs/access.log format: json bufferingSize: 100 filters: statusCodes: - "400-599" # log only errors to keep the file slim fields: headers: defaultMode: drop names: User-Agent: keep Authorization: drop Cookie: drop

    ----- Metrics -----

    metrics: prometheus: entryPoint: metrics buckets: - 0.1 - 0.3 - 1.2 - 5.0 addEntryPointsLabels: true addServicesLabels: true

    Key points:

    • exposedByDefault: false means containers must explicitly opt in with traefik.enable=true. This prevents accidentally publishing internal services.
    • Two ACME resolvers are declared: letsencrypt (HTTP-01, good for most single hostnames) and letsencrypt-dns (DNS-01 via Cloudflare, required for wildcards).
    • keyType: EC256 issues ECDSA certificates, which are smaller and faster than RSA and widely supported by modern browsers.
    • The metrics entryPoint is bound to port 8082, which we will not expose on the host. It is reachable only from inside the Docker network for Prometheus scraping.

    Step 5: Write the Dynamic Configuration (dynamic.yml)

    Dynamic configuration holds middlewares and TLS options that we want available to any router. Create the file:

    bash
    sudo nano /opt/traefik/config/dynamic/dynamic.yml

    Paste:

    yaml
    # /opt/traefik/config/dynamic/dynamic.yml

    http: middlewares:

    # Force HTTPS (belt-and-braces; the entryPoint redirect already does this) redirect-to-https: redirectScheme: scheme: https permanent: true

    # Generic security headers secure-headers: headers: frameDeny: true browserXssFilter: true contentTypeNosniff: true referrerPolicy: "strict-origin-when-cross-origin" stsIncludeSubdomains: true stsPreload: true stsSeconds: 63072000 customResponseHeaders: X-Robots-Tag: "noindex, nofollow, nosnippet, noarchive" customFrameOptionsValue: "SAMEORIGIN" contentSecurityPolicy: "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'"

    # Rate limit - 100 average, 200 burst, per client IP rate-limit: rateLimit: average: 100 burst: 200 period: 1s sourceCriterion: ipStrategy: depth: 1 # trust one layer of X-Forwarded-For (Cloudflare)

    # Tight rate limit for login endpoints rate-limit-auth: rateLimit: average: 5 burst: 10 period: 1m

    # Basic auth for the dashboard. Replace hash in Step 7 labels, not here. dashboard-auth: basicAuth: users: - "admin:$apr1$REPLACE$ME" # replace with real hash realm: "Traefik Dashboard" removeHeader: true

    # Forward-auth -> Authelia (see Step 10) authelia: forwardAuth: address: "http://authelia:9091/api/authz/forward-auth" trustForwardHeader: true authResponseHeaders: - "Remote-User" - "Remote-Groups" - "Remote-Email" - "Remote-Name"

    # Strip a path prefix before forwarding (useful for path-based routing) strip-api-prefix: stripPrefix: prefixes: - "/api"

    # Compress responses compress-gzip: compress: {}

    ----- TLS Options -----

    tls: options: modern: minVersion: VersionTLS13 sniStrict: true

    intermediate: minVersion: VersionTLS12 cipherSuites: - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 curvePreferences: - CurveP521 - CurveP384 sniStrict: false

    Generate the basic-auth hash for the dashboard. The username below is admin; use any password you like:

    bash
    htpasswd -nbB admin 'YourStrongPasswordHere'

    Expected output (the $2y$... hash is bcrypt):

    text
    admin:$2y$05$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345

    We will put the hash into the docker-compose labels in Step 7 (not directly into dynamic.yml) so the username+password travels with the Traefik container definition. When placing it in compose labels, escape each $ as $$ because Docker Compose treats $ as a variable prefix.

    Step 6: Configure Cloudflare DNS-01 for Wildcard Certificates

    If your domain is on Cloudflare, DNS-01 lets you issue wildcard certificates (*.example.com) and works even if port 80 is blocked. Skip this section if you only need single-hostname certs.

  • Log in to Cloudflare and go to My Profile -> API Tokens -> Create Token.
  • Use the Edit zone DNS template.
  • Set Zone Resources to Include -> Specific zone -> example.com.
  • Click Continue to summary -> Create Token and copy the token value.
  • Create an env file that Traefik will read at startup:

    bash
    sudo nano /opt/traefik/.env

    Paste:

    env
    CF_DNS_API_TOKEN=your-cloudflare-api-token-here
    bash
    sudo chmod 600 /opt/traefik/.env

    The compose file in the next step references this via env_file: so the token never appears on the command line or in docker inspect output.

    Step 7: Launch Traefik with docker-compose

    Create the compose file:

    bash
    sudo nano /opt/traefik/docker-compose.yml

    Paste:

    yaml
    # /opt/traefik/docker-compose.yml

    services: traefik: image: traefik:v3.3 container_name: traefik restart: unless-stopped security_opt: - no-new-privileges:true ports: - "80:80" - "443:443" - "443:443/udp" # HTTP/3 env_file: - .env volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./config/traefik.yml:/etc/traefik/traefik.yml:ro - ./config/dynamic:/etc/traefik/dynamic:ro - ./letsencrypt:/letsencrypt - ./logs:/logs networks: - traefik_proxy labels: - "traefik.enable=true"

    # ----- Dashboard router ----- - "traefik.http.routers.dashboard.rule=Host(traefik.example.com)" - "traefik.http.routers.dashboard.entrypoints=websecure" - "traefik.http.routers.dashboard.service=api@internal" - "traefik.http.routers.dashboard.tls=true" - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" - "traefik.http.routers.dashboard.tls.options=modern@file" - "traefik.http.routers.dashboard.middlewares=dashboard-auth-inline,secure-headers@file,rate-limit@file"

    # ----- Inline basic-auth middleware (replace with your bcrypt hash) ----- # IMPORTANT: Each '$' in the hash MUST be escaped as '$$' in compose labels - "traefik.http.middlewares.dashboard-auth-inline.basicauth.users=admin:$$2y$$05$$REPLACE_WITH_YOUR_HASH"

    # ----- Wildcard certificate (optional, requires Cloudflare DNS-01) ----- - "traefik.http.routers.dashboard.tls.domains[0].main=example.com" - "traefik.http.routers.dashboard.tls.domains[0].sans=*.example.com" # To use DNS-01 for the wildcard instead of HTTP-01, swap the certresolver above: # - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt-dns"

    networks: traefik_proxy: external: true

    Replace traefik.example.com and example.com with your domain, and paste your bcrypt hash (with $ -> $$) into the dashboard-auth-inline label.

    Bring Traefik up:

    bash
    cd /opt/traefik
    docker compose up -d

    Expected output:

    text
    [+] Running 2/2
     ✔ Network traefik_proxy  External
     ✔ Container traefik      Started

    Watch the logs to confirm the ACME flow:

    bash
    docker compose logs -f traefik

    You should see:

    text
    level=info msg="Configuration loaded from file: /etc/traefik/traefik.yml"
    level=info msg="Starting provider *docker.Provider"
    level=info msg="Starting provider *file.Provider"
    level=info msg="Starting provider *acme.Provider"
    level=info msg="Testing certificate renew..."

    The first certificate issuance takes 10-30 seconds. Press Ctrl+C to exit the log stream.

    Step 8: Verify the Installation

    Visit https://traefik.example.com in your browser. You should get a basic-auth prompt. Enter the credentials you set in Step 5 and you will see the Traefik dashboard with tabs for HTTP, TCP, UDP, and the certificate store.

    Verify the certificate from the command line:

    bash
    curl -vI https://traefik.example.com 2>&1 | grep -E "subject:|issuer:|expire"

    Expected output:

    text
    * Server certificate:
    
    • subject: CN=traefik.example.com
    • start date: Apr 16 10:00:00 2026 GMT
    • expire date: Jul 15 10:00:00 2026 GMT
    • issuer: C=US; O=Let's Encrypt; CN=E5
    Confirm HTTP/3 is advertised:

    bash
    curl -sI https://traefik.example.com | grep -i alt-svc

    Expected: a header like alt-svc: h3=":443"; ma=2592000.

    Check that port 80 correctly redirects to 443:

    bash
    curl -sI http://traefik.example.com | head -1

    Expected: HTTP/1.1 308 Permanent Redirect.

    Step 9: Route Your First Service with Docker Labels

    Let's put a real backend behind Traefik. The traefik/whoami image is a minimal HTTP server that echoes request details -- perfect for testing routing.

    Create a new compose file in its own directory:

    bash
    sudo mkdir -p /opt/whoami
    sudo nano /opt/whoami/docker-compose.yml

    Paste:

    yaml
    services:
      whoami:
        image: traefik/whoami:latest
        container_name: whoami
        restart: unless-stopped
        networks:
          - traefik_proxy
        labels:
          - "traefik.enable=true"
          - "traefik.docker.network=traefik_proxy"

    # HTTPS router - "traefik.http.routers.whoami.rule=Host(whoami.example.com)" - "traefik.http.routers.whoami.entrypoints=websecure" - "traefik.http.routers.whoami.tls=true" - "traefik.http.routers.whoami.tls.certresolver=letsencrypt" - "traefik.http.routers.whoami.middlewares=secure-headers@file,rate-limit@file,compress-gzip@file"

    # Backend port inside the container - "traefik.http.services.whoami.loadbalancer.server.port=80"

    networks: traefik_proxy: external: true

    Launch it:

    bash
    cd /opt/whoami
    docker compose up -d

    Within seconds Traefik discovers the container, requests a certificate for whoami.example.com, and starts routing:

    bash
    curl https://whoami.example.com

    Expected output:

    text
    Hostname: 5f2a1b3c4d6e
    IP: 127.0.0.1
    IP: 172.22.0.3
    RemoteAddr: 172.22.0.2:52314
    GET / HTTP/1.1
    Host: whoami.example.com
    User-Agent: curl/8.5.0
    Accept: /
    X-Forwarded-For: 203.0.113.42
    X-Forwarded-Host: whoami.example.com
    X-Forwarded-Port: 443
    X-Forwarded-Proto: https
    X-Forwarded-Server: 5e9a2b1c8f7d
    X-Real-Ip: 203.0.113.42

    From here, any new container with the right labels becomes a live site. No reloads, no edits to Traefik's config.

    Common Label Patterns

    GoalLabels
    HTTPS with auto certtraefik.enable=true, router .rule, .entrypoints=websecure, .tls.certresolver=letsencrypt
    Multiple domains on one service<code> rule=Host(</code>a.com)Host(b.com) `</td></tr><tr><td>Path-based routing</td><td> rule=Host(example.com) && PathPrefix(/api) </td></tr><tr><td>Strip path prefix</td><td>attach middleware <code>strip-api-prefix@file</code></td></tr><tr><td>Redirect www to root</td><td>dedicated router with <code>redirectregex</code> middleware</td></tr><tr><td>Weighted load balancing</td><td><code>traefik.http.services.NAME.loadbalancer.weighted</code> plus multiple backend services</td></tr><tr><td>Sticky sessions</td><td><code>traefik.http.services.NAME.loadbalancer.sticky.cookie=true</code></td></tr><tr><td>Custom health check</td><td><code>traefik.http.services.NAME.loadbalancer.healthcheck.path=/healthz</code></td></tr></tbody></table></div>

    Step 10: Add Forward-Auth with Authelia

    Basic auth is fine for the dashboard, but for anything serious (Sonarr, Portainer, a company wiki) you want a real single sign-on. Authelia is the go-to choice for Traefik-fronted stacks.

    This section assumes you have followed our Authelia install guide and have Authelia running on the traefik_proxy network with container name authelia and port 9091.

    The authelia middleware is already declared in dynamic.yml (Step 5). Attach it to any service that should require login:

    <div class="code-block" data-lang="yaml"><div class="code-block__header"><span class="code-block__lang">yaml</span></div><pre><code class="language-yaml">labels: - &quot;traefik.enable=true&quot; - &quot;traefik.http.routers.portainer.rule=Host(portainer.example.com)&quot; - &quot;traefik.http.routers.portainer.entrypoints=websecure&quot; - &quot;traefik.http.routers.portainer.tls.certresolver=letsencrypt&quot; - &quot;traefik.http.routers.portainer.middlewares=authelia@file,secure-headers@file&quot; - &quot;traefik.http.services.portainer.loadbalancer.server.port=9000&quot;</code></pre></div>

    When an unauthenticated request hits portainer.example.com, Traefik calls http://authelia:9091/api/authz/forward-auth. Authelia redirects the user to its login page, validates credentials (with optional 2FA), sets a session cookie, and on subsequent requests responds 200 OK with Remote-User, Remote-Groups, and Remote-Email headers that the backend can consume.

    Authelia itself needs to be exposed by Traefik too, so users can reach the login page:

    <div class="code-block" data-lang="yaml"><div class="code-block__header"><span class="code-block__lang">yaml</span></div><pre><code class="language-yaml">authelia: image: authelia/authelia:latest container_name: authelia networks: - traefik_proxy volumes: - ./authelia/config:/config labels: - &quot;traefik.enable=true&quot; - &quot;traefik.http.routers.authelia.rule=Host(auth.example.com)&quot; - &quot;traefik.http.routers.authelia.entrypoints=websecure&quot; - &quot;traefik.http.routers.authelia.tls.certresolver=letsencrypt&quot; - &quot;traefik.http.services.authelia.loadbalancer.server.port=9091&quot;</code></pre></div>

    Cookie domain, session secret, and access control rules live in authelia/config/configuration.yml -- see the linked Authelia guide for a production-ready example.

    Alternative: Traefik as a Binary with systemd

    Some operators prefer running Traefik directly on the host rather than in Docker -- for example, when proxying non-container workloads or when Docker is not available. Traefik ships a single static binary, so the systemd setup is straightforward.

    Download the latest v3 release:

    <div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">TRAEFIK_VERSION=v3.3.4 cd /tmp wget https://github.com/traefik/traefik/releases/download/${TRAEFIK_VERSION}/traefik_${TRAEFIK_VERSION}_linux_amd64.tar.gz tar -xzvf traefik_${TRAEFIK_VERSION}_linux_amd64.tar.gz sudo install -o root -g root -m 0755 traefik /usr/local/bin/traefik traefik version</code></pre></div>

    Create a dedicated system user and directories:

    <div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo useradd --system --no-create-home --shell /usr/sbin/nologin traefik sudo mkdir -p /etc/traefik/dynamic /var/log/traefik /var/lib/traefik sudo touch /var/lib/traefik/acme.json sudo chown -R traefik:traefik /etc/traefik /var/log/traefik /var/lib/traefik sudo chmod 600 /var/lib/traefik/acme.json</code></pre></div>

    Copy the traefik.yml from Step 4 into /etc/traefik/traefik.yml and adjust paths: storage to /var/lib/traefik/acme.json, filePath to /var/log/traefik/..., and directory to /etc/traefik/dynamic. Remove the Docker provider block (there is no docker socket here).

    Because ports 80 and 443 are privileged, grant the binary permission to bind them without running as root:

    <div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/traefik</code></pre></div>

    Create the systemd unit:

    <div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo tee /etc/systemd/system/traefik.service &gt; /dev/null &lt;&lt;'EOF' [Unit] Description=Traefik Proxy Documentation=https://doc.traefik.io/traefik/ After=network-online.target Wants=network-online.target

    [Service] Type=simple User=traefik Group=traefik EnvironmentFile=-/etc/traefik/traefik.env ExecStart=/usr/local/bin/traefik --configFile=/etc/traefik/traefik.yml Restart=on-failure RestartSec=5 LimitNOFILE=1048576 NoNewPrivileges=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/var/lib/traefik /var/log/traefik PrivateTmp=true

    [Install] WantedBy=multi-user.target EOF

    sudo systemctl daemon-reload sudo systemctl enable --now traefik sudo systemctl status traefik</code></pre></div>

    Put the Cloudflare token (if used) in /etc/traefik/traefik.env with mode 600:

    <div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">echo 'CF_DNS_API_TOKEN=your-token' | sudo tee /etc/traefik/traefik.env &gt; /dev/null sudo chmod 600 /etc/traefik/traefik.env sudo systemctl restart traefik</code></pre></div>

    For most users, the Docker approach from Steps 2-7 is simpler to maintain -- upgrades become docker compose pull && docker compose up -d instead of downloading a new tarball.

    Access Logs and Monitoring

    Reading access logs

    JSON access logs go to /opt/traefik/logs/access.log. Tail them live:

    <div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">docker exec traefik tail -f /logs/access.log</code></pre></div>

    If you have jq installed on the host, pretty-print:

    <div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo tail -f /opt/traefik/logs/access.log | jq .</code></pre></div>

    A single request line looks like:

    <div class="code-block" data-lang="json"><div class="code-block__header"><span class="code-block__lang">json</span></div><pre><code class="language-json">{ &quot;ClientHost&quot;: &quot;203.0.113.42&quot;, &quot;RequestHost&quot;: &quot;whoami.example.com&quot;, &quot;RequestMethod&quot;: &quot;GET&quot;, &quot;RequestPath&quot;: &quot;/&quot;, &quot;DownstreamStatus&quot;: 200, &quot;Duration&quot;: 2145832, &quot;RouterName&quot;: &quot;whoami@docker&quot;, &quot;ServiceName&quot;: &quot;whoami@docker&quot;, &quot;StartUTC&quot;: &quot;2026-04-16T10:15:42Z&quot;, &quot;RequestProtocol&quot;: &quot;HTTP/2.0&quot;, &quot;TLSVersion&quot;: &quot;1.3&quot;, &quot;TLSCipher&quot;: &quot;TLS_AES_128_GCM_SHA256&quot; }</code></pre></div>

    Because statusCodes: 400-599 is the active filter, only errors are written. Flip it off during debugging by commenting out the filter block and restarting Traefik.

    Prometheus metrics

    Port 8082 inside the container exposes /metrics. To scrape it from Prometheus running on the same Docker network, add a job:

    <div class="code-block" data-lang="yaml"><div class="code-block__header"><span class="code-block__lang">yaml</span></div><pre><code class="language-yaml">scrape_configs: - job_name: traefik static_configs: - targets: [&quot;traefik:8082&quot;]</code></pre></div>

    Useful metrics:

    • traefik_entrypoint_requests_total{code, entrypoint}
    • traefik_service_request_duration_seconds_bucket
    • traefik_service_open_connections
    • traefik_tls_certs_not_after
    Grafana dashboard 4475 renders these out of the box.

    Log rotation

    For the Docker install, the log files are on the host volume. Add a logrotate config:

    <div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo tee /etc/logrotate.d/traefik &gt; /dev/null &lt;&lt;'EOF' /opt/traefik/logs/*.log { daily rotate 14 missingok notifempty compress delaycompress copytruncate } EOF</code></pre></div>

    Troubleshooting

    <div class="article-table-wrap"><table><thead><tr><th>Problem</th><th>Cause</th><th>Solution</th></tr></thead><tbody><tr><td>Dashboard returns 404</td><td>Router rule does not match</td><td>Check <code>docker logs traefik</code> and confirm the Host rule exactly matches the URL you typed</td></tr><tr><td><code>acme: error: 403 :: urn:ietf:params:acme:error:unauthorized</code></td><td>HTTP-01 challenge could not reach the server on port 80</td><td>Verify DNS is correct and port 80 is not blocked by firewall, cloud security group, or Cloudflare orange-cloud (temporarily grey the record or switch to DNS-01)</td></tr><tr><td>Certificate stuck as self-signed (<code>TRAEFIK DEFAULT CERT</code>)</td><td>ACME provisioning still in progress or failed</td><td>
    docker logs traefik \
    grep -i acme. For Cloudflare DNS-01, confirm token is loaded: docker exec traefik env \grep CF_DNS</td></tr><tr><td>Basic-auth prompt loops forever</td><td>Hash contains unescaped <code>$</code> in compose labels</td><td>Replace each <code>$</code> with <code>$$</code> in docker-compose labels</td></tr><tr><td><code>Gateway Timeout</code> when hitting a backend</td><td>Backend not on <code>traefik_proxy</code> network or wrong port label</td><td><code>docker network inspect traefik_proxy</code> and confirm backend is attached; verify <code>traefik.http.services.NAME.loadbalancer.server.port</code> matches the container's listening port</td></tr><tr><td>HTTP/3 not negotiated</td><td>UDP 443 closed on firewall</td><td><code>sudo ufw allow 443/udp</code> and open UDP at the cloud provider firewall layer</td></tr><tr><td>Rate limit blocks real traffic</td><td><code>ipStrategy.depth</code> wrong for your proxy chain</td><td>If you sit directly on the internet, set <code>depth: 0</code>. Behind one Cloudflare layer, <code>depth: 1</code>. Behind Cloudflare and a load balancer, <code>depth: 2</code></td></tr><tr><td><code>permission denied</code> reading acme.json</td><td>File permissions too open</td><td>Must be exactly <code>600</code>: <code>chmod 600 /opt/traefik/letsencrypt/acme.json</code></td></tr><tr><td>Dashboard works but shows no routers</td><td>Containers not on the same network, or <code>exposedByDefault=false</code> plus missing <code>traefik.enable=true</code> label</td><td>Add the label, restart the backend container</td></tr></tbody></table></div>

    Enable DEBUG logging temporarily

    Change log.level in traefik.yml from INFO to DEBUG, then:

    <div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">docker compose restart traefik docker compose logs -f traefik | grep -i acme</code></pre></div>

    Revert to INFO once the issue is solved -- DEBUG writes a lot of output.

    FAQ

    Can I migrate from Nginx to Traefik without downtime?

    Yes, and it is a common migration. Run Traefik on an alternate host-port pair (for example 8080 and 8443) during the transition, route a single subdomain through it for testing, then swap the DNS records or the cloud load balancer target once you are confident. Traefik's automatic ACME handles certificate provisioning as soon as DNS points at it. Keep Nginx installed as a fallback until Traefik has served production traffic for a week.

    Does Traefik support HTTP/3 out of the box?

    Traefik v3 supports HTTP/3 natively -- no compile-time flags, no extra modules. Declare http3: {} on the websecure entryPoint (as we did in Step 4) and expose UDP port 443. Browsers that support HTTP/3 will upgrade automatically via the alt-svc header Traefik sends on the first HTTP/2 response.

    How does Traefik handle certificate renewal?

    Traefik renews certificates automatically when they are within 30 days of expiry. The ACME resolver writes to acme.json and the new cert is served on the next request -- no restart needed. Make sure acme.json is on a persistent volume (not an ephemeral overlay) and is included in your backup routine. Losing it means you will hit Let's Encrypt rate limits when re-issuing many certificates at once.

    Should I use HTTP-01 or DNS-01?

    Use HTTP-01 for simple single-hostname setups where port 80 is reachable. It is the simplest path and works with any DNS provider. Use DNS-01 when you need wildcard certificates (*.example.com), when port 80 is blocked (corporate networks, certain cloud providers), or when the target host is not yet publicly reachable (new deployments). DNS-01 requires API credentials for a supported DNS provider -- Cloudflare is the most common, but Traefik supports 75+ providers.

    Is the Traefik dashboard safe to expose publicly?

    Only with strong authentication. Never use api.insecure: true in production -- that exposes the dashboard on plain HTTP port 8080 with no auth. The label-based setup in Step 7 puts the dashboard behind HTTPS, basic auth, rate limiting, and security headers. For even stronger protection, swap the dashboard-auth-inline middleware for authelia@file (Step 10) so the dashboard sits behind 2FA.

    How do I back up Traefik?

    Back up the entire /opt/traefik directory. The critical files are letsencrypt/acme.json (your Let's Encrypt account key and issued certs), the config/ tree (static and dynamic YAML), and the .env file (API tokens). A daily rsync to an offsite location is sufficient. Traefik is stateless beyond acme.json.

    Can Traefik route TCP and UDP, not just HTTP?

    Yes. Define a new entryPoint on the desired port and use tcp.routers or udp.routers instead of http.routers. Common use cases include MinIO TLS passthrough, Postgres over TLS, and game servers. See the official TCP routers docs for SNI-based routing.

    What about Kubernetes?

    Traefik is one of the most popular ingress controllers for Kubernetes. The same concepts (routers, services, middlewares) are exposed as Kubernetes CRDs -- IngressRoute, Middleware, TLSOption`. If you plan to move to Kubernetes later, the learning you do here transfers directly.

    Next Steps

    With Traefik running, here are natural follow-ons:

    • Deploy Authelia for SSO -- add real single sign-on with 2FA in front of every self-hosted service. See our Authelia install guide.
    • Add CrowdSec for bouncer-based protection -- ban scanning IPs and credential-stuffing attacks automatically via the CrowdSec Traefik bouncer plugin. See our CrowdSec install guide.
    • Front a Coolify PaaS -- use Traefik as the edge for a Coolify install that handles application deploys for your team.
    • Build a full monitoring stack -- scrape Traefik's Prometheus metrics into a Grafana dashboard using our monitoring stack guide.
    • Compare alternatives -- if Traefik's label model is not for you, try Nginx Proxy Manager for a GUI, plain Nginx for static configs, or Caddy for zero-config automatic HTTPS.
    • Consult the official docs -- the Traefik documentation is excellent and kept up to date with every release.

    Need a VPS that is ready for Traefik?
    >
    Our CloudCore Starter plan ships Ubuntu 24.04 with full root access, unmetered bandwidth, and instant provisioning -- ideal for a Traefik-fronted self-hosted stack.
    >
    - 4 vCPU cores
    - 8 GB RAM
    - 200 GB NVMe SSD
    - Instant deploy, hourly or monthly billing
    >
    Launch a VPS now and be serving HTTPS traffic in under ten minutes.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket