Multiple, tailored, Mullvad VPN qubes from a single disposable template using vm-config

Original forum link
https://forum.qubes-os.org/t/34764
Original poster
eli h
Created at
2025-07-09 18:19:47
Posts count
5
Likes count
7
Tags
disposable-template, networking, version-r42, vpn

This guide provides an easily modified example of how one can employ vm-config to tailor an arbitrary number of named disposable VPN qubes from a single disposable Mullvad VPN template.

0. Motivation

Mullvad dropping support for OpenVPN in the coming months, necessitated finding an alternative to the dedicated VPN chains I've been using:

appVM > vpn-firewall > **city-specific-openvpn-qube** > sys-firewall > sys-net
Since @solene's Mullvad VPN setup in a standalone Fedora qube works just as well in a disposable Debian template, I combined her approach with vm-config for named disposable customization, which makes tailoring Mullvad VPN qubes super simple. While this guide focuses only on the tweaks required to implement this combination for "city-specific-MullvadVPN-qubes", one could use vm-config to modify any of the JSON config data employed by the Mullvad VPN App.

1. Create the disposable template <MULLVAD-DVM>

Follow the qubes documentation to create a single disposable template: <MULLVAD-DVM>. Once this appVM is in place, follow @solene's guide to build the appropriate nftable rules.

Open the Mullvad App in <MULLVAD-DVM>, setup with your account details, and toggle the desired settings. Select a default city as the exit; for this guide we'll use NYC. If you prefer to instead select a country, or a specific server, tweak the following setup accordingly.

Persist the default Mullvad App setup

As root:

mkdir -p /rw/bind-dirs/etc/mullvad-vpn
cp /etc/mullvad-vpn/* /rw/bind-dirs/etc/mullvad-vpn/

mkdir -p /rw/config/qubes-bind-dirs.d
touch /rw/config/qubes-bind-dirs.d/50_user.conf
echo "binds+=( '/etc/mullvad-vpn' )" >> /rw/config/qubes-bind-dirs.d/50_user.conf
You should now have three JSON files in /rw/bind-dirs/etc/mullvad-vpn/: account-history.json, device.json, and settings.json. The file settings.json will begin with something like the following (line numbers added for reference):

1{
2  "relay_settings": {
3    "normal": {
4      "location": {
5        "only": {
6          "location": {
7            "city": [
8              "us",
9              "nyc"
Create the rc.local-early script

Once /rw/config/rc.local-early is in place, add the following lines to the script. This will allow one to modify the city location before the Mullvad VPN App runs in each named disposable by modifying lines 8 and 9 in the settings.json file. Be sure to replace "us" and "nyc" below with the data from your settings.json file and correctly identify the line numbers.

country=$(qubesdb-read /vm-config/country)
city=$(qubesdb-read /vm-config/city)

sed -i "8s/us/$country/" /rw/bind-dirs/etc/mullvad-vpn/settings.json
sed -i "9s/nyc/$city/" /rw/bind-dirs/etc/mullvad-vpn/settings.json

2. Create named disposable qubes

In the Create New Qube GUI, select the following options for each new city-specific-MullvadVPN-qube.

Basic properties-Name: <MULLVAD-CITY>
Disposable qubes template: <MULLVAD-DVM>
Network: sys-firewall
Applications: Mullvad VPN
Advanced Options: Provides network access to other qubes

3. Assign country and city data to each named disposable qube

Use vm-config to assign each named disposable with a distinct <COUNTRY> and <CITY> combination.

In the dom0 terminal:

qvm-features <MULLVAD-CITY> vm-config.country <COUNTRY>
qvm-features <MULLVAD-CITY> vm-config.city <CITY>

Failing to assign this data, the <MULLVAD-CITY> qube will default to the choice of exit node given by the disposable template (NYC in this example).

Close the templates to complete the setup.