This method offers a more secure way to utilize VPNs when you seek similar security and (pseudo)anonymity advantages of the Whonix Workstation, but prefer not to use Tor as your exit node.
-
Clone your
whonix-workstation-17
and 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.sh
and 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-script
prevent 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-script
to 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.