Skip to main content
Paul Welty, PhD AI, WORK, AND STAYING HUMAN

· technology · 17 min read

How I made Tailscale and NordVPN coexist: the Linux exit node pattern

A practical pattern for running Tailscale and NordVPN together, plus the larger question it exposed: private networks need explicit exit profiles, not one vague VPN toggle.

I spent about a day trying to make Tailscale and NordVPN live together without knocking over remote access to my own machines. It felt like one of those problems that should have a tidy answer. Both products are good. Both are common. Both are doing normal VPN things. Surely there is a blessed checkbox somewhere.

There was not a blessed checkbox.

The breakthrough was realizing I was asking the wrong machine to do the wrong job. Tailscale and NordVPN should not both be first-class VPN clients on every laptop. Tailscale should get me to a trusted machine. NordVPN should get that machine to the internet.

The final shape is simple:

Laptop / phone / remote machine
        |
        v
Tailscale mesh
        |
        v
Always-on Linux VM
        |
        v
NordVPN
        |
        v
Internet

In my case the always-on Linux VM runs on a small home server. The result is that my laptop can select that VM as its Tailscale exit node, and public internet traffic exits through NordVPN from there.

It took a while to get to that boring diagram.

I have anonymized the machine names, MagicDNS hostnames, and exact tailnet IPs in the examples below. Placeholders like <EXIT_NODE_TAILSCALE_IP> mean “use your own value.” The 100.64.0.0/10 and fd7a:115c:a1e0::/48 ranges are Tailscale ranges, not specific machines on my network.

The easy way, which I did not take

If you are starting from zero, the sensible answer may be: use Mullvad with Tailscale.

Tailscale has a documented Mullvad exit-node integration. That is the clean path. Pay for Mullvad through Tailscale, select a Mullvad exit node, move on with your life.

I was not starting from zero. I already had NordVPN. I already had Tailscale. I already had a small fleet of old machines, each with its own little job and personality. I also had enough stubbornness to spend a day debugging something that, from a certain angle, I could have solved with a credit card and a fresh VPN subscription.

This article is for that situation: you already have a mesh VPN, you already have a privacy VPN, and you want the architecture to make sense without making every client machine host the fight.

Why this still feels like a missing product

This is the part that still bothers me.

Tailscale already solves the private-network part. It lets my machines reach one another from wherever I am, without making one gateway the center of the whole network.

Add a home exit node and it gives me something else: residential egress from a network I actually own.

The missing piece is choice over public identity.

Give me one boring place to say:

These are my private machines.
This is how they reach each other.
For this work, public traffic should exit here.
For that work, public traffic should exit somewhere else.

The consumer products are split across those promises.

Tailscale solved internal reachability. A residential exit solves “look like me at home.” NordVPN or Mullvad solve privacy-provider egress. A VPS can solve stable server-ish egress. The hard part is choosing among those exits without turning every laptop into a routing experiment.

There are products near this. Tailscale plus Mullvad is the cleanest privacy-exit version if you are willing to use Mullvad. Tailscale exit nodes can already give you home residential exits. Router and firewall appliances can do parts of it too. But the exact thing I wanted was not a single product: a personal mesh, explicit exit profiles, commercial privacy egress when I want it, residential egress when that is the safer identity, and enough control that I can SSH in when the GUI falls over.

So I composed it.

The problem with two VPNs on one machine

Tailscale and NordVPN want different kinds of control.

Tailscale wants to keep a private mesh alive. It wants stable peer discovery, WireGuard tunnels, DERP fallback, MagicDNS, exit-node routing, and enough control-plane connectivity to keep the network map fresh.

NordVPN wants to protect public egress. It wants to own default routes, DNS, firewall rules, leak protection, and sometimes a kill switch. Those are reasonable things for a privacy VPN to want.

The problem is that those desires overlap. On a single client, they can produce strange behavior:

  • VNC or Screen Sharing drops.
  • SSH survives while the admin UI says the machine is offline.
  • Tailscale peer traffic works while the coordination server goes stale.
  • LAN routes get confused by subnet advertisements.
  • The VPN app insists nothing is wrong because, from its point of view, it is doing its job.

That last part matters. NordVPN was not “broken.” Tailscale was not “broken.” They were both trying to be the network authority.

