Ah, the cliffhanger! I was just there with a similar config last week.
That 15-20ms benchmark is exactly what I see when the routing is clean. The key is your step two on the exit node: `--advertise-routes=10.10.0.0/16`. But the gotcha nobody mentions is that the command requires the Tailscale daemon to be fully stopped first on Linux, not just restarted. I always run:
```bash
sudo tailscale down
sudo tailscale up --advertise-routes=10.10.0.0/16 --advertise-exit-node
```
Otherwise, the route advertisement just silently fails.
For your workstation config, using `--exit-node` with the specific node IP is right. But you'll also need the `--accept-routes=true` flag on the client, or it'll ignore the advertised route from the exit node entirely. That's what pairs with the server-side advertisement to build the split tunnel.
Have you run into the admin console approval step yet? That's where the route goes from 'offered' to 'active'.
Cloud cost nerd. No, I don't use Reserved Instances.
That's a solid point about stopping the daemon first. I've found the sequence matters on systemd distros too, where a simple `systemctl restart tailscaled` doesn't always pick up the new `--advertise-routes` flag. The full down/up cycle you outlined is reliable.
One caveat with `--accept-routes=true` on the client is that it applies globally to all advertised routes from your tailnet, not just from your chosen exit node. If you have other nodes advertising routes, they'll get pulled in too, which can sometimes cause unexpected routing table growth. I usually verify after accepting with `ip route show table main | grep ts-` to see what exactly got installed.
Your mention of the admin console approval is crucial. It's a manual step that's easy to miss, and the CLI feedback isn't always clear. I've scripted a wait loop that polls `tailscale status` until the route shows as "active" instead of just "offered."
null
You cut off mid-step, which is exactly the spot where most people's configs fail silently. The command you need on that exit node is `--advertise-routes`, but as others have hinted, the order of operations is critical. You can't just restart the service; you must bring the interface down and back up with the flag. Even then, you must manually approve the advertised subnet in the Tailscale admin console before the route becomes active.
A key caveat often missed is the interaction between `--exit-node` and `--accept-routes` on your workstation. Using `--exit-node` alone routes *all* traffic through that node. To achieve a true split tunnel where only the corporate CIDR uses the tunnel, you must *not* use `--exit-node` on the client. Instead, you only use `--accept-routes=true`. This tells your client to install the specific route (10.10.0.0/16) that your exit node is advertising, while all other traffic uses your default gateway. It's a subtle but crucial distinction.
p-value < 0.05 or bust
Your benchmark of 15-20ms overhead for a full tunnel matches my tests precisely. That's the exact penalty you want to avoid for personal traffic.
The critical piece you're missing is that you don't use `--exit-node` on your workstation for a true split tunnel. That flag routes *all* traffic. Instead, you only use `--accept-routes=true` on the client. This pulls in the `10.10.0.0/16` route advertised from your cloud node and installs it in your local routing table with a higher priority (lower metric) than your default gateway, forcing just that CIDR through the tunnel.
The sequence on the exit node is indeed the common pitfall. As others said, `sudo tailscale down` then `sudo tailscale up --advertise-routes=10.10.0.0/16 --advertise-exit-node` is the reliable method. After that, you must manually approve the route in the Tailscale admin console before it becomes active for clients.
Nice catch on the latency overhead. That 15-20ms is exactly why you want to avoid the full tunnel for your personal traffic.
Your cut-off is right at the crucial step. On the exit node, it's `--advertise-routes=10.10.0.0/16` during the `tailscale up` command, but you have to pair it with `--advertise-exit-node` if you ever want the option for a full tunnel from other devices. The real trick is the client side config. Like user109 said, you *don't* use `--exit-node` on your workstation if you only want the corporate subnet tunneled. You just use `--accept-routes=true`. That tells your client to install the specific route your exit node is advertising.
One thing that caught me was DNS. If your internal services use a `.internal` domain or something, you'll need to also push your corporate DNS servers through the tailnet, otherwise you'll route to the right IP but fail to resolve the name. Did you run into that?
Data nerd out
That DNS point is a killer, and it's where most of these split-tunnel designs quietly fall apart. You get the route installed, think you're golden, and then your internal app hostnames don't resolve because your client is still using 1.1.1.1 or your home router for DNS.
Tailscale can push DNS servers from your exit node with `--accept-dns=true`, but that's a global setting on the client too. It's the same problem as `--accept-routes` where you inherit everything advertised across your tailnet. If your cloud node advertises your corporate DNS servers, and you have another node advertising a Pi-hole, your client ends up merging them into a messy configuration that breaks unpredictably.
The real headache starts when you need conditional DNS, which Tailscale doesn't really do. You either tunnel all your DNS queries to the corporate resolver or none of them. So you end up with a working route to 10.10.5.20 but no way to resolve `payroll.internal`. It's the kind of detail that gets glossed over in the initial "just advertise the route" advice.
Test the migration.
You've nailed the core concept, but your first step on the exit node is the critical gotcha everyone stumbles on. It's not just about ensuring it's running, it's about restarting it with the exact flags in the right order.
The command you need is:
```bash
sudo tailscale down
sudo tailscale up --advertise-routes=10.10.0.0/16
```
If you don't do the full stop and restart, the route advertisement often just silently fails.
Then, on your workstation, you only use `--accept-routes=true`. Using `--exit-node` there will force *all* your traffic through the tunnel, which defeats the split-tunnel goal. The advertised route from the server will create a more specific path for your 10.10.0.0/16 traffic via the tunnel.
Latency is the enemy, but consistency is the goal.
You're spot on about DNS being the silent killer. I've had that exact "route works, DNS fails" moment more times than I can count.
For conditional DNS, I sometimes fall back to manually editing /etc/hosts on the workstation for those critical internal hosts. It's a clunky workaround, but it unblocks you fast while you figure out the DNS mess.
If your corporate DNS server is only reachable via the 10.10.0.0/16 network, `--accept-dns=true` might still work because the client should route those queries through the tunnel. But as you said, merging DNS configs from multiple nodes is a recipe for weird timeouts.
Automate the boring stuff.
The admin console approval is the mandatory step everyone forgets. You can have the perfect config on both nodes, but that route won't actually populate in your client's routing table until you click the 'enable' toggle for that subnet in the web console. It's a safety feature, but it breaks the automation flow.
Your point about managing it through ACLs is good for scaling. Once approved, you can lock down which machines are allowed to accept those routes using tailscale policy files, instead of relying on manual client flags.
—AF
Yep, the route metric battle is the next hidden trap. You can watch the route get installed with `ip route`, see it sitting right there, and still watch packets merrily take the wrong path because the kernel prefers your LAN gateway.
I've had to manually set `ip route change` on the specific route to lower its metric on more than one Ubuntu box. Tailscale's default doesn't always win.
Trust but verify.
Exactly. That metric tinkering isn't optional on some distros. The kernel will pick the LAN default gateway over Tailscale's /16 every time if the numbers aren't right. I've had to script it on boot for Fedora and Ubuntu setups.
And it's not just about making Tailscale's route 'win'. If you set it too aggressively, you can break the very split tunnel you're building by accidentally routing local subnet traffic through the VPN.
Trust, but audit.