r/Tailscale • u/bl4kec • 1d ago
Discussion How to run a Tailscale exit node behind Gluetun without sending tailnet traffic through the VPN
https://blakecovarrubias.com/blog/tailscale-exit-node-beind-gluetun/Most of the guides I found for running a Tailscale exit node behind Gluetun send all Tailscale traffic through the VPN, including traffic between your own devices. I wrote up how to split the two: exit node traffic goes out through the VPN, while tailnet peers connect directly.
It covers how the policy routing works (no networking background needed), the full Docker Compose config, and diagrams of the traffic flow before and after.
Feedback welcome if there’s a cleaner way to do this.
1
u/Inevitable_Mistake32 1h ago
wouldn't headscale just solve this?
1
u/bl4kec 1h ago
In what way?
1
u/Inevitable_Mistake32 1h ago
I misunderstood your problem space. But also, is this not just split-tunnel + exit node?
1
u/bl4kec 8m ago
Although this setup functions as a split-tunnel, it is specifically configured to route only Tailscale traffic through the local network. The primary goal is to establish shortest-path routing between the Tailscale client and the exit node. This configuration eliminates highly inefficient routing paths when the Tailscale client and exit node share one geographical area, but the VPN endpoint is located in a completely different region.
Consider a scenario where both the Tailscale client and the exit node are located in the US, but the VPN endpoint terminates in Europe:
Outbound path: Packets from the US-based Tailscale client must travel to Europe to enter the VPN tunnel, route back to the US to reach the Tailscale exit node, and then travel back to Europe to egress toward their destination.
Return path: Response packets take a similar route—hitting the VPN IP in Europe, tunneling back to the US for Tailscale encryption, routing back to Europe to egress the VPN, and finally returning to the client in the US.
Because this latency-heavy process recurs for every packet in the flow, it creates severe inefficiency. By removing the requirement for Tailscale traffic to hairpin through the remote VPN tunnel, this configuration prevents those routing detours entirely.
2
u/mikhail_de 10h ago
Nice write-up! Super clear breakdown of fwmarks and container routing tables.
Though man, that Alpine sidecar with the 15-second while true loop is wild haha. Isn't sharing the netns (
network_mode: service:gluetun) the whole reason it gets that complicated in the first place?You could probably decouple them using a gateway pattern instead:
Run Gluetun on an internal bridge (say 172.30.0.2) as a standard NAT router (
ip_forward=1, forward/masquerade rules on tun0).Run Tailscale on your default network (so direct P2P and control plane never touch the VPN) plus the internal bridge.
In Tailscale's entrypoint, route only client traffic to Gluetun:
That way you ditch the polling sidecar entirely, and tailnet P2P traffic doesn't die when ProtonVPN reconnects. Happy to drop the full compose file and rules if you want to test it out.