The false start: putting everything on the always-on Mac

My first instinct was to use an always-on desktop machine. It was already in my tailnet. It already supported VNC and SSH. It seemed like the obvious place to advertise a Tailscale exit node and run NordVPN.

That was the wrong host.

The NordVPN macOS app kept disrupting remote access. Connecting Nord could kill the VNC path. Killing Nord could kill the VNC path. Sometimes I could connect from one Mac but not another. Sometimes I could SSH but not use Screen Sharing. At one point I was VNC’ing into one Mac, then VNC’ing from there into another Mac, over Tailscale, just to have enough hands on the keyboard.

That was the first lesson: do not debug this from a GUI remote desktop session you are about to break.

The second lesson was more important: macOS was the wrong layer for the job. I wanted a boring routing appliance, not an interactive desktop with a commercial VPN app trying to be helpful.

Linux is better for this.

The pattern: separate access from egress

The stable architecture is:

Tailscale handles private access.
NordVPN handles public egress.
Linux sits between them.

Instead of asking every client to run Tailscale and NordVPN at the same time, run Tailscale everywhere and NordVPN only on one Linux exit node.

That gives each system a cleaner job:

  • Tailscale gets me from my laptop to the Linux VM.
  • The Linux VM advertises itself as an exit node.
  • NordVPN runs on the Linux VM.
  • My client routes internet-bound traffic through that VM.
  • The VM sends that traffic out through NordVPN.

The client does not need NordVPN at all.

The working Tailscale setup

On the Linux VM, enable IP forwarding. Tailscale’s exit-node setup documentation covers this, and newer Tailscale installs often help configure it, but I still like checking it explicitly:

sysctl net.ipv4.ip_forward
sysctl net.ipv6.conf.all.forwarding

Then advertise the machine as an exit node:

sudo tailscale up --advertise-exit-node --accept-routes=false

The --accept-routes=false detail mattered in my network. I had other machines advertising subnet routes, and one of those routes made a Mac route LAN replies through the wrong Tailscale path. For an exit node whose job is to be a gateway, I want it advertising the exit route, not also accepting every random route in the tailnet.

After running that command, approve the exit node in the Tailscale admin console:

Machines -> linux-exit -> Edit route settings -> Use as exit node

From a client, select the exit node:

tailscale set --exit-node=<EXIT_NODE_TAILSCALE_IP> --exit-node-allow-lan-access=true

Use the Tailscale IP or MagicDNS hostname for your own machine:

<EXIT_NODE_TAILSCALE_IP>
linux-exit.your-tailnet.ts.net

Then check your public IP:

curl -4 https://ifconfig.me

At this point, without NordVPN connected on the exit node, the public IP should be whatever your Linux VM normally uses. The goal is to make that IP become Nord’s egress IP once Nord connects.

The NordVPN setup that actually worked

On my Linux VM, NordVPN was installed through Snap, not apt. That cost me some time because I initially tried to update the wrong package system.

The useful commands were:

nordvpn disconnect
nordvpn set killswitch disabled
nordvpn set lan-discovery enable
nordvpn allowlist add subnet 100.64.0.0/10
nordvpn allowlist add port 41641 protocol UDP

The 100.64.0.0/10 subnet is Tailscale’s IPv4 CGNAT range. Allowlisting it tells NordVPN not to treat tailnet traffic like ordinary internet traffic. Port 41641 is Tailscale’s default WireGuard UDP port.

I also tried to allowlist Tailscale’s IPv6 ULA range:

nordvpn allowlist add subnet fd7a:115c:a1e0::/48

NordVPN refused that while LAN discovery was enabled:

Allowlisting a private subnet is not available while local network discovery is turned on.

I left it alone. IPv4 was the path I needed for the first working version.

Then connect Nord:

nordvpn connect
nordvpn status
curl -4 https://ifconfig.me

On the exit node itself, the public IP should change to a NordVPN egress IP.

Then test from the client while using the Tailscale exit node:

tailscale set --exit-node=<EXIT_NODE_TAILSCALE_IP> --exit-node-allow-lan-access=true
curl -4 https://ifconfig.me

When this finally worked, my laptop’s public IP changed to the same NordVPN egress IP used by the Linux VM.

That was the moment the architecture became real:

laptop -> Tailscale -> linux-exit -> NordVPN -> internet

