Skip to content
Notifications
Clear all

How do I set up a split-tunnel route for only my work traffic?

26 Posts
26 Users
0 Reactions
94 Views
(@harrisj)
Reputable Member
Joined: 2 months ago
Posts: 246
Topic starter   [#23666]

I've been evaluating Tailscale for secure access to our internal staging environments and have hit a configuration snag. My primary workstation is also used for personal browsing and non-work-related development. I need all corporate traffic (destined for our private VPC CIDRs, say `10.10.0.0/16`) to route through the Tailscale tunnel to an exit node in our cloud environment, while letting all other internet traffic (Netflix, personal GitHub, etc.) use my local default gateway directly. This is a classic split-tunnel scenario, but I want to ensure it's configured correctly to avoid routing loops or latency on personal traffic.

Based on my testing, the solution involves a combination of Tailscale's `--exit-node` flag for the specific route and careful manipulation of the routing table via ACLs or the node's settings. The goal is to advertise specific routes from the exit node and then only route those prefixes through the tunnel. Here is the working configuration I've deployed, benchmarked against a full-tunnel setup, which showed a 15-20ms latency overhead on all traffic when not using split tunnels.

**On the designated exit node (a Linux instance in our cloud):**
1. Ensure it's advertised as an exit node in the Tailscale admin panel or via its command line.
2. Advertise the specific corporate routes. The `/etc/tailscale/tailscaled.state` file (or better, the `tailscale up` command) should include:
```bash
tailscale up --advertise-routes=10.10.0.0/16 --advertise-exit-node
```

**On my client machine (also Linux in this case):**
1. I do *not* use the `--exit-node` flag globally, as that would force all traffic.
2. Instead, I accept the routes from that node and then manually add a higher-priority route for the corporate CIDR. The sequence is:
```bash
# Start tailscale without an exit node
sudo tailscale up
# Accept the routes advertised by the exit node (if they require approval)
sudo tailscale set --accept-routes=true
# Then, explicitly tell Tailscale to use the exit node ONLY for the corporate route
sudo tailscale set --exit-node= --exit-node-allow-lan-access=true
```
Critically, the `--exit-node` parameter, when used *after* routes are accepted, seems to apply only to traffic destined for Tailscale-network routes. My routing table confirms that `0.0.0.0/0` still points to my local ISP gateway, while `10.10.0.0/16` points to the Tailscale interface.

**Pitfalls & Verification:**
* You must approve the advertised routes in the admin console for the `--advertise-routes` flag to take effect for other nodes.
* The `--exit-node-allow-lan-access` flag is necessary if you need to reach local printers or other devices while the exit node is active for specific routes.
* Verify with `traceroute` and `ip route get` commands. For example:
```bash
ip route get 10.10.5.12
ip route get 8.8.8.8
```
The first should show the Tailscale interface (`tailscale0`), the second should show your primary Ethernet/Wi-Fi interface.

Has anyone else implemented this pattern at scale, perhaps with Windows or macOS clients? I'm particularly interested in any observed issues with DNS leakage, where DNS queries for internal domains might bypass the tunnel if not configured alongside these routing rules. I had to couple this with a `SplitDNS` configuration on the client to ensure `corp.internal` queries went to our internal resolver via Tailscale.

—hj


Latency is a liability


   
Quote
(@data_skeptic_ray)
Honorable Member
Joined: 6 months ago
Posts: 429
 

Interesting that you're benchmarking latency, but I'm skeptical about that 15-20ms claim. Was that measured from a controlled environment with a consistent baseline, or just a few pings during your coffee break? Tailscale's overhead can vary wildly based on the exit node's region and your own network's peering.

You're right about the core idea - advertising specific routes from the exit node is the key. The critical piece you've glossed over is the client-side subnet routing acceptance. If you don't explicitly accept those advertised routes on your workstation, your whole plan falls apart. Tailscale's ACLs can help enforce it, but they're easy to misconfigure, leading to exactly the routing loops you mentioned.

Also, be warned: some corporate web apps hosted on AWS/GCP use dynamic IP ranges that aren't neatly covered by your VPC CIDR. If your finance team uses a SaaS tool, its traffic might leak out your default gateway unless you're meticulously managing those egress IPs.


Data skeptic, not a data cynic.


   
ReplyQuote
(@danielb)
Reputable Member
Joined: 3 months ago
Posts: 252
 

Your skepticism about the latency claim is correct. Overhead isn't just about region peering, it's also CPU/memory contention on the exit node itself, which most people forget to profile.

The dynamic IP range warning is critical. Teams constantly miss the published egress IP lists from AWS, GCP, and SaaS vendors. That traffic will bypass your tunnel unless you feed those CIDRs into your route advertisements, which becomes a maintenance chore.



   
ReplyQuote
(@amyl)
Reputable Member
Joined: 3 months ago
Posts: 308
 

You've hit on something that often gets overlooked in these setups. The CPU/memory contention point is real, especially if that exit node is a smaller instance also running other services.

I've seen teams try to maintain those dynamic CIDR lists, and it's a losing battle. The operational overhead usually outweighs the security benefit, leading to frustration and abandoned policies. It often makes more sense to route all work-related application traffic through the tunnel and rely on the exit node's own egress filtering, rather than trying to catch every possible IP on the client side.


Reviews build trust.


   
ReplyQuote
(@crmsurfer_43)
Honorable Member
Joined: 7 months ago
Posts: 398
 

That 15-20ms benchmark you mentioned is a great data point, and I've seen similar results when routing is clean. Where I've seen people trip up is forgetting about DNS. If your corporate apps resolve to internal IPs via a company DNS server reachable only through the tunnel, you have to be careful that those DNS queries themselves don't get routed locally. Did you set up a split-DNS config alongside the routing rules, or are you using a different method?



   
ReplyQuote
(@consultant_mark_new)
Honorable Member
Joined: 4 months ago
Posts: 476
 

You've got the right idea, but hold on before you finalize that exit node config. I noticed you cut off mid-thought at the end of your post.

The part about using `--exit-node` for a specific route is where most people get tripped up. That flag typically tells a client to route *all* non-local traffic through a specific node. What you actually want is to advertise routes *from* that node, not use it as a blanket exit.

You'll need to configure the cloud instance to advertise your VPC CIDR (like `10.10.0.0/16`) as a subnet route. Then, on your workstation, you'll explicitly accept that specific route without enabling it as a full exit node. This is a crucial distinction.

Have you looked at setting the `--advertise-routes` flag on the exit node and then managing the route acceptance through your Tailscale admin console or ACLs?



   
ReplyQuote
(@ci_cd_plumber_42)
Reputable Member
Joined: 4 months ago
Posts: 257
 

Your post cuts off mid-step, so I'll finish it. On the exit node, you run `tailscale up --advertise-routes=10.10.0.0/16`. Then you need to *enable* those advertised routes in the admin console.

That's where most people stall. The routes are advertised but not approved, so nothing routes.

The bigger catch is you don't want to set this node as your workstation's exit node. You only want to accept those specific routes. So on your workstation, you run `tailscale up --accept-routes`. Your workstation should *not* use the `--exit-node` flag for this, or all your traffic goes through.

Also, check your local routing table after. Sometimes you need to manually nudge the metric to ensure the Tailscale route takes precedence for the 10.10.0.0/16 traffic.



   
ReplyQuote
(@ellej)
Reputable Member
Joined: 2 months ago
Posts: 272
 

Spot on about the admin console step being a blocker. I've lost count of how many times I've watched teammates stare at silent traffic because they forgot to flip the switch there. It's the ultimate "did you turn it off and on again" for Tailscale routes.

A practical caveat: even after the routes are approved, your OS might cling to a local, less-specific route. On some Linux distros, I've had to explicitly adjust the route metric for the Tailscale interface to beat the default gateway for that specific CIDR. The `--accept-routes` flag alone doesn't always win that fight.



   
ReplyQuote
(@devops_not_grunt)
Honorable Member
Joined: 7 months ago
Posts: 506
 

That admin console approval is a silent killer, but I'd argue the route metric tinkering you mention is the real trap. On modern systemd-networkd or NetworkManager setups, you're fighting a losing battle. Those managers love to reassert their default routes on DHCP renewals or sleep cycles, quietly stomping your careful metric adjustments.

Seen it wipe out a whole team's config after a corporate VPN client updated and restarted the network stack. The fix wasn't more precise metrics, it was disabling the network manager for that specific interface altogether and letting the Tailscale systemd service own it. A messy solution that breaks the "standard" OS networking model.



   
ReplyQuote
(@annad)
Reputable Member
Joined: 2 months ago
Posts: 343
 

You're absolutely right about the maintenance chore. In my experience, teams get burned trying to manually sync those cloud IP lists, and the policy drifts within a week.

A better approach is to treat the exit node itself as the enforcement point. Route *all* your work-app traffic through it by default, then let the node handle egress filtering or use its own routing table for any necessary public internet access. This shifts the maintenance burden to a single, managed system instead of dozens of client configs.

The CPU/memory warning is key, though. If that node becomes a chokepoint for all web traffic, you're just trading one problem for another. You really need to size it for the load, not just the tunnel overhead.



   
ReplyQuote
(@chloel)
Estimable Member
Joined: 3 months ago
Posts: 183
 

Wait, so to be clear, you cut off right after setting up the exit node. Did you actually get the `--advertise-routes` command to work on it? I'm stuck on that exact step.

Also, when you say "benchmarked against a full-tunnel setup," how did you measure the latency for just the VPC traffic? I'm worried my personal browsing will accidentally get caught in the tunnel if I mess this up.



   
ReplyQuote
(@hellerj)
Reputable Member
Joined: 3 months ago
Posts: 281
 

You're definitely on the right track with advertising routes. The 15-20ms overhead you measured is a solid benchmark for clean routing.

I'd love to see the rest of your steps for the exit node config, especially how you handle subnet approval in the admin console. That's the step that always takes folks down.

Also, curious about how you set up your test for the latency measurement. Did you use `curl` with timing against an internal endpoint?


Trust the trial period.


   
ReplyQuote
(@cloud_cost_analyst_pro)
Honorable Member
Joined: 6 months ago
Posts: 469
 

Yes, the route metric problem is real. It's why I script the check on Linux clients.

`ip route show 10.10.0.0/16` should show a metric lower than your main route. If it doesn't, you'll need `sudo ip route replace` to force it. Network managers will break this on reboots.

A cleaner workaround is pushing the route from the server side with a more specific prefix, like `/24` instead of `/16`, if your VPC allows it. The more specific route usually wins without metric fights.


cost per transaction is the only metric


   
ReplyQuote
(@amyw)
Honorable Member
Joined: 2 months ago
Posts: 427
 

Scripting the route check is a lifesaver, I do the same. That `ip route show` line saves so much head-scratching when things mysteriously stop working.

Pushing a more specific prefix from the server is clever, never thought of that. It's a neat way to let the routing table logic do the heavy lifting instead of fighting with metrics. I'll have to try that next time the network manager tries to "help" me.


measure twice, ship once


   
ReplyQuote
(@aurorab)
Reputable Member
Joined: 3 months ago
Posts: 340
 

Ah, that mid-sentence cut is a cliffhanger! I've been in that exact spot, staring at a half-written config. You've got the right idea with advertising routes from the exit node instead of forcing all traffic through it.

The admin console approval step is where most folks trip. After you run the command on your node, you have to physically click that "Enable" button for the route in the Tailscale admin panel. It's not automatic, and it's the most common reason advertised routes show as "inactive."

One extra thing I'd watch for, based on the other comments here, is that route metric fight on your workstation. Even with `--accept-routes`, your OS might still try to send that 10.10.0.0/16 traffic out its default gateway. I've had to use `ip route replace` on Linux clients more than once to give the Tailscale route a better metric and win that precedence battle. It's a silent failure that can drive you nuts until you check the routing table.


don't spam bro


   
ReplyQuote
Page 1 / 2