Chute Devs

Hello Tailscale — Native Mesh VPN Without the Go Runtime

Hello everyone.

Today we are introducing something we have wanted for a long time: native Tailscale support inside the Chute engine, available on iOS, macOS, and tvOS alongside your existing proxy protocols.

This is not a plugin. It is not a second VPN running next to Chute. It is the full Tailscale protocol stack — control plane, relay, NAT traversal, and WireGuard data plane — implemented directly in the kernel, speaking the same configuration language as Shadowsocks, VMess, and Hysteria2.

Why Tailscale, and Why Inside the Engine

Tailscale builds a private mesh network over your devices. Each device gets a stable address, devices find each other directly when possible, and you can reach your home server, an office machine, or a self-hosted exit node from anywhere as if they were on the same LAN. It is one of the most useful networking tools of the last few years.

On iOS there is a hard problem, though. Apple gives each app exactly one VPN slot. The Tailscale app and any TUN-based proxy like Chute cannot run at the same time — they fight over the single Network Extension. Users have been stuck choosing: either a mesh VPN, or a proxy. Not both.

The only clean answer is to put Tailscale inside the proxy engine, so the single VPN slot serves both purposes. That is what we did.

What We Built

Chute’s Tailscale reaches the feature level of the official iOS client, without depending on the official client or shipping a Go runtime:

  • Auth-key registration. You join a tailnet with an auth key (tskey-auth-...), no browser login, no interactive OAuth. This is the mode the official client only exposes under MDM — we made it the default, because it is by far the easiest way to set up a node.
  • Custom control server. Point at the official controlplane.tailscale.com, or at your own Headscale / Ionscale server. Self-hosters are first-class.
  • Network map. The engine pulls your tailnet’s node list, addresses, peer routes, the DERP relay map, DNS config, and ACL packet filter — and keeps the map live as a streaming long-poll, so peers coming and going are reflected without a reconnect.
  • WireGuard data plane. Every peer gets a real WireGuard tunnel. This is the same WireGuard engine Chute already ships — now shared between standalone WireGuard policies and Tailscale peers.
  • DERP relay. When two peers cannot talk directly, encrypted packets relay through Tailscale’s DERP servers (or your own), so the mesh works even behind strict NAT.
  • NAT traversal. STUN endpoint discovery plus the disco signaling protocol upgrade relayed paths to direct UDP wherever the network allows. More on this in the next post.
  • Exit nodes. Route your whole device’s traffic through a tailnet peer that advertises as an exit node — a self-hosted, trust-no-one alternative to commercial VPN exits.
  • MagicDNS. Reach peers by name (myserver, laptop.tailnet-name.ts.net) instead of memorizing 100.x addresses. The engine injects peer names into its DNS layer so they resolve the same way whether your traffic enters via the TUN interface or the system proxy.

No Go Runtime

This is the part we are proudest of, and it is worth explaining why it matters.

The official Tailscale client is a large Go program. On iOS, Go inside a Network Extension is a constant memory fight — the process is capped at roughly 50 MB, and the official team has spent years tuning the garbage collector, the linker, and the goroutine count to keep large tailnets from getting killed by the OS. It is a known, long-standing pain point.

We took a different route: we re-implemented the protocol from scratch in the native language of the platform, sharing the memory and crypto primitives the engine already had. The result is a Tailscale stack that lives inside the same process budget as your proxy traffic, with no second runtime to feed.

Concretely, this means:

  • One process, one memory budget. Tailscale and your proxy share the kernel’s existing allocations — there is no second 50 MB Go heap sitting next to your VPN.
  • One configuration, one rule engine. A Tailscale peer is just another policy in your config. You can route a domain through a tailnet peer, through your Shadowsocks server, or direct — using the same rules you already write.
  • No cross-language boundary. The WireGuard data plane, the crypto, and the control plane all speak the same code, so debugging is native debugging and crashes are native crashes.

Memory-Constrained From Day One

Because we knew the iOS memory ceiling, we built the Tailscale stack against an explicit, per-protocol memory policy from the start. Every queue, every peer packet buffer, every pending record has a bounded budget that scales down on iOS and tvOS and stays generous on macOS. When the device is under memory pressure, the engine degrades gracefully — it does not get jetsamed mid-connection.

This is the difference between “it runs on iOS” and “it survives on iOS.” Long tailnets, unstable networks, and background sessions are exactly where naive ports fall over.

Using It

Configuration follows the same shape as every other protocol in Chute. You give Tailscale its own section in your config, point it at your control server, drop in an auth key, and it behaves like any other policy source:

1
2
3
4
[Tailscale]
control-url = https://controlplane.tailscale.com
# or your own: https://headscale.example.net
auth-key = tskey-auth-xxxxxxxxxxxxxxxx

Once joined, your tailnet peers appear as first-class routing targets. A rule can send traffic to a peer by name, by tailnet address, or through an exit node — and you can hot-swap the exit node at runtime without a config reload, with existing connections keeping their original path and new flows picking up the change.

We also surfaced live status — node state, current exit node, peer reachability — to the host app and the web dashboard, so you can see your mesh the way you see your proxies.

A Note on Scope

This first release focuses on the client experience: joining a tailnet, reaching peers, using exit nodes, and MagicDNS. A few advanced Tailscale features are deliberately out of scope for now — interactive SSO browser login, Tailscale SSH, Taildrop, Funnel/serve, and acting as a subnet router or exit node server. If you depend on one of those, the official client remains the better tool for that specific job.

Tailscale also makes trade-offs visible to the user. On some cross-border links, continuous UDP gets throttled by carrier QoS — and a “successful” direct path can actually be slower than the DERP relay. We surface a force-derp knob for exactly that case, and the next post digs into why.

What This Means

For a long time the rule on iOS was: pick one, mesh or proxy. That rule is now suspended. With Tailscale in the kernel, your tailnet and your proxy stack are the same process, the same config, and the same rule engine — and they coexist in the single VPN slot Apple gives you.

Tailscale support is rolling out now on iOS, macOS, and tvOS. Check the Chute Manual for the full configuration reference, and if you run a Headscale server, give it a try — we would love to hear how it holds up against your setup.

Thanks.

Chute Devs