Keeps Mullvad and Tailscale from fighting at the netfilter/DNS layer.
Arbitrates a set of network providers — currently Mullvad and Tailscale — so a
chosen combination coexists cleanly. The hard part isn't running each one; it's
keeping them from fighting at the netfilter/DNS layer (Mullvad's killswitch
drops Tailscale, and both daemons claw at /etc/resolv.conf). vpnmux keeps that
truce continuously.
It's an operator/control-loop: a root daemon reconciles the system to a desired set of providers every couple of seconds; the CLI just writes the desired state and reads back status. Single writer, idempotent, std-only Rust (no external crates).
Status: works, validated as a shell prototype and reimplemented in Rust
(43 unit tests). Linux-only — it drives nft/mullvad/tailscale directly.
| Method | Command |
|---|---|
| Debian/Ubuntu | Download .deb — sudo dpkg -i vpnmux_*_amd64.deb |
| Fedora/RHEL | Download .rpm — sudo rpm -i vpnmux-*.x86_64.rpm |
| Binary | Grab a tarball from Releases (x86_64, aarch64) |
| From source | cargo build --release → target/release/vpnmux |
The .deb/.rpm ship a systemd unit (vpnmux.service, disabled by default).
Enable it once installed:
sudo systemctl enable --now vpnmuxRun the daemon as root — it drives nft/mullvad/tailscale and reconciles
every ~2s. Keep it in a terminal; add VPNMUX_LOG=debug for the full
command-by-command trace (default is a quiet, diff-based change-log):
sudo target/release/vpnmux daemonSwitch state in another shell. If your user is in the vpnmux group (see
Sudo-less CLI below) the sudo is optional — the CLI only needs write
access to /var/lib/vpnmux/desired, which the daemon picks up:
vpnmux set mullvad tailscale # both, Tailscale via the tunnel
vpnmux set mullvad # Mullvad only
vpnmux set tailscale # Tailscale only
vpnmux set # none
vpnmux statusThe daemon mirrors mullvad-daemon's pattern: at startup it chowns
/var/lib/vpnmux and /run/vpnmux to root:vpnmux (mode 02770, setgid)
when a vpnmux system group exists, so members of that group can drive
vpnmux set/status without sudo. To enable:
sudo groupadd --system vpnmux
sudo usermod -aG vpnmux "$USER"
sudo systemctl restart vpnmux
# log out & back in (or `newgrp vpnmux`) for the group to take effectOverride the group name with VPNMUX_GROUP=othergroup in the unit's
Environment=, or set it empty to opt out and keep the dirs root-only.
Anyone in the
vpnmuxgroup can flip providers, including disabling Mullvad while lockdown is on (the[y/N]prompt still applies). Same trust model as themullvadgroup on systems that use one.
Switching to none/tailscale while Mullvad lockdown is on warns and prompts
first — it would cut all connectivity (that's the killswitch doing its job).
| State | Mullvad | Tailscale | DNS |
|---|---|---|---|
none |
off | off | system |
mullvad |
connected | off | Mullvad (10.64.0.1) |
tailscale |
off | up | MagicDNS |
| both | connected | up, via the tunnel | Mullvad (MagicDNS off) |
The daemon never imposes a default: with no desired state set it stays idle and touches nothing.
| Var | Purpose |
|---|---|
VPNMUX_LOG |
error / info (default) / debug |
VPNMUX_NFT |
absolute path to nft (else scans /gnu/store) |
VPNMUX_MULLVAD / VPNMUX_TAILSCALE |
adapter binary paths |