Two tailnets at the same time on one Linux notebook
I use two tailnets on my notebook: my private one and the one from work.
Tailscale supports multiple accounts, but only one is active at a time, so I was switching with tailscale switch regularly.
From the work tailnet I only need the subnet routes into our AWS infrastructure -- nobody there needs to reach my notebook.
So the work tailnet can run as a second tailscaled instance next to the private one.
A second systemd service
The second instance is a copy of tailscaled.service from the Arch Linux package, with its own state file, socket, TUN device and UDP port.
I saved it as /etc/systemd/system/tailscaled-work.service:
[Unit] Description=Tailscale node agent (work tailnet) Wants=network-pre.target # systemd-networkd here; with NetworkManager use NetworkManager.service instead After=network-pre.target systemd-networkd.service systemd-resolved.service tailscaled.service [Service] ExecStart=/usr/sbin/tailscaled --state=/var/lib/tailscale-work/tailscaled.state --socket=/run/tailscale-work/tailscaled.sock --tun=tailscale1 --port=41642 Restart=on-failure RuntimeDirectory=tailscale-work RuntimeDirectoryMode=0755 StateDirectory=tailscale-work StateDirectoryMode=0700 CacheDirectory=tailscale-work CacheDirectoryMode=0750 Type=notify [Install] WantedBy=multi-user.target
The original unit has ExecStopPost=/usr/sbin/tailscaled --cleanup, which I left out.
Both instances share the same policy routing rules and routing table 52, and the cleanup of one instance should not remove what the other one is using.
This does not help completely, though.
Stopping tailscaled-work still deletes the shared rules -- ip rule afterwards only lists local, main and default:
Without the lookup 52 rule the private tailnet is unreachable too.
Starting the work instance again adds the rules back, and restarting the private one with sudo systemctl restart tailscaled should do the same.
As long as both services just run all the time, this does not matter.
Enable and start the new service:
Logging in
All tailscale commands for the second instance need the socket, so an alias helps:
Logging in creates a new node in the work tailnet -- the old work profile in the main instance is not reused:
sudo tailscale --socket=/run/tailscale-work/tailscaled.sock up \ --accept-routes --accept-dns=false --shields-up --netfilter-mode=off --operator=$USER
--accept-routes-
Gets the AWS subnet routes.
--accept-dns=false-
Keeps MagicDNS and the DNS settings with the private tailnet.
--shields-up-
Blocks all incoming connections from the work tailnet.
--netfilter-mode=off-
Leaves the iptables chains to the private instance; both would write into the same
ts-inputchain, and its rule drops 100.x traffic that does not come in on its own interface. --operator-
Allows my user to run
tailscale-work statuswithout sudo.
The old work node still has the hostname, so the new machine gets the old name with -1 appended.
I deleted the old node in the admin console and renamed the new one to drop the -1.
Result
Both instances are logged in and each has its own interface:
tailscale status --peers=false tailscale-work status --peers=false ip -br addr show | grep tailscale
The subnet routes from the work tailnet land in table 52 on tailscale1, next to the peers of the private tailnet on tailscale0:
ip route get with an address from one of these subnets shows dev tailscale1 table 52.
A host at work that is only reachable via the tailnet loads in the browser, and my private peers still answer tailscale ping.
It is only this simple because the two tailnets do not share any 100.x addresses and my private tailnet has no routes into AWS.
Finally, remove the old work profile from the main instance (tailscale switch --list shows the names):
No more tailscale switch when a dev setup needs AWS access for work.