Run traefik behind traefik reverse proxy

Hello,
I realize this is an old topic, but I recently came across this issue because I have basically the same use case as @kdankert ...and I managed to get it working!
In the following example, I will be referring to the Traefik instances as specified in @kdankert 's post.

A couple of things to keep in mind:

  • I am using separate domains for the Traefik1 and Traefik2 dashboards.
  • Traefik1 is using a TCP router to forward requests to Traefik2, as requests are still encrypted when passing through Traefik1.
  • I am using constraints to limit the containers each Traefik instance creates routes for.
  • I already created a second network in order to demonstrate the isolation of each proxy's "subcontainers"

Traefik1 configuration:

traefik.yml

## STATIC CONFIGURATION
log:
  level: INFO

api:
  insecure: false
  dashboard: true

entryPoints:
  websecure:
    address: ":443"

providers:
  file:
    filename: dynamic.yml
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    constraints: Label(`my.zone`, `zone1`)

certificatesResolvers:
  lets-encr:
    acme:
      email: <your email>
      storage: acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

docker-compose.yml

version: "3.3"

services:
  traefik1:
    image: "traefik:v2.6.1"
    container_name: "traefik1"
    restart: "always"
    hostname: "traefik1"
    ports:
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./dynamic.yml:/dynamic.yml:ro"
      - "./traefik.yml:/traefik.yml:ro"
      - "./acme.json:/acme.json"
    labels:
      - "my.zone=zone1"
      - "traefik.enable=true"
      - "traefik.http.routers.traefik1.entrypoints=websecure"
      - "traefik.http.routers.traefik1.tls.certresolver=lets-encr"
      - "traefik.http.routers.traefik1.tls.domains[0].main=traefik.$MY_DOMAIN1"
      - "traefik.http.routers.traefik1.rule=Host(`traefik1.$MY_DOMAIN1`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
      - "traefik.http.routers.traefik1.service=api@internal"
    # adding DNS mapping to /etc/hosts in order to avoid Traefik issues in case of wrong container starting order
    extra_hosts:
      - "$TRAEFIK2_DNS_MAPPER"
    # used to pass environment variables into the container in order to support environment substition in traefik files
    env_file:
      - ".env"

networks:
  default:
    external:
      name: $TRAEFIK1_NET

dynamic.yml

tcp:
  routers:
    to_traefik2:
      entrypoints:
        - "websecure"
      rule: HostSNI({{ env "TAEAFIK2_HOST_SNI" }})
      tls:
        passthrough: true
      service: traefik2

  services:
    traefik2:
      loadBalancer:
        servers:
          - address: {{ env "TRAEFIK2_NET1_ADDRESS" }}

.env

MY_DOMAIN1=<domain for Traefik1's dashboard>
TRAEFIK1_NET=traefik1_net
CF_API_EMAIL=<your CF API email>
CF_ZONE_API_TOKEN=<your Zone API Token>
CF_DNS_API_TOKEN=-<your DNS token>
TRAEFIK2_DNS_MAPPER=traefik2:<static ip of traefik2 in NET1>

TAEAFIK2_HOST_SNI=`traefik.<Traefik2's dashboard domain>`
TRAEFIK2_NET1_ADDRESS=traefik2:443

Traefik2 configuration:

traefik.yml

## STATIC CONFIGURATION
log:
  level: INFO

api:
  insecure: false
  dashboard: true

entryPoints:
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    constraints: Label(`my.zone`, `zone2`)

certificatesResolvers:
  lets-encr:
    acme:
      email: <your email>
      storage: acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

docker-compose.yml

version: '3.3'
services:
  traefik2:
    image: "traefik:v2.6.1"
    container_name: "traefik2"
    restart: "always"
    hostname: "treafik2"
    env_file:
      - ".env_traefik"
    ports:
      - "443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./traefik.yml:/traefik.yml:ro"
      - "./acme.json:/acme.json"
    labels:
      - "my.zone=zone2"
      - "traefik.enable=true"
      - "traefik.http.routers.traefik2.entrypoints=websecure"
      - "traefik.http.routers.traefik2.tls.certresolver=lets-encr"
      - "traefik.http.routers.traefik2.tls.domains[0].main=traefik.$MY_DOMAIN2"
      - "traefik.http.routers.traefik2.rule=Host(`traefik.$MY_DOMAIN2`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
      - "traefik.http.routers.traefik2.service=api@internal"
    networks:
      traefik1_net:
        ipv4_address: $TRAEFIK2_NET1_IPV4
      traefik2_net: null

networks:
  traefik1_net:
    external:
      name: $TRAEFIK1_NET
  traefik2_net:
    external:
      name: $TRAEFIK2_NET

.env

MY_DOMAIN2=<domain for Traefik2's dashboard>
TRAEFIK1_NET=traefik1_net
TRAEFIK2_NET=traefik2_net
TRAEFIK2_NET1_IPV4=<static ip of traefik2 in NET1>

.env_traefik

# this .env file should not be used for substitution in docker-compose.yml as the environment variables will only be available in the running container
CF_API_EMAIL=<your CF API email>
CF_ZONE_API_TOKEN=<your Zone API Token>
CF_DNS_API_TOKEN=-<your DNS token>