How to configure Whonix to use Mullvad

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.

  1. Clone your whonix-workstation-17 and name it whonix-workstation-17-vpn

  2. Open a terminal in whonix-workstation-17-vpn template

  3. Run
    sudoedit /etc/uwt.d/50_user.conf

  4. Add.
    uwtwrapper_global="0"

  5. Save and exit (Ctrl+S).

  6. 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.

  1. Clone a fedora-41 template and name it MullvadApp-TVM

  2. Open a terminal in MullvadApp-TVM

  3. Run this script by Tommy from PrivSec to minimize the template and reduce the attack surface: fedora-gnome.sh script

  4. 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 
  1. Install Mullvad
sudo dnf config-manager addrepo --from-repofile=https://repository.mullvad.net/rpm/stable/mullvad.repo
sudo dnf install -y mullvad-vpn
  1. 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
  1. Restart/Shutdown your template for the changes to take effect

    Done with the MullvadApp-TVM template! Onto the AppVM

  2. 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.

  3. Checkmark “Launch settings after creation” and go to the Advanced tab and checkmark “Provide network access to other qubes”.
    Click Ok.

  4. In your settings window go to the Advanced tab and set both Initial memory and Max memory to 512.
    Click Ok.

  5. 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!

  1. Make the script executable with
    sudo chmod +x /usr/local/bin/mullvad-dns.sh

  2. 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

  3. 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

  4. (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.

1 Like

this can be replaced by

awk '/nameserver/ { print $2 }' /etc/resolv.conf

this can be replaced by

awk '/nameserver/ { print $2 ; exit }' /etc/resolv.conf
2 Likes

If you prefer, the inotify-tools part and script could be entirely replaced by systemd implementation.

The could be a .path systemd unit that watch for changes in /etc/resolv.conf and would trigger a script everytime it change, which could run your script.

That would look like this:

In /etc/systemd/system/resolv-reload.path:

[Path]
PathChanged=/etc/resolv.conf

[Install]
WantedBy=multi-user.target

In /etc/systemd/system/resolv-reload.service:

[Service]
ExecStart=/usr/local/bin/update-firewall.sh
Type=oneshot

Extra step for Fedora / SElinux to put the SElinux tag for systemd:

/sbin/restorecon -v /etc/systemd/system/resolv-reload.*

Then, enable it:

systemctl daemon-reload
systemctl start resolv-reload.path
2 Likes

Is there a reason to do things this way instead of using a kicksecure template?

1 Like

Good question, after some reading and asking this is the best answer from Patrick himself
http://forums.dds6qkxpwdeubwucdiaord2xgbbeyds25rbsgr73tbfpqpt4a6vjwsyd.onion/t/configure-whonix-workstation-to-use-sys-vpn/21460/6

The specific, technical reason(s) is unknown to me even after skimming through the linked documentation.

My thought is as Whonix is designed for anonymity and Kicksecure for security. Whonix is better suited if you still require some sense of anonymity even with a VPN layer.

1 Like