The gotcha: Tailscale looked dead until I restarted it

The strangest failure was that Tailscale traffic could still work while the Tailscale admin console showed the Linux VM as offline.

That sounds impossible until you separate the data plane from the control plane.

Peer traffic from my laptop to the Linux VM still worked. I could even route through it. But tailscaled on that VM was not getting fresh network maps from Tailscale’s coordination server. tailscale status showed stale/offline warnings. tailscale netcheck could not get useful DERP latency results. Logs showed timeouts to Tailscale control and DERP endpoints.

I was tempted to blame NordVPN’s firewall layer. That may still be part of the general class of problems when commercial VPN clients and Tailscale share a machine, but it was not the fix in this case.

This fixed it:

sudo systemctl restart tailscaled

After that, tailscale netcheck started showing DERP latencies again, and the Tailscale admin UI stopped insisting the machine was offline. The stale state cleared when tailscaled restarted.

This is the part where I need to be honest about the tradeoff. Restarting tailscaled fixed the symptom I saw, but it does not prove the root cause. My goal was stable Tailscale exit-node routing through NordVPN. If your threat model requires “no packets ever leave except through Nord,” you should add explicit Linux firewall rules and test them. Do not treat a daemon restart as a privacy guarantee.

For my use case, the practical pattern mattered more than perfect leak prevention. I wanted an always-on exit node that my machines could use reliably. Once I had that, I could harden further.

The second twist: Nord was the wrong exit for one job

After I got the Nord path working, another use case immediately made it feel incomplete.

I was experimenting with Hermes, an agent tool I had just installed locally. One thing I wanted help with was monitoring LinkedIn for posts that might be worth reading or replying to. Not auto-commenting. Not posting without me. More like: watch the public surface I already check, summarize what matters, and leave the decision with me.

Hermes did not want NordVPN in that path.

That made sense after I stopped being annoyed. For a site like LinkedIn, a commercial VPN exit is not automatically “more secure” in the way that matters. It can look less like me. If the goal is normal account continuity, the better exit is often my home residential connection:

Hermes
        |
        v
Tailscale
        |
        v
linux-exit
        |
        v
home ISP
        |
        v
LinkedIn

That is the opposite of the Nord pattern:

Hermes
        |
        v
Tailscale
        |
        v
linux-exit
        |
        v
NordVPN
        |
        v
internet

The same exit node can support both ideas, but not at the same time for the same workflow. One job wants privacy-provider egress. Another wants residential egress. Another might want to appear in a different region. Another might want private mesh access only, with no public egress trickery at all.

That was the moment the problem changed. This was no longer just “how do I make Tailscale and NordVPN coexist?”

It became: how do I choose the right exit for the work I am doing?

Tailscale plus Mullvad may be the sane privacy path

This is where I have to admit the boring answer.

If the job is “I want privacy VPN egress from my Tailscale devices,” Tailscale plus Mullvad is probably the sane path. Tailscale sells Mullvad exit nodes as a supported feature. You pick a Mullvad region from inside the Tailscale world and avoid a whole category of conflicts between Tailscale and a separate commercial VPN client.

That does not make my Linux exit-node pattern useless. It still answers a different question: how do I make a machine I control bridge Tailscale to some other egress path?

But for the narrow privacy-exit use case, the supported integration matters. The hard part of my Nord setup was not Nord authentication or Tailscale exit-node setup. It was all the hidden policy: firewall behavior, route ownership, DNS, control-plane liveness, and the fact that killing a VPN client can also kill the remote session you were using to fix the VPN client.

The lesson is not “never compose your own network.” Sometimes you need to. The lesson is that composition should be reserved for the cases where it buys something you cannot get from the supported path.

For me, the supported path would likely look like this:

Privacy / travel:
  Tailscale + Mullvad exit node

Residential / normal web:
  Tailscale -> linux-exit -> home ISP

Experimental / provider-specific:
  Tailscale -> linux-exit -> NordVPN

That is already a profile system. It just does not have a product around it.

The Snap detour

One more embarrassing detail: I tried to update NordVPN with apt and got nowhere.

The reason was simple. On the Linux VM, NordVPN lived in:

/snap/bin/nordvpn

The update command was:

sudo snap refresh nordvpn

