Mullvad + Wireguard on Qubes-OS

You just paste the wireguard config for mullvad or whatever VPN and reboot. I think what you are looking for is something nice and fancy like this solution on Gitlab for Qubes called QOSVPN. I have never tested it.


It seems QOSVPN is for OpenVPN, not wireguard.

Getting the Mullvad GUI to work.

What I did:
I have installed the Mullvad GUI in (the template of) an AppVM and set that AppVM to “provide networking”. In the Mullvad AppVM I set /etc/mullvad-vpn to be persistent and I start the GUI and connect to VPN.

I inserted this VM either between sys-firewall and sys-net or between my normal appvms and sys-firewall (don’t know which would be better).
AppVM → sys-firewall → mullvad → sys-net
AppVM → mullvad → sys-firewall → sys-net

What I though this would do:
I thought I could use the Mullvad GUI in the mullvad Qube to control the traffic for all my AppVMs.

What actually happens:
From my AppVMs I can ping an IP address, but I cannot resolve any DNS names.

If I run sudo /usr/lib/qubes/qubes-setup-dnat-to-ns in the mullvad VM, then DNS resolution works.

What I still need help with:

  1. A way to automatically run this qubes-setup-dnat-to-ns command when Mullvad connects (or disconnects?)
  2. Bonus points if I can still access a printer on my home 192.168 network circumventing VPN.
  3. A better understanding of how DNS resolution is handled in Qubes.

So you have Multihop with SOCKS5?

In the link provided I could not find out how to exit Qube 1 to a different exit point than Qube 2.

For instance, if you are connected to on the ProxyVM and then want to exit via on Qube1, how would you configure Qube1 to use on port 1080 as your exit node?

Do you know the Debian equivalent by any chance?

It’s somewhat unusual for Debian packages to be signed, although the
capability exists. It’s the repository metadata that is signed.
If you have downloaded a .deb you should be able to install it with
apt install or dpkg -i

If the repository is not signed, you should ask yourself why - you can
edit the repository definition in /etc/apt/sources.list.d to include
deb [trusted=yes]..., or you can use apt-get --allow-unauthenticated.
Both of these are risky practices.

1 Like

This worked in my case:

Insert the line sleep 5 above the line /usr/lib/qubes/qubes-setup-dnat-to-ns in /etc/NetworkManager/dispatcher.d/qubes-nmhook like this:


# Source Qubes library.
# shellcheck source=init/functions
. /usr/lib/qubes/init/functions

if [ "$2" = "up" ] || [ "$2" = "vpn-up" ] || [ "$2" = "vpn-down" ]; then

    # Here!
    # You may need to increase it beyond 5 seconds.
    sleep 5


    # FIXME: Tinyproxy does not reload DNS servers.
    if under_systemd ; then
        systemctl --no-block try-restart qubes-updates-proxy.serviceBut 
        service qubes-updates-proxy try-restart

Hello everyone!

I know it’s old thread but i found it somehow and i was thinking that it will be helpful for other people as well, so decided to register and comment here :wink:

So after spending my whole day to solve it
(sleep5 didn’t work)

the solution is very simple without to modify anything at all,

first at all you must install the mullvad vpn from their site.

  1. open the mullvad gui
  2. click on Settings
  3. click on Advanced
  4. tick the use custom dns server - by default it’s OFF (red color)
    just turn it on so it will be in “green color”


now press the back button , reconnect the vpn and that’s it!!
do not touch that function anymore, it will be saved for the next reboot as is
(even if you see it in red- i guess it did some magic in their config file )

Enjoy :slight_smile:

I see this topic pop up from time to time.
Mullvad is very Qubes friendly and they recently published a guide to use WireGuard with Mullvad on Qubes 4. I used the guide to set up a proxyVM using WireGuard and it was flawless. In my case Wireguard is much faster in connect-time and bandwidth than my previous experience with OpenVPN in Qubes.

Have fun!

In my experience, Wireguard is lame even with the best providers, such as mullvad and ivpn, because of the extensive, manual configuration required per server, unless using their app.

(I’ve yet to get the ivpn to work with the same setup i’ve long used with mullvad - both openvpn and wireguard more recently.)

What happens periodically is that mullvad either completely eliminates a wireguard server or it’s just down for a long time.

Then, once you go through all the elimination steps of figuring out that it’s not some isp problem or myriad of qubes’ glitches, you’ll have to go through all the mullvad morass in the instructions above again to set up a different wireguard server.

That server might also be eliminated or down the next day, and so on…
Naturally, this happens without warning, and exactly when you have the least spare time to figure it all out and command line fuss to fix it…

