Skip to content

Instantly share code, notes, and snippets.

@4piu
Created June 13, 2025 08:09
Show Gist options
  • Select an option

  • Save 4piu/e08f27ef0032b1a72bdbd063c084929b to your computer and use it in GitHub Desktop.

Select an option

Save 4piu/e08f27ef0032b1a72bdbd063c084929b to your computer and use it in GitHub Desktop.
Route Tailscale exit node traffic to Wireguard

This script enables you to forward all traffic from a Tailscale exit node through a WireGuard tunnel.

Most mobile devices support only one active VPN connection at a time. As a result, users can't simultaneously use Tailscale for connectivity and route their internet traffic through another VPN. This script provides a workaround by turning a Tailscale exit node into a VPN bridge. When clients use this special exit node, their internet traffic is protected by the VPN while still maintaining Tailscale connectivity.

Requirements:

  • Enable forwarding
  • Tailscale need to run with --netfilter-mode=[off|nodivert] for manual routing control
  • Set Table = off in Wireguard config to manual control ip rules
  • Set PostUp and PreDown script in Wireguard config

Example Wireguard Config:

[Interface]
Address = 10.200.200.3/32
PrivateKey = [Client's private key]
DNS = 8.8.8.8
Table = off
PostUp = /usr/local/bin/post-up.sh %i
PreDown = /usr/local/bin/pre-down.sh

[Peer]
PublicKey = [Server's public key]
PresharedKey = [Pre-shared key, same for server and client]
Endpoint = [Server Addr:Server Port]
AllowedIPs = 0.0.0.0/0
#!/usr/bin/env bash
set -e
WG_IFACE=$1
ROUTE_TABLE=39
if [ -z "$WG_IFACE" ]; then
echo "Usage: $0 <wg_interface>"
exit 1
fi
echo "[+] Adding nftables rules..."
nft -f - <<EOF
table inet ts-warp {
chain prerouting {
type filter hook prerouting priority mangle; policy accept;
iifname "tailscale0" counter packets 0 bytes 0 meta mark set mark and 0xff00ffff or 0x0040000
}
chain input {
type filter hook input priority filter; policy accept;
iifname != "tailscale0" ip saddr 100.115.92.0/23 counter packets 0 bytes 0 return
iifname != "tailscale0" ip saddr 100.64.0.0/10 counter packets 0 bytes 0 drop
iifname "tailscale0" counter packets 0 bytes 0 accept
}
chain forward {
type filter hook forward priority filter; policy accept;
oifname "tailscale0" ip saddr 100.64.0.0/10 counter packets 0 bytes 0 drop
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
meta mark & 0x00ff0000 == 0x00040000 counter packets 0 bytes 0 masquerade
}
}
EOF
echo "[+] Adding routing rule for marked packets..."
ip route add default dev "$WG_IFACE" table $ROUTE_TABLE || true
ip -6 route add default dev "$WG_IFACE" table $ROUTE_TABLE || true
ip rule add fwmark 0x40000/0xff0000 lookup $ROUTE_TABLE || true
ip -6 rule add fwmark 0x40000/0xff0000 lookup $ROUTE_TABLE || true
# echo 1 > /proc/sys/net/ipv4/ip_forward
# echo 1 > /proc/sys/net/ipv6/conf/all/forwarding
# echo 0 > /proc/sys/net/ipv4/conf/warp0/rp_filter
#!/usr/bin/env bash
set -e
WG_IFACE=$1
ROUTE_TABLE=39
if [ -z "$WG_IFACE" ]; then
echo "Usage: $0 <wg_interface>"
exit 1
fi
echo "[-] Deleting nftables rules..."
nft delete table inet ts-warp || true
echo "[-] Removing routing rules..."
ip rule del fwmark 0x40000/0xff0000 lookup $ROUTE_TABLE || true
ip -6 rule del fwmark 0x40000/0xff0000 lookup $ROUTE_TABLE || true
ip route flush table $ROUTE_TABLE || true
ip -6 route flush table $ROUTE_TABLE || true
@DarthKilroy
Copy link

Thank you so much for this! I spent so long trying to figure this out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment