Documentation

Behind a reverse proxy

Let a local reverse proxy (Traefik, nginx, …) reach tailnet backends through TailMux — no system-wide Tailscale, no kernel route.

View as Markdown

The problem#

A reverse proxy forwards each request by dialing a network address; it does not speak a forward-proxy protocol for its upstreams. So pointing it straight at a tailnet host like api.work.ts.net only works when the machine already has a route into the tailnet — e.g. the official Tailscale client running. TailMux stays at the proxy layer and creates no such route, so a naïve upstream just fails to connect.

How it works#

TailMux's router on 127.0.0.1:43100 accepts ordinary HTTP requests and routes them by their Host header (port included) to the profile that owns the suffix. That makes it usable as a reverse-proxy backend: point the upstream at the TailMux loopback and set the request Host to the real tailnet target.

request flow
1reverse proxy2   |  upstream = 127.0.0.1:431003   |  Host: api.work.ts.net:80804   v5TailMux router  127.0.0.1:431006   |  hostname suffix match -> work profile7   v8api.work.ts.net:8080   (resolved + dialed inside the tailnet)
From a Docker container, reach the host loopback as host.docker.internal:43100 (Docker Desktop and OrbStack provide it). TailMux resolves the tailnet hostname and routes it, so the container needs no tailnet DNS of its own.

Traefik#

Point the service at the loopback and add a headers middleware that forces the upstream Host:

dynamic config (yaml)
1http:2  services:3    api:4      loadBalancer:5        servers:6          - url: http://host.docker.internal:431007  middlewares:8    api-host:9      headers:10        customRequestHeaders:11          Host: "api.work.ts.net:8080"

Attach the api-host middleware to the router that uses the api service. To generate this from a template, keep one variable for the upstream and one for the Host, so the same config switches between a direct route and the TailMux loopback.

nginx#

nginx.conf
1location /api/ {2    proxy_pass http://127.0.0.1:43100;3    proxy_set_header Host api.work.ts.net:8080;4}

Notes#

  • The profile that owns the suffix must list it under suffixes, and — when the backend sits behind a tailnet subnet router — set accept_routes: true.
  • The profile must be logged in for the dial to succeed.
  • This routes plain-HTTP upstreams by Host. For TLS-to-backend or raw TCP (databases, SSH) use tailmux connect / tunnel instead.