Having a VPN in today’s world is a must, and I can’t recommend Mullvad VPN enough. I’ve been using it for years now and am very satisfied (not paid sponsor).

Mullvad VPN is serious about privacy, zero-knowledge, requires no email registration, and you can pay with crypto (and even better, Monero).

mullvad-vpn.webp



Mullvad Clients

Linux Client: Mullvad has a Linux client that works great on Fedora (and any other distro). It’s simple to use and works great. Good GUI for normal configurations and a CLI client for advanced settings.

Android Client: Mullvad also has a really good Android client app (don’t like Apple ecosystem jail), which works great, simple, and fully featured.



Running Multiple VPNs

Normally, you would use only one VPN for all traffic, however I need to use 2 VPNs at once.

  1. Mullvad to protect all my network traffic
  2. Tailscale to access my private services available on a public VPS. The VPS runs a Headscale server that allows connections only from authenticated Tailscale clients.

Usually, you would have issues running 2 VPNs simultaneously. One takes over routing, and the other breaks. Mullvad allows split tunneling to have a specific app traffic bypass the VPN and connect directly, but this is app-based, which doesn’t work with Tailscale.

mullvad-tailscale.webp



Using Both Mullvad and Tailscale

I researched online for a solution and found that Tailscale offers a paid add-on for allowing Tailscale traffic over Mullvad via exit nodes, so, I decided to create a simple nftables rule to allow both connections on Linux.


How It Works

Mullvad’s split tunneling feature uses nftables packet marking. It tags excluded traffic with:

  • 0x00000f41 for connection tracking
  • 0x6d6f6c65 for routing

By applying these marks to Tailscale traffic (identified by the 100.64.0.0/10 IP range and Tailscale’s 0x80000 mark), the kernel routes it outside the Mullvad tunnel. In other words, connect via Tailscale to any Headscale/Tailscale server while using Mullvad VPN on all other traffic.


Configuration

Create the nftables Configuration

# Create the nftables directory if it doesn't exist
sudo mkdir -p /etc/nftables

# Create config file
sudo vim /etc/nftables/mullvad-tailscale.nft
# Contents of mullvad-tailscale.nft
table inet mullvad-tailscale {
    chain prerouting {
        type filter hook prerouting priority -100; policy accept;
        # Mark incoming Tailscale traffic (using default Tailscale subnet)
        ip saddr 100.64.0.0/10 ct mark set 0x00000f41 meta mark set 0x6d6f6c65;
    }
       
    chain outgoing {
        type route hook output priority -100; policy accept;
        # Mark outgoing traffic with Tailscale's mark
        meta mark 0x80000 ct mark set 0x00000f41 meta mark set 0x6d6f6c65;
        # Mark traffic to Tailscale IPs (using default Tailscale subnet)
        ip daddr 100.64.0.0/10 ct mark set 0x00000f41 meta mark set 0x6d6f6c65;
    }
}

Load the Rules

sudo nft -f /etc/nftables/mullvad-tailscale.nft

Make Changes Persistent

# Enable and start service
sudo systemctl enable --now nftables

Check the Table is Loaded

sudo nft list table inet mullvad-tailscale

Done. No restarts needed.



Testing

This should be enough. No complex configuration is needed.

Regular traffic goes through Mullvad and Tailscale connections bypass Mullvad (which makes Headscale server access possible).



When This Makes Sense

This is useful if you need to access Headscale/Tailscale VPN protected services while also need to route all your traffic through Mullvad VPN.

It has worked for me for many months now and had no issues. So I’m sharing it with the world.