Snap refused at first because a Nord process was still running. Killing the stuck process and refreshing worked. After that, the Nord CLI behaved normally.

This matters because network debugging is full of these tiny wrong assumptions. If the package manager cannot find nordvpn, check where the binary actually lives:

command -v nordvpn
snap list nordvpn
apt-cache policy nordvpn

There is no dignity in VPN debugging. There is only evidence.

The route bug I did not expect

One of the side quests involved Screen Sharing between Macs on the same LAN. One Mac was advertising a LAN subnet route. Another machine accepted that route and started sending replies for a local LAN IP through Tailscale instead of the local gateway.

The fix was:

tailscale set --accept-routes=false

That is not a universal recommendation. Subnet routes are useful. I use them for things like printers and home services. But in this setup, on this machine, accepting routes was adding another variable to a problem that already had too many variables.

The broader lesson: while debugging exit-node behavior, remove extra routing features until the simple path works.

Get this working first:

client -> Tailscale -> Linux exit node -> NordVPN -> internet

Then bring subnet routes back deliberately.

The old Mac feature that had the right idea

This reminded me of something old macOS used to make obvious: network locations.

Back in the modem and early Wi-Fi days, I used connection profiles when traveling. You did not just “turn networking on.” You selected a location. Home. Office. Hotel. Client site. Each one carried a bundle of assumptions: which interface mattered, what DNS to use, whether proxies existed, and what the machine should consider normal.

macOS still has this feature. It is now called Network Locations, and on my Mac the only defined location was Automatic. That feels about right. The old abstraction is still in the system, but the modern network has moved somewhere else.

For a personal fleet in 2026, the location is not just Wi-Fi versus Ethernet. It is:

  • Which private network should be reachable?
  • Which machine should be the exit?
  • Should public traffic leave from home, a VPS, Mullvad, NordVPN, or nowhere?
  • Should DNS follow the VPN provider, the home router, or a tailnet resolver?
  • Should the profile fail closed if the exit path breaks?
  • Are automation tools allowed to browse from this profile?

That is the product shape I wish existed: Network Locations for the post-Tailscale era.

Not a giant enterprise dashboard. Not another VPN app with a big connect button. A small set of named profiles:

Home
  Direct ISP, LAN visible, Tailscale normal

Travel private
  Tailscale on, privacy VPN exit, fail closed

Human web
  Tailscale to home exit, residential egress, no commercial VPN

Region test
  Tailscale or provider exit in a selected country

Agent sandbox
  Fixed exit, conservative rate limits, explicit approval before action

That last one matters more now that more of us are letting agents browse for us. An agent should not have to infer the network posture from whatever VPN app happens to be connected. The profile should say what kind of traffic is allowed, where it should appear from, and what happens if the path changes.

Why Linux is the right place for the fight

I am not saying you cannot make this work on macOS or Windows. I am saying I stopped wanting to.

A Linux VM gives me:

  • systemd for restarting services
  • ip route, ip rule, and sysctl for inspecting routing state
  • a boring SSH session instead of fragile remote desktop
  • a cleaner VPN/firewall model
  • one machine to harden instead of every client

That last point is the architecture. The exit node is the boundary. All of the weird VPN coexistence work happens there.

My laptop just runs Tailscale.

The final checklist

Here is the condensed version I wish I had started with.

On the Linux exit node:

# Tailscale
sudo tailscale up --advertise-exit-node --accept-routes=false

# NordVPN
nordvpn disconnect
nordvpn set killswitch disabled
nordvpn set lan-discovery enable
nordvpn allowlist add subnet 100.64.0.0/10
nordvpn allowlist add port 41641 protocol UDP
nordvpn connect

Approve the exit node in Tailscale admin.

On the client:

tailscale set --exit-node=<EXIT_NODE_TAILSCALE_IP> --exit-node-allow-lan-access=true
curl -4 https://ifconfig.me

Useful debugging commands:

tailscale status
tailscale exit-node list
tailscale ping <EXIT_NODE_TAILSCALE_IP>
tailscale netcheck
journalctl -u tailscaled -n 80 --no-pager
nordvpn status
nordvpn settings
ip route
ip rule
sudo systemctl restart tailscaled

