You want Mullvad privacy and Tailscale remote access on one Linux machine. By default they conflict. This guide makes them run together.
The Problem π€
Both tools change how your machine routes traffic.
Mullvad sends all traffic through a VPN and adds a kill switch. Tailscale builds a private network between your devices. Run both on one machine and they conflict.
The first symptom: tailscale up hangs. No error. No login link. No output.
The kill switch causes this. Mullvad's firewall drops any packet without the Mullvad mark (0x6d6f6c65). The Tailscale daemon sends first packets without the mark, so the firewall drops them. The daemon never reaches the Tailscale control server, so login never starts.
A second problem hits IPv6. Mullvad removes the IPv6 default route. IPv6 attempts fail with network is unreachable.
The Fix: mullvad-exclude π§
You do not disable the kill switch. You add an exception.
mullvad-exclude is Mullvad's split-tunnel tool. Run a program under mullvad-exclude and Mullvad tags the traffic with the mark. The traffic leaves the machine. Everything else stays inside the VPN.
Run the Tailscale daemon under mullvad-exclude:
mullvad-exclude tailscaled
Make the Fix Survive Reboots β»οΈ
A typed command dies on reboot. systemd restarts tailscaled from the unit file and drops your wrapper.
Add a systemd drop-in at /etc/systemd/system/tailscaled.service.d/override.conf:
[Unit]
After=mullvad-daemon.service
Wants=mullvad-daemon.service
[Service]
ExecStart=
ExecStart=/usr/bin/mullvad-exclude /usr/sbin/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/run/tailscale/tailscaled.sock --port=${PORT} $FLAGS
Reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart tailscaled
Two lines do the work.
First, the empty ExecStart= line. A service holds one ExecStart. To replace the original, clear the original with an empty line, then set your own. Skip the empty line and the service fails to start.
Second, After= and Wants=mullvad-daemon.service. These set boot order. If tailscaled starts before the Mullvad daemon, mullvad-exclude finds no daemon and Tailscale fails again. The failure shows only after a reboot. These two lines start Mullvad first.
The DNS Gotcha π
After tailscale up, your internet looks dead. Pages do not load.
The firewall is not the cause here. DNS is. Tailscale MagicDNS took over the system resolver. Normal lookups break with Temporary failure in name resolution.
Refuse MagicDNS:
sudo tailscale up --accept-dns=false
This sets CorpDNS=false in your saved preferences. The setting holds across reboots.
Remote Login with Tailscale SSH π
Now add remote login.
Skip OpenSSH. Tailscale ships SSH built in. No open port 22. No password file. Access uses your Tailscale identity and ACLs.
Enable Tailscale SSH:
tailscale set --ssh
This saves RunSSH=true and holds across reboots.
Use tailscale set for changes, not tailscale up. tailscale up resets any flag you omit. tailscale set changes only the flags you name. So tailscale set keeps --ssh and --accept-dns=false safe.
A Self-SSH Limit π΅οΈ
Tailscale SSH from a host to the same host fails.
The traffic crosses the WireGuard tunnel. A node does not route to the same node. Test from a different device. A connection refused from the same machine is expected, not a fault.
Verify After Reboot β
Confirm the setup survives a restart.
Six checks pass when the setup works:
-
tailscaledis active -
tailscaledruns undermullvad-exclude - the tailnet is up
- SSH is enabled
- normal DNS resolves
- normal traffic exits through Mullvad
One script runs all six:
#!/usr/bin/env sh
echo "== tailscale + mullvad coexistence check =="
systemctl is-active --quiet tailscaled \
&& echo "PASS tailscaled active" \
|| echo "FAIL tailscaled not active"
systemctl show tailscaled -p ExecStart | grep -q mullvad-exclude \
&& echo "PASS running under mullvad-exclude" \
|| echo "FAIL not under mullvad-exclude"
if tailscale status >/dev/null 2>&1 && ! tailscale status 2>&1 | grep -qi "logged out"; then
echo "PASS tailnet up (logged in)"
else
echo "FAIL tailnet down / logged out"
fi
tailscale debug prefs 2>/dev/null | grep -q '"RunSSH": true' \
&& echo "PASS tailscale SSH enabled" \
|| echo "FAIL SSH pref off"
getent hosts google.com >/dev/null 2>&1 \
&& echo "PASS normal DNS works" \
|| echo "FAIL DNS broken"
curl -fsS --max-time 8 https://am.i.mullvad.net/connected 2>/dev/null | grep -qi "connected to Mullvad" \
&& echo "PASS normal traffic via Mullvad" \
|| echo "FAIL not protected by Mullvad"
Reboot. Run the script. Six PASS lines confirm the setup.
π Done. Mullvad protects your normal traffic. Tailscale reaches your devices and serves SSH. The setup holds across restarts.
Top comments (0)