Skip to content

tailscale-x/tailscale-traefik

Repository files navigation

cloudflare-tailscale-traefik

Traefik client for Cloudflare Tailscale DNS

A first-party extension/plugin for the cloudflare-tailscale-dns project. This Docker image runs Traefik with dynamic configuration fetched from the Cloudflare Tailscale DNS sync service's SSE endpoint (/api/config). Traefik automatically routes traffic to Tailscale machines based on WAN domain DNS records.

Features

Feature Description
Dynamic Configuration Real-time Traefik configuration updates via Server-Sent Events (SSE)
Automatic Reconnection Automatically reconnects to SSE endpoint with configurable retry delays
HTTP/HTTPS Routing Routes HTTP and HTTPS traffic to Tailscale machines based on domain names
TCP Routing Supports TCP routing with TLS passthrough for protocols like MySQL, SSH, RDP
ACL-Based Configuration Routes configured via Tailscale ACL host entries using SRV record format
Multiple Entrypoints Supports multiple Traefik entrypoints (web, websecure, tcp, custom)

How It Works

sequenceDiagram
    participant GEN as generate-config
    participant SSE as cloudflare-tailscale-dns<br/>/api/config
    participant FILE as Dynamic Config File
    participant TRAEFIK as Traefik
    participant TS as Tailscale Machines
    
    GEN->>SSE: Connect to SSE endpoint
    SSE-->>GEN: machines-snapshot event
    GEN->>GEN: Parse ACL hosts
    GEN->>GEN: Generate Traefik config
    GEN->>FILE: Write dynamic.yml
    FILE->>TRAEFIK: File provider detects change
    TRAEFIK->>TRAEFIK: Reload configuration
    Note over TRAEFIK,TS: Routes traffic based on<br/>ACL host entries
    TRAEFIK->>TS: Forward requests to<br/>Tailscale IPs
Loading

Component Details

Component Description
generate-config Rust binary that connects to SSE endpoint, receives configuration snapshots, and generates Traefik dynamic configuration JSON
SSE Endpoint (/api/config) First-party API endpoint from the cloudflare-tailscale-dns project. Cloudflare Worker endpoint that streams machines-snapshot events containing Tailscale devices and ACL configuration
Traefik File Provider Watches the dynamic configuration file and automatically reloads when changes are detected
Traefik Reverse proxy that routes traffic to Tailscale machines based on the dynamic configuration

Prerequisites

Requirement Description
Docker Docker installed and running
cloudflare-tailscale-dns The cloudflare-tailscale-dns service must be deployed and accessible
SSE Endpoint Access to the /api/config SSE endpoint from the cloudflare-tailscale-dns service
WAN Domain Records The cloudflare-tailscale-dns service must have WAN domain records configured

Setup Instructions

1. Build the Docker Image

docker build -t cloudflare-tailscale-traefik .

2. Configure Environment Variables

Name Type Required Description Example/Default
TRAEFIK_CONFIG_API_ENDPOINT Variable Yes The SSE (Server-Sent Events) endpoint URL from the cloudflare-tailscale-dns service that streams Traefik dynamic configuration. This first-party API endpoint streams configuration snapshots via SSE events (machines-snapshot) https://your-worker.your-subdomain.workers.dev/api/config
TRAEFIK_DYNAMIC_CONFIG_FILE Variable No Path to the dynamic configuration file /etc/traefik/dynamic.yml
TRAEFIK_CONFIG_RETRY_DELAY_SECONDS Variable No Delay between reconnection attempts in seconds 5
HTTP_PORT Variable No Port for HTTP entrypoint 80
HTTPS_PORT Variable No Port for HTTPS entrypoint 443
TCP_PORT Variable No Port for TCP with TLS entrypoint 4433

3. Run the Container

Basic usage:

TRAEFIK_CONFIG_API_ENDPOINT=https://cloudflare-tailscale-dns.dlkn.workers.dev/api/config docker run -ti \
  --name traefik \
  -p 80:80 \
  -p 443:443 \
  -p 4433:4433 \
  -p 8080:8080 \
  -e TRAEFIK_CONFIG_API_ENDPOINT \
  cloudflare-tailscale-traefik

With custom ports:

TRAEFIK_CONFIG_API_ENDPOINT=https://cloudflare-tailscale-dns.dlkn.workers.dev/api/config \
HTTP_PORT=8080 \
HTTPS_PORT=8443 \
TCP_PORT=9443 \
docker run -d \
  --name traefik \
  -p 8080:8080 \
  -p 8443:8443 \
  -p 9443:9443 \
  -p 8080:8080 \
  -e TRAEFIK_CONFIG_API_ENDPOINT \
  -e HTTP_PORT \
  -e HTTPS_PORT \
  -e TCP_PORT \
  cloudflare-tailscale-traefik