What “good” looked like:

  • The client could tailscale ping the Linux exit node.
  • The exit node showed NordVPN connected.
  • The client’s public IP matched the exit node’s NordVPN egress IP.
  • Tailscale admin showed the exit node as online/active.
  • tailscale netcheck on the exit node reported DERP latency again after restarting tailscaled.

What this pattern is, and is not

This is not a guide to building a perfect privacy gateway. It is a pattern for making a mesh VPN and a commercial privacy VPN coexist without turning every client machine into a routing experiment.

It works because it separates concerns:

Tailscale: identity, private reachability, exit-node selection
NordVPN: public egress from one Linux machine
Linux VM: the boundary where those two worlds meet

That is the same kind of lesson I learned from the Docker Tailscale sidecar pattern. In that setup, I stopped exposing services directly and gave each service a private mesh identity. In this setup, I stopped running two VPNs everywhere and gave public egress one place to happen.

The principle is similar:

Do not spread networking complexity across every service or device.
Put it at a boundary you can understand.

Sometimes the right abstraction is one boring machine with one boring job.

But after living with this for a bit, I do not think the machine is the whole story. The machine is the implementation detail. The thing I want to name is the exit profile.

An exit profile would say:

For this kind of work, use this private network,
this exit,
this egress path,
this DNS policy,
this fail behavior,
and this automation policy.

That is the missing piece. Tailscale gives me private reachability. Mullvad or NordVPN can give me privacy egress. My home ISP gives me residential egress. A VPS can give me a stable server-ish identity. What I do not have is one place to say which one I mean right now.

Questions I still have

I do not know whether this wants to be a product, a script, a router image, or a set of blessed recipes.

I do know the questions feel sharper now:

  • Should exit profiles live inside Tailscale, beside Tailscale, or on the exit node itself?
  • Should a profile manage only routes, or should it also own DNS, firewall policy, and browser/agent permissions?
  • Can a profile be safe enough to fail closed without making remote recovery miserable?
  • What is the right default for AI agents that browse the web on your behalf: home residential, privacy VPN, VPS, or explicit per-task choice?
  • Is Tailscale plus Mullvad enough for most privacy-exit cases, leaving custom Linux exits only for home-residential and provider-specific paths?
  • What would the modern version of macOS Network Locations look like if it understood tailnets, exit nodes, commercial VPN providers, and agents?

For now, I have a working pattern and a better question.

The working pattern:

client -> Tailscale -> Linux exit node -> NordVPN -> internet

The better question:

Which exit should this work use?

References

The agent-shaped org chart

Every real org has the same topology: principal, role-holder, specialists. Staff AI maps onto it, node for node, and the cost collapse shows up in the deliverables that were always just human-handoff overhead.

AI as staff, not software

Two frames for what AI is doing to work. The tool frame makes tools smarter. The staff frame makes roles unnecessary. Those aren't the same product, the same company, or the same industry.

Knowledge work was never work

Knowledge work was always coordination between humans who couldn't share state directly. The artifacts were never the work. They were the overhead — and AI just made the overhead optional.

The work of being available now

A book on AI, judgment, and staying human at work.

The practice of work in progress

Practical essays on how work actually gets done.

The 2 a.m. press check

Twenty-five years ago I stood under color-true lamps at 2 a.m., loupe in hand, waiting on the 50th spread. This month an open-source compiler handed me the thing Pantone only ever promised.

How do I get my dev team to adopt AI?

A stub on helping mixed-interest development teams find their own useful ways into AI.

Want to learn about agents? Talk to someone who ran an agency.

I spent 20 years running consulting engagements at Fortune 500 companies. Turns out that's the best preparation for running a fleet of AI agents ... because the problems are identical.

How i eliminated networking complexity: Docker tailscale sidecar patterns

Simplify your container networking with Docker Tailscale sidecar patterns, eliminating complexity and enhancing security for your self-hosted projects.

When teaching stops being bounded

AI removes the constraints that gave teaching its shape—one teacher, thirty students, limited time. But lifting constraints doesn't make the work easier. It makes it different. Teachers trained for a bounded classroom now face an unbounded role that requires judgment, discernment, and presence in ways we haven't yet mapped.

Why your job matters more than mine: The selective morality of job loss

This article reveals the uncomfortable pattern behind which jobs get moral protection and which get called 'market forces'—and what that means for everyone outside the creative class.