Skip to content

Traefik Behind Traefik (tcp router) : A Step-by-Step Guide

Introduction

Setting up Traefik behind Traefik allows for a flexible, scalable, and secure reverse proxy architecture. In this guide, we'll configure a primary Traefik instance that passes TCP routes (without SSL termination) to secondary Traefik instances. These secondary instances reside on separate VMs and can run Docker or Kubernetes environments, handling SSL termination at their level.

Traefik TCP Routers

Traefik TCP routing is similar to AWS Gateway Load Balancer (GWLB).
However, GWLB itself does not handle SNI directlyβ€”it works with third-party appliances (e.g., NGINX, HAProxy, or Traefik) that can inspect SNI and forward traffic accordingly.

Architecture Overview

graph TD
    Internet["🌍 Internet"]
    PrimaryTraefik["πŸ”€ Traefik (Primary) 
    *.domain1.com 
    *.domain2.com"]
    Secondary1["πŸ”€ Traefik Secondary VM1 
    🏠 *.domain1.com" 
    πŸ”’ TLS / Certificate]
    Secondary2["πŸ”€ Traefik Secondary VM2 
    🏠 *.domain2.com"
    πŸ”’ TLS / Certificate]

    subgraph "Services (VM2)"
        Service2c["🌐 Nginx"]
        Service2d["πŸ—„οΈ MongoDB"]
    end

    subgraph "Services (VM1)"
        Service1c["🌐 Nginx"]
        Service1d["πŸ—„οΈ MongoDB"]
    end

    Internet -->|TCP Traffic| PrimaryTraefik
    PrimaryTraefik -->|TCP Route 1 | Secondary1
    PrimaryTraefik -->|TCP Route 2 | Secondary2

    Secondary1 --> Service1c
    Secondary1 --> Service1d

    Secondary2 --> Service2c
    Secondary2 --> Service2d

Why Use This Setup?

  • Scalability: Distribute traffic across multiple secondary instances.
  • Security: Offload SSL termination to the secondary instances.
  • Flexibility: Use different environments (Docker, Kubernetes) for different services.

Prerequisites

  • A primary Traefik instance running on a VM.
  • Secondary Traefik instances running on separate VMs (Docker or Kubernetes-based).
  • DNS records pointing to the primary Traefik instance.
  • Basic knowledge of Traefik configuration and networking.

Step 1: Configure the Primary Traefik Instance

Install Traefik

https://github.com/jdedev/tophomelab/blob/main/docker/traefik3/docker-compose.yml

services:
  traefik3:
    image: traefik:v3.0
    container_name: traefik
    restart: always
    command:
      - "--configFile=/etc/traefik/traefik.yml"  # Load static config from file     
    environment:
      - TZ=Australia/Melbourne       
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"  # Dashboard (if insecure mode is enabled)
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./traefik.yml:/etc/traefik/traefik.yml:ro"  # Mount static config
      - "./dynamic.yml:/etc/traefik/dynamic.yml:ro"  # Mount dynamic config      
      - "./logs:/var/log"  # Mount logs to local directory      
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik3.homelab.lan`)"
      - "traefik.http.routers.traefik.entrypoints=web"
      - "traefik.http.routers.traefik.service=api@internal"

Update traefik.yml

https://github.com/jdedev/tophomelab/blob/main/docker/traefik3/traefik.yml

Add reference to a dynamic configuration file

# Enable Docker & File Provider
providers:
  docker:
    exposedByDefault: false
  file:
    filename: "/etc/traefik/dynamic.yml"  # Loads dynamic configuration
    watch: true  # Auto-reload on changes

Update dynamic.yml

Route different domains to secondary Traefik instances / VMs.
Here we are using regular expressions to route traffic based on the servername. https://doc.traefik.io/traefik/routing/routers/#hostsni-and-hostsniregexp

  • domain1.com → 10.0.5.111:443
  • domain2.com → 10.0.6.101:443

https://github.com/jdedev/tophomelab/blob/main/docker/traefik3/dynamic.yml

tcp:
  routers:
    domain1-router:
      entryPoints:
        - websecure  # This should match the entrypoint for 443
      rule: "HostSNIRegexp(`^.+\\.domain1\\.com`)"
      service: domain1-service
      tls:
        passthrough: true        

    domain2-router:
      entryPoints:
        - websecure  # This should match the entrypoint for 443
      rule: "HostSNIRegexp(`^.+\\.domain2\\.com`)"
      service: domain2-service
      tls:
        passthrough: true   

  services:
    domain1-service:
      loadBalancer:
        servers:
          - address: "10.0.5.111:443"  # Forward traffic to the VM     

    domain2-service:
      loadBalancer:
        servers:
          - address: "10.0.6.101:443"  # Forward traffic to the VM

Step 2: Configure the Secondary Traefik Instances

Update traefik.yml on Secondary Traefik

entryPoints:
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: "letsencrypt"

Enable Let's Encrypt

certificatesResolvers:
  letsencrypt:
    acme:
      email: "[email protected]"
      storage: "/etc/traefik/acme.json"
      httpChallenge:
        entryPoint: "web"

Step 3: Testing the Setup

  • Run Traefik on both primary and secondary instances.
  • Verify traffic is routed through the primary Traefik and reaches the secondary Traefik.
  • Check SSL termination at the secondary Traefik instance using
$ curl -v https://yourdomain.com

Response

*   Trying 12.34.56.78:443...
* Connected to domain1.com (12.34.56.78) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):

Source Code

You can find the source code for this setup in the following GitHub repository:

https://github.com/jdedev/tophomelab/tree/main/docker/traefik3

Conclusion

By setting up Traefik behind Traefik, you achieve a modular, scalable, and secure proxy setup. This allows different environments to handle SSL termination while maintaining centralized routing control.