The CLI solution seems to be to write complicated failover scripts for several wireguard servers, which will require their own maintenance… :face_with_head_bandage:

For me, upgrade from OpenVPN to Wireguard has been a waste of time so far, and has just made mullvad more unrealiable and labour intensive to keep working.

So, I don’t recommend Wireguard on Qubes even with the best providers.

1 Like

Interesting feedback. Wireguard was also on my to do list. Has anyone experienced the same?

This is a limitation of nm-applet lacking ability to add multiple wireguard servers/IPs in one configuration at once; (not the case with OpenVPN). They could easily allow this by enabling you to add multiple IPs with the other parameters the same - as for most VPN providers the keys and IP config provided are the same for each server.

You could write a script to do this for you, or just labour manually in the GUI. But the NM-applet GUI I do find lacking if you want to separate lots of regions. Again, this could be solved with a simple script.

I have seen some open-source extensions/add-ons to nm-applet which achieve some improvements, though not all I desire; however I have not used them as they are not officially adopted and I prefer to keep to the mainstream repos.

1 Like

These are the instructions that made it work for me.

First, from @turkja, with additions/ clarifications particularly in sections 5-7:

  1. Create new TemplateVM based on Fedora/Debian template. For example
    qvm-clone debian-11 debian-11-mullvad
    Start a terminal in that TemplateVM

  2. Copy the Mullvad installation .rpm/.deb to your TemplateVM (download it with internet-connected AppVM, verify signature, copy using Qubes tools (right-click, copy to other AppVM → debian-11-mullvad)

  3. In the debian-11-mullvad TemplateVM, install the Mullvad .deb
    So, something like sudo apt install ./MullvadVPN-2021.4_x86_64.deb

  4. Shut down the template.

  5. Next, create new AppVM, sys-vpn-mullvad, based on this template. Set the net qube as default (sys-firewall) Launch Settings after creation, and In the Advanced tab, click “provides networking”. When settings comes up, In the services tab, add network-manager with the + button. Under Applications add Mullvad VPN Unber Basic check Start qube automatically on boot

  6. In the AppVM, configure bind-dirs for /etc/mullvad-vpn as explained here.

    sudo mkdir -p /rw/config/qubes-bind-dirs.d
    sudo touch /rw/config/qubes-bind-dirs.d/50_user.conf
    echo “binds+=( '/etc/mullvad-vpn' )" | sudo tee /rw/config/qubes-bind-dirs.d/50_user.conf


  7. In the AppVM, start the GUI, /opt/Mullvad VPN/mullvad-gui Configure it to your taste.
    Select the country
    User Interface Settings >> Start Minimized
    VPN Settings >> Launch App on Startup; Auto-connect; Local Network Sharing?; Kill Switch & Lockdown Mode; Tunnel Protocol ⇒ Wireguard

In a qube that you want to use with the VPN, say the Untrusted qube, in Settings >> Basic, change the Net qube to sys-vpn-mullvad
Start a terminal in the Untrusted (or other) qube
Try ping If you end up with a name resolution error, then, following @cobordism:
In the AppVM, sys-vpn-mullvad, start a terminal
Run sudo /usr/lib/qubes/qubes-setup-dnat-to-ns
If name resolution now works in the Untrusted qube, then use the approach of @tngbng:
Start a terminal in the Mullvad TemplateVM, debian-11-mullvad
In the file /etc/NetworkManager/dispatcher.d/qubes-nmhook, insert the command sleep 5 before:
sudo /usr/lib/qubes/qubes-setup-dnat-to-ns
Restarting the AppVM and then the Untrusted qube, name resolution should work.

Finally, follow the instructions at Mullvad to add the DNS hijacking rules in the AppVM.
(For some reason, these stick after a reboot despite bind-dirs not having been configured for /rw/config.)

To find out your vif* IP address, run `ip a | grep -i vif`
Edit the firewall user file with nano: 
    `sudo nano /rw/config/qubes-firewall-user-script`
Copy and paste the following in the bottom. 
Replace with your own vif* IP address:
    # replace with the IP address of your vif* interface
    iptables -F OUTPUT
    iptables -I FORWARD -o eth0 -j DROP
    iptables -I FORWARD -i eth0 -j DROP
    iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS  --clamp-mss-to-pmtu
    iptables -F PR-QBS -t nat
    iptables -A PR-QBS -t nat -d $virtualif -p udp --dport 53 -j DNAT --to $vpndns1
    iptables -A PR-QBS -t nat -d $virtualif -p tcp --dport 53 -j DNAT --to $vpndns1

Then add the qube firewall rules to the Settings manager for the AppVM:

Click on the "Firewall rules" tab.
Click on "Limit outgoing internet connections to ...".
Click on "+" and enter the IP addresses of the VPN servers that you want to be able to connect to. You can find it in the WireGuard configuration file (mlvd-se9.conf) on the Endpoint line, or in our [Servers list](
Click on OK.

If you have to add more servers later you can do it in the Terminal Emulator using the following command (replace SERVER-IP with the IP-address to the Mullvad VPN server).
`qvm-firewall MullvadVPN add accept dsthost=SERVER-IP`

Than also disable ping replies:

Click on the Qubes app menu and open Terminal Emulator (for dom0)
Run `qvm-firewall sys-vpn-mullvad list`
Find the rule in the bottom that says `accept icmp` and note the line number.
Run `qvm-firewall sys-vpn-mullvad del --rule-no NUMBER`. Replace NUMBER with the line number you found above.
Run `qvm-firewall sys-vpn-mullvad add --before NUMBER drop proto=icmp`. Replace NUMBER with the line number you found above. This new rule will be added before the last "drop" line.
Check it by running the list command again. The rules should be in this order: accept (the IP addresses of the VPN servers), accept dns, drop icmp, drop.

Troubleshooting if the Internet works in the ProxyVM, but not in the AppVM.

Make sure you shut down your AppVM before setting the Networking to ProxyVM. It seems that it does not work as well to change it on the fly.

Try to lower the MTU in your AppVM:
`sudo ip link set mtu 1200 eth0`

Files in /rw are persistent in an AppVM. (That is the reason why the configuration for bound directories is located inside /rw, if that wasn’t persistent it wouldn’t be very useful!)

From the docs you linked:

In an app qube all of the file system comes from the template except /home , /usr/local , and /rw . This means that changes in the rest of the filesystem are lost when the app qube is shutdown. [Implicit: the changes in those three directories are persistent.] (docs)

1 Like

Ok, well I thought that all worked, but …

In the sys-vpn-mullvad AppVM (based on a debian-11-mullvad TemplateVM), which runs the Mullvad GUI, I am getting good up & down internet speeds. But when I link my Untrusted AppVM to sys-vpn-mullvad, I’m getting good download speeds but almost nonexistent (0.21Mb/s) upload speeds. I removed any of the firewall rules in the settings manager (using this post) but it doesn’t help.


Can you try reducing your MTU to 1320 or 1280 in the app?
Also, apply this iptables rule in the ProxyVM:

sudo iptables -t nat -I POSTROUTING 3 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Thanks for your help @DVM.

Reducing the mtu to 1280 gave me the full upload speed (1320 gave me half).

sudo ip link set mtu 1280 eth0

I suppose I’ll have to create a bootup script to adjust that?

sudo iptables -t nat -I POSTROUTING 3 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

It seemed to work without that, but I will add it in. I’m not versed in iptables, so I looked around and found some discussion of what this is doing, here and here. At that second link there is a comment,

MSS Clamping is for TCP-based protocols. There’s an whole world of applications left out.

Should I be expecting problems with UDP protocols?

Since you are using the app, you should be able to adjust it in the settings.
For the iptables rule, it’s to force all connected qube to use the correct MTU.

No, it will be fine

Ok, so I had misunderstood you. My setup is:

→ sys-firewall
→ sys-vpn-mullvad (based on a debian-11-mullvad template and containing the Mullvad.deb app)
→ Untrusted AppVM

I now see what you were saying was to go into the Mullvad.deb app in sys-vpn-mullvad

→ Settings → VPN Settings → Wireguard Settings → MTU
And there set the MTU at 1280. Then perhaps reboot (which I did).

Finally, in sys-vpn-mullvad, run the iptables command.

Now that I’ve done that, it does not work. What does work is instead, in the Untrusted AppVM, to run:

sudo ip link set mtu 1280 eth0

With or without the iptables command in sys-vpn-mullvad, with or without setting the MTU in the Mullvad.deb, that one command makes it possible to upload.

This, as I now see, is what is suggested in the Troubleshooting section at the bottom of this page at Mullvad.

Edit: I’ve tried as desribed here:

Adding under the line send host-name = gethostname(); in /etc/dhcp/dhclient.conf

default interface-mtu 1280;
supersede interface-mtu 1280;

I’ve also tried adding to /etc/network/interfaces:

post-up ip link set mtu 1280 eth0

And in /rw/config/qubes-bind-dirs.d/50_user.conf, to make the file changes stick:

binds+=( '/etc/dhcp/dhclient.conf' )
binds+=( '/etc/network/interfaces' )

In neither case does the change of the mtu stick on reboot.

So changing the MTU In the app and using the iptables rule doesn’t work?

Try to add this to /rw/config/rc.local, it will be executed every time the qube is started.

1 Like