Configuration

Static Configuration

The Traefik static configuration is in traefik.yml.template and includes:

Component Description
Entrypoints HTTP (port 80 by default, configurable via HTTP_PORT) with redirect to HTTPS, HTTPS (port 443 by default, configurable via HTTPS_PORT), TCP with TLS (port 4433 by default, configurable via TCP_PORT)
File Provider Watches the dynamic configuration file generated by the generate-config binary
API Dashboard Enabled on port 8080 (insecure mode - configure authentication for production)

Dynamic Configuration

The dynamic configuration is streamed from the /api/config endpoint (provided by the cloudflare-tailscale-dns service) via Server-Sent Events (SSE). The generate-config binary processes SSE events and generates JSON in the following format:

{
  "http": {
    "routers": {
      "router-server1-wan-example-com": {
        "rule": "Host(`server1.wan.example.com`)",
        "service": "service-server1-wan-example-com-http-3000",
        "entryPoints": ["web"]
      }
    },
    "services": {
      "service-server1-wan-example-com-http-3000": {
        "loadBalancer": {
          "servers": [{"url": "http://100.64.0.1:3000"}]
        }
      }
    }
  },
  "tcp": {
    "routers": {
      "router-database-wan-example-com-mysql": {
        "rule": "HostSNI(`database.wan.example.com`)",
        "service": "service-database-wan-example-com-mysql-3306",
        "entryPoints": ["tcp"],
        "tls": {"passthrough": false}
      }
    },
    "services": {
      "service-database-wan-example-com-mysql-3306": {
        "loadBalancer": {
          "servers": [{"address": "100.64.0.1:3306"}]
        }
      }
    }
  }
}

Configuration Format

Traefik routes are configured via Tailscale ACL host entries using the following SRV record style format:

_<traefik_entrypoint>_<ts_scheme>_<ts_port>._<tcp/udp>.<domain>

Format Components

Component Description
<traefik_entrypoint> The name of a Traefik entrypoint defined in traefik.yml.template. Must match exactly with an entrypoint defined in your Traefik configuration. For HTTP routers (http/https schemes): Determines which Traefik entrypoint to use (e.g., web, websecure). For TCP routers (all other schemes): Determines which Traefik entrypoint to use (e.g., tcp, tcp-secure)
<ts_scheme> Backend URL scheme or protocol identifier. http or https: Creates HTTP routers, used to build the backend service URL scheme. All other schemes (e.g., tls, mysql, ssh, rdp): Creates TCP routers. If tls: Enables TLS passthrough (backend handles TLS). If anything else: Disables TLS passthrough (plain TCP connection)
<ts_port> Backend service port on the Tailscale machine
<tcp/udp> Transport protocol (only tcp is currently supported)
<domain> Domain name for routing

Entrypoints

The available Traefik entrypoints are defined in traefik.yml.template. By default, the template includes:

Entrypoint Port Description
web 80 (configurable via HTTP_PORT) HTTP entrypoint with redirect to HTTPS
websecure 443 (configurable via HTTPS_PORT) HTTPS entrypoint
tcp 4433 (configurable via TCP_PORT) TCP entrypoint

You can add custom entrypoints by modifying traefik.yml.template. The <traefik_entrypoint> in your ACL host entries must match an entrypoint name defined in your Traefik configuration.

Examples

Example Entrypoint Scheme Port Router Type Backend TLS Passthrough
_web_http_3000._tcp.server1.wan.example.com web (80) http 3000 HTTP http://100.64.0.1:3000 N/A
_websecure_https_3000._tcp.server1.wan.example.com websecure (443) https 3000 HTTP https://100.64.0.1:3000 N/A
_tcp_tls_443._tcp.secure-service.wan.example.com tcp (4433) tls 443 TCP 100.64.0.1:443 Yes
_tcp_mysql_3306._tcp.database.wan.example.com tcp (4433) mysql 3306 TCP 100.64.0.1:3306 No

Note: The IP address used for backend services is taken directly from the Tailscale ACL hosts configuration. The ACL host value is used as the IP address for Traefik service backends.

DNS Record Usage Ideas

The cloudflare-tailscale-dns service creates A records for each machine across three domains (TS, WAN, and LAN). The WAN domain records contain the public IP address (before router NAT), making them ideal for dyndns-style CNAME records without installing separate dyndns software.

Example: api.example.com → CNAME → server1.wan.example.com

This allows you to use custom domain names while leveraging the automatically managed WAN domain records. The WAN IPs typically point to Traefik, which then routes traffic to service backends based on ACL host entries.

Important Notes

Traefik Routing: WAN domain records point to Traefik (the public IP). Traefik then routes traffic to backend services using Tailscale IPs from ACL host entries. The machine running Traefik must have Tailscale running (tailscale up) to reach backend services via Tailscale IPs.

WAN vs Direct Backend: WAN IPs are not meant for direct service backend connections. They point to Traefik, which handles routing to the actual services based on domain names and ACL configuration.

Traffic Flow Diagram

sequenceDiagram
    participant CLIENT as Client
    participant DNS as Cloudflare DNS
    participant TRAEFIK as Traefik<br/>(WAN IP)
    participant TS as Tailscale Network
    participant BACKEND as Backend Service<br/>(Tailscale IP)
    
    Note over CLIENT,DNS: DNS Resolution Phase
    CLIENT->>DNS: 1. Query api.example.com
    DNS->>DNS: 2. CNAME → server1.wan.example.com
    DNS->>DNS: 3. A Record → WAN IP (public, before NAT)
    DNS-->>CLIENT: 4. Return WAN IP
    
    Note over CLIENT,BACKEND: Traffic Routing Phase
    alt HTTP/HTTPS Request
        CLIENT->>TRAEFIK: 5. HTTP/HTTPS to WAN IP<br/>Host: api.example.com
        TRAEFIK->>TRAEFIK: 6. Match domain to ACL entry<br/>_web_http_3000._tcp.server1.wan.example.com
        TRAEFIK->>TS: 7. Route via Tailscale IP<br/>100.x.y.z:3000
        TS->>BACKEND: 8. Forward to backend
        BACKEND-->>TS: 9. Response
        TS-->>TRAEFIK: 10. Response
        TRAEFIK-->>CLIENT: 11. Response
    else TCP Request (SSH/RDP)
        CLIENT->>TRAEFIK: 5. TCP to WAN IP:4433<br/>SNI: server1.wan.example.com
        TRAEFIK->>TRAEFIK: 6. Match SNI to ACL entry<br/>_tcp_ssh_22._tcp.server1.wan.example.com
        TRAEFIK->>TS: 7. Route via Tailscale IP<br/>100.x.y.z:22
        TS->>BACKEND: 8. Forward to backend
        BACKEND-->>TS: 9. Data
        TS-->>TRAEFIK: 10. Data
        TRAEFIK-->>CLIENT: 11. Data
    end
Loading

Flow Explanation:

DNS Resolution:

  1. Client queries api.example.com
  2. DNS resolves CNAME to server1.wan.example.com
  3. DNS resolves A record to WAN IP (public IP, before router NAT)
  4. Client receives WAN IP address

Traffic Routing: 5. Client sends HTTP/HTTPS/TCP traffic to Traefik at WAN IP 6. Traefik matches domain name (HTTP) or SNI (TCP) to ACL host entry 7. Traefik routes to backend service using Tailscale IP from ACL configuration 8. Traffic flows through Tailscale network to backend service 9-11. Response/data flows back through the same path

Using TCP Routers for SSH/RDP

Traefik TCP routers use SNI (Server Name Indication) to route traffic based on domain names. Protocols like SSH and RDP don't send SNI headers by default, so you need a local proxy/port forward that adds the SNI header.

The Problem: Traefik uses HostSNI() rules to match connections. SSH/RDP clients don't send SNI, so Traefik can't route them.

Solution: Use a local proxy that listens on a local port, adds SNI headers, and forwards to Traefik's TCP entrypoint.

Local Proxy Tools

Tool Platform Command Example
socat Linux/macOS socat TCP-LISTEN:2222,fork,reuseaddr OPENSSL:traefik-host:4433,verify=0,snihost=server1.wan.example.com
stunnel Linux/macOS/Windows Configure stunnel.conf with sni = server1.wan.example.com
HAProxy Linux Configure haproxy.cfg with sni str(server1.wan.example.com)
socat (WSL) Windows Same as Linux socat command

Example Workflow

  1. Add ACL entry: _tcp_ssh_22._tcp.server1.wan.example.com
  2. Start proxy: socat TCP-LISTEN:2222,fork,reuseaddr OPENSSL:traefik-host:4433,verify=0,snihost=server1.wan.example.com
  3. Connect: ssh -p 2222 user@localhost

Note: Replace traefik-host with your Traefik hostname/IP and use custom TCP port if configured via TCP_PORT.

Security Notes

Security Consideration Recommendation
API Dashboard The API dashboard is exposed on port 8080 in insecure mode by default. For production, configure authentication or disable the dashboard
SSE Endpoint The SSE configuration endpoint (TRAEFIK_CONFIG_API_ENDPOINT) from cloudflare-tailscale-dns should be properly secured (HTTPS, authentication if needed)
Reconnection The generate-config binary automatically reconnects if the SSE connection is lost, with configurable retry delays

License

MIT

About

Traefik client for Cloudflare Tailscale DNS

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors