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: https://github.com/TommyTran732/QubesOS-Scripts/blob/main/fedora-gnome/fedora-gnome.sh

  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 
    

  5. Install Mullvad

    sudo dnf config-manager addrepo --from-repofile=https://repository.mullvad.net/rpm/stable/mullvad.repo
    sudo dnf install -y mullvad-vpn
    

  6. Install inotify sudo dnf install -y inotify-tools

  7. Restart/Shutdown your template for the changes to take effect

    Done with the MullvadApp-TVM template! Onto the AppVM

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

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

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

  11. 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=$(grep "nameserver" < /etc/resolv.conf| awk `{print $2}` | head -n 1)
    
            # 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=$(grep "nameserver" < /etc/resolv.conf| awk `{print $2}`)
    
            # 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
    # check for /etc/resolv.conf content change
    inotifywait -m -q -e close_write /etc/resolv.conf | while read -r;
    do
        update_dns
    done
    
    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!

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

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

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

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

Done!

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.