This guide offers a way to use Whonix Workstation with Mullvad as its NetVM. Which could be needed when you seek similar security and (pseudo)anonymity advantages of the Whonix Workstation, but prefer not to use Tor as your NetVM.
-
Clone your
whonix-workstation-17and name itwhonix-workstation-17-vpn -
Open a terminal in whonix-workstation-17-vpn template
-
Run
sudoedit /etc/uwt.d/50_user.conf -
Add.
uwtwrapper_global="0" -
Save and exit (Ctrl+S).
-
Verify that Stream Isolation is disabled by running:
uwt_settings_show(Optional but highly recommended)
Here’s a simple script to install the Mullvad Browser in the template:
# Configures tinyproxy
export https_proxy=http://127.0.0.1:8082
# Downloads the signing key
curl -fsSLo /usr/share/keyrings/mullvad-keyring.asc https://repository.mullvad.net/deb/mullvad-keyring.asc
# Add the Mullvad repository server to apt
echo "deb [signed-by=/usr/share/keyrings/mullvad-keyring.asc arch=$( dpkg --print-architecture )] https://repository.mullvad.net/deb/stable stable main" | sudo tee /etc/apt/sources.list.d/mullvad.list
# Install the package
apt update
apt install mullvad-browser
Template setup is complete! Now, let’s configure Mullvad for networking.
-
Clone a fedora-41 template and name it MullvadApp-TVM
-
Open a terminal in MullvadApp-TVM
-
Run this script by Tommy from PrivSec to minimize the template and reduce the attack surface: fedora-gnome.sh script
-
Create bind directories for Mullvad (So your Mullvad configuration stays after restart)
sudo mkdir -p /etc/qubes-bind-dirs.d
echo `binds+=( `\```/etc/mullvad-vpn``\`` )` | sudo tee /etc/qubes-bind-dirs.d/50_user.conf
- Install Mullvad
sudo dnf config-manager addrepo --from-repofile=https://repository.mullvad.net/rpm/stable/mullvad.repo
sudo dnf install -y mullvad-vpn
- Setup the systemd path and service units
Create your path unit sudoedit /etc/systemd/system/resolv-reload.path
[Path]
PathModified=/etc/resolv.conf
[Install]
WantedBy=multi-user.target
Create your service unit sudoedit /etc/systemd/system/resolv-reload.service
[Service]
ExecStart=/usr/local/bin/mullvad-dns.sh
Type=oneshot
Extra step for Fedora / SElinux to put the SElinux tag for systemd:
/sbin/restorecon -v /etc/systemd/system/resolv-reload.*
Enable it to start on boot
systemctl daemon-reload
systemctl enable resolv-reload.path
-
Restart/Shutdown your template for the changes to take effect
Done with the MullvadApp-TVM template! Onto the AppVM
-
Create an AppVM based on your MullvadApp-TVM template, name it sys-mullvad and set your network qube to sys-firewall or sys-whonix if you want a VPN over Tor solution.
-
Checkmark “Launch settings after creation” and go to the Advanced tab and checkmark “Provide network access to other qubes”.
Click Ok. -
In your settings window go to the Advanced tab and set both Initial memory and Max memory to 512.
Click Ok. -
Start your new sys-mullvad Qube and open a terminal. Run the command
sudoedit /usr/local/bin/mullvad-dns.shand paste in the following contents:
#! /usr/bin/env bash
update_dns() {
# mullvad_on: 0 -> off, 1 -> on
mullvad_on=$([[ $(grep -v -c "nameserver \+10.139" /etc/resolv.conf) -gt 0 ]] && echo 1 || echo 0)
if [[ $mullvad_on -eq 1 ]]; then
echo "Mullvad is on"
# get the mullvad dns ip address. First one is used if there is more than one.
mullvad_dns_ip=$(awk '/nameserver/ { print $2 ; exit }' /etc/resolv.conf)
# get the local ip address of qube vm.
qube_vm_ip=$(hostname -I | awk '{print $1}')
# delete all the lines defined in dnat-dns
sudo nft flush chain ip qubes dnat-dns
# forward all dns requests to mullvad dns servers
sudo nft add rule ip qubes dnat-dns meta l4proto { tcp, udp } ip daddr { "$qube_vm_ip" } th dport 53 dnat to "$mullvad_dns_ip"
else
echo "Mullvad is off"
# get qubes nameserver ip addresses
nameserver_ips=$(awk '/nameserver/ { print $2 }' /etc/resolv.conf)
# delete all the lines defined in dnat-dns
sudo nft flush chain ip qubes dnat-dns
# add rule to forward dns requests to qubes nameservers
for ip in $nameserver_ips; do
sudo nft add rule ip qubes dnat-dns ip daddr "$ip" udp dport 53 dnat to "$ip"
sudo nft add rule ip qubes dnat-dns ip daddr "$ip" tcp dport 53 dnat to "$ip"
done
fi
}
update_dns
Note: This is a slightly altered version of Solene’s DNS script from their Mullvad App VPN guide, so thanks to them for the original!
-
Make the script executable with
sudo chmod +x /usr/local/bin/mullvad-dns.sh -
Configure rc.local to boot mullvad-dns.sh at Qube boot with this command
echo "/usr/local/bin/mullvad-dns.sh &" | sudo tee -a /rw/config/rc.local -
Add this firewall rule to
/rw/config/qubes-firewall-user-scriptprevent a common MTU issue with Wireguard
nft add rule ip qubes custom-forward tcp flags syn / syn,rst tcp option maxseg size set rt mtu -
(Optional) Add these firewall rules to
/rw/config/qubes-firewall-user-scriptto add another killswitch in case Mullvad`s fails for whatever reason
nft add rule qubes custom-forward oifname eth0 counter drop
nft add rule ip6 qubes custom-forward oifname eth0 counter drop
Keep in mind that when you create an AppVM with your sys-mullvad qube, it must be based on the whonix-workstation-17-vpn template.
Big thanks to @solene and @TommyTran732 from PrivSec as this guide is a mash of both their guides plus a bit of my own.
