Mullvad VPN: Difference between revisions
imported>Pear Create page |
m Category:VPN added |
||
(8 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
<b>Mullvad VPN</b> is a virtual private network service operated in Sweden by [https://mullvad.net Mullvad VPN AB]. It uses [[WireGuard]] under the hood and includes both a CLI and a GUI package. | |||
== Installation == | |||
To install Mullvad VPN, you need to enable it in your system options: | To install Mullvad VPN, you need to enable it in your system options: | ||
{{ | {{warning|1=Mullvad VPN currently only works if [[systemd-resolved]] is enable. More info [https://discourse.nixos.org/t/connected-to-mullvadvpn-but-no-internet-connection/35803/8?u=lion at this forum post]. If this issue is fixed please remove this warning banner.}} | ||
services.mullvad-vpn.enable = true; | |||
{{file|configuration.nix|nix|<nowiki> | |||
{ | |||
services.mullvad-vpn.enable = true; | |||
} | |||
</nowiki>}} | </nowiki>}} | ||
If you want to use the GUI application: | If you want to use the GUI application: | ||
< | {{file|configuration.nix|nix|<nowiki> | ||
services.mullvad-vpn.package = pkgs.mullvad-vpn; | { pkgs, ... }: | ||
</ | { | ||
services.mullvad-vpn.package = pkgs.mullvad-vpn; | |||
} | |||
</nowiki>}} | |||
{{Evaluate}} | |||
== Configuration == | |||
You can declaratively configure the Mullvad daemon by writing the <code>settings.json</code> file to the location set by the <code>MULLVAD_SETTINGS_DIR</code> environment variable. (<code>/etc/mullvad-vpn</code> by default) | |||
The example below sets up a few custom lists that let you select a whole continent as an exit location, or simply randomizes all countries if you don't care where your IP is coming from. A caveat with this configuration is that it must be manually updated every time Mullvad implements a server in a new country. | |||
{{file|configuration.nix|nix|<nowiki> | |||
{ pkgs, ... }: | |||
let | |||
mullvadConfig = | |||
let | |||
mkCountryList = id: countries: name: { | |||
inherit id name; | |||
locations = map (country: { inherit country; }) countries; | |||
}; | |||
in | |||
pkgs.writeText "mullvad-settings" ( | |||
builtins.toJSON { | |||
allow_lan = true; | |||
auto_connect = true; | |||
block_when_disconnected = true; | |||
api_access_methods = { | |||
custom = [ ]; | |||
direct = { | |||
access_method.built_in = "direct"; | |||
enabled = true; | |||
id = "00000000-0000-0000-0000-000000000008"; | |||
name = "Direct"; | |||
}; | |||
mullvad_bridges = { | |||
access_method.built_in = "bridge"; | |||
enabled = true; | |||
id = "00000000-0000-0000-0000-000000000009"; | |||
name = "Mullvad Bridges"; | |||
}; | |||
encrypted_dns_proxy = { | |||
access_method.built_in = "encrypted_dns_proxy"; | |||
enabled = true; | |||
id = "00000000-0000-0000-0000-000000000010"; | |||
name = "Encrypted DNS proxy"; | |||
}; | |||
}; | |||
# The bridge options are only useful for OpenVPN. You can configure Wireguard under Shadowsocks with `obfuscation_settings` below. | |||
bridge_state = "auto"; | |||
bridge_settings = { | |||
bridge_type = "normal"; | |||
custom = null; | |||
normal = { | |||
location = "any"; | |||
ownership = "any"; | |||
providers = "any"; | |||
}; | |||
}; | |||
custom_lists.custom_lists = | |||
let | |||
northAmerica = [ | |||
"ca" | |||
"mx" | |||
"us" | |||
]; | |||
southAmerica = [ | |||
"br" | |||
"cl" | |||
"co" | |||
"pe" | |||
]; | |||
europe = [ | |||
"al" | |||
"at" | |||
"be" | |||
"bg" | |||
"ch" | |||
"cy" | |||
"cz" | |||
"de" | |||
"dk" | |||
"ee" | |||
"es" | |||
"fi" | |||
"fr" | |||
"gb" | |||
"gr" | |||
"hr" | |||
"hu" | |||
"ie" | |||
"it" | |||
"lv" | |||
"nl" | |||
"no" | |||
"pl" | |||
"pt" | |||
"ro" | |||
"rs" | |||
"se" | |||
"si" | |||
"sk" | |||
"tr" | |||
"ua" | |||
]; | |||
africa = [ "za" ]; | |||
asia = [ | |||
"hk" | |||
"id" | |||
"il" | |||
"jp" | |||
"sg" | |||
"th" | |||
]; | |||
oceania = [ | |||
"au" | |||
"nz" | |||
]; | |||
in | |||
[ | |||
(mkCountryList "00000000-0000-0000-0000-000000000000" ( | |||
northAmerica ++ southAmerica ++ europe ++ africa ++ asia ++ oceania | |||
) "All Countries") | |||
(mkCountryList "00000000-0000-0000-0000-000000000002" northAmerica "North America") | |||
(mkCountryList "00000000-0000-0000-0000-000000000003" southAmerica "South America") | |||
(mkCountryList "00000000-0000-0000-0000-000000000004" europe "Europe") | |||
(mkCountryList "00000000-0000-0000-0000-000000000005" africa "Africa") | |||
(mkCountryList "00000000-0000-0000-0000-000000000006" asia "Asia") | |||
(mkCountryList "00000000-0000-0000-0000-000000000007" oceania "Oceania") | |||
]; | |||
# This is where you would configure IP overrides for Mullvad's servers, if you have any. | |||
relay_overrides = [ | |||
/* | |||
{ | |||
hostname = "se-sto-wg-001"; | |||
ipv4_addr_in = "[remote IPv4]"; | |||
ipv6_addr_in = "[remote IPv6]"; | |||
} | |||
*/ | |||
]; | |||
relay_settings = { | |||
normal = { | |||
location.only.custom_list.list_id = "00000000-0000-0000-0000-000000000000"; # This selects all countries by default, so you get a random exit country every time you connect to your VPN. | |||
openvpn_constraints.port = "any"; | |||
ownership = "any"; | |||
providers = "any"; | |||
tunnel_protocol = "any"; | |||
wireguard_constraints = { | |||
entry_location.only.custom_list.list_id = "00000000-0000-0000-0000-000000000000"; # This will be ignored if DAITA is enabled, or if `use_multihop` is set to false. | |||
ip_version = "any"; | |||
port = "any"; | |||
use_multihop = true; | |||
}; | |||
}; | |||
}; | |||
obfuscation_settings = { | |||
selected_obfuscation = "auto"; # This can be set to `shadowsocks`, for instance. | |||
udp2tcp.port = "any"; | |||
}; | |||
tunnel_options = { | |||
openvpn.mssfix = null; | |||
generic.enable_ipv6 = true; | |||
wireguard = { | |||
mtu = null; | |||
quantum_resistant = "auto"; | |||
rotation_interval = null; | |||
# DAITA (https://mullvad.net/en/vpn/daita) can be configured here. | |||
daita = { | |||
enabled = true; | |||
use_multihop_if_necessary = true; | |||
}; | |||
}; | |||
dns_options = { | |||
state = "default"; | |||
custom_options.addresses = [ ]; # Configure custom DNS providers here. | |||
default_options = { | |||
# Configure your DNS blocking. (Only usable if the option above is an empty list.) | |||
block_ads = true; | |||
block_trackers = true; | |||
block_malware = true; | |||
block_g*mbling = true; # Yes, this is exactly what you think it is. The Wiki's protection filter blocks this word. TODO: Someone in the trusted group needs to add the word. | |||
block_adult_content = true; | |||
block_social_media = true; | |||
}; | |||
}; | |||
}; | |||
settings_version = 10; # This configuration is up to date as of Mullvad VPN 2025.2. Usually, Mullvad will automatically migrate your configuration imperatively, but you may occasionally need to edit it here. | |||
show_beta_releases = false; | |||
} | |||
); | |||
in | |||
{ | |||
systemd = { | |||
services."mullvad-daemon".environment.MULLVAD_SETTINGS_DIR = "/var/lib/mullvad-vpn"; # This is necessary if `system.etc.overlay.mutable` is set to false, because Mullvad expects the settings directory to be writable. | |||
tmpfiles.settings."10-mullvad-settings"."/var/lib/mullvad-vpn/settings.json"."C+" = { | |||
group = "root"; | |||
mode = "0700"; # This isn't necessary for the settings file itself, but Mullvad will store your WireGuard private key on the same directory as the settings file, so ensure that /var/lib/mullvad-vpn or /etc/mullvad-vpn is owned by `root` and has -rwx------ permissions. | |||
user = "root"; | |||
argument = "${mullvadConfig}"; | |||
}; | |||
}; | |||
} | |||
</nowiki>}} | |||
=== Autostarting the GUI application === | |||
If you don't want to rely on Mullvad's autostart file in <code>~/.config/autostart</code>, (Perhaps because your configuration is [[Impermanence|stateless]]) you can set up an autostart file with <code>makeAutostartItem</code>: | |||
{{file|configuration.nix|nix|<nowiki> | |||
{ pkgs, ... }: | |||
let | |||
mullvad-autostart = pkgs.makeAutostartItem { | |||
name = "mullvad-vpn"; | |||
package = pkgs.mullvad-vpn; | |||
}; | |||
in | |||
{ | |||
environment.systemPackages = [ mullvad-autostart ]; | |||
} | |||
</nowiki>}} | |||
[[Category:VPN]] | |||
[[Category:Networking]] |
Latest revision as of 17:10, 20 March 2025
Mullvad VPN is a virtual private network service operated in Sweden by Mullvad VPN AB. It uses WireGuard under the hood and includes both a CLI and a GUI package.
Installation
To install Mullvad VPN, you need to enable it in your system options:

configuration.nix
{
services.mullvad-vpn.enable = true;
}
If you want to use the GUI application:

configuration.nix
{ pkgs, ... }:
{
services.mullvad-vpn.package = pkgs.mullvad-vpn;
}
Configuration
You can declaratively configure the Mullvad daemon by writing the settings.json
file to the location set by the MULLVAD_SETTINGS_DIR
environment variable. (/etc/mullvad-vpn
by default)
The example below sets up a few custom lists that let you select a whole continent as an exit location, or simply randomizes all countries if you don't care where your IP is coming from. A caveat with this configuration is that it must be manually updated every time Mullvad implements a server in a new country.

configuration.nix
{ pkgs, ... }:
let
mullvadConfig =
let
mkCountryList = id: countries: name: {
inherit id name;
locations = map (country: { inherit country; }) countries;
};
in
pkgs.writeText "mullvad-settings" (
builtins.toJSON {
allow_lan = true;
auto_connect = true;
block_when_disconnected = true;
api_access_methods = {
custom = [ ];
direct = {
access_method.built_in = "direct";
enabled = true;
id = "00000000-0000-0000-0000-000000000008";
name = "Direct";
};
mullvad_bridges = {
access_method.built_in = "bridge";
enabled = true;
id = "00000000-0000-0000-0000-000000000009";
name = "Mullvad Bridges";
};
encrypted_dns_proxy = {
access_method.built_in = "encrypted_dns_proxy";
enabled = true;
id = "00000000-0000-0000-0000-000000000010";
name = "Encrypted DNS proxy";
};
};
# The bridge options are only useful for OpenVPN. You can configure Wireguard under Shadowsocks with `obfuscation_settings` below.
bridge_state = "auto";
bridge_settings = {
bridge_type = "normal";
custom = null;
normal = {
location = "any";
ownership = "any";
providers = "any";
};
};
custom_lists.custom_lists =
let
northAmerica = [
"ca"
"mx"
"us"
];
southAmerica = [
"br"
"cl"
"co"
"pe"
];
europe = [
"al"
"at"
"be"
"bg"
"ch"
"cy"
"cz"
"de"
"dk"
"ee"
"es"
"fi"
"fr"
"gb"
"gr"
"hr"
"hu"
"ie"
"it"
"lv"
"nl"
"no"
"pl"
"pt"
"ro"
"rs"
"se"
"si"
"sk"
"tr"
"ua"
];
africa = [ "za" ];
asia = [
"hk"
"id"
"il"
"jp"
"sg"
"th"
];
oceania = [
"au"
"nz"
];
in
[
(mkCountryList "00000000-0000-0000-0000-000000000000" (
northAmerica ++ southAmerica ++ europe ++ africa ++ asia ++ oceania
) "All Countries")
(mkCountryList "00000000-0000-0000-0000-000000000002" northAmerica "North America")
(mkCountryList "00000000-0000-0000-0000-000000000003" southAmerica "South America")
(mkCountryList "00000000-0000-0000-0000-000000000004" europe "Europe")
(mkCountryList "00000000-0000-0000-0000-000000000005" africa "Africa")
(mkCountryList "00000000-0000-0000-0000-000000000006" asia "Asia")
(mkCountryList "00000000-0000-0000-0000-000000000007" oceania "Oceania")
];
# This is where you would configure IP overrides for Mullvad's servers, if you have any.
relay_overrides = [
/*
{
hostname = "se-sto-wg-001";
ipv4_addr_in = "[remote IPv4]";
ipv6_addr_in = "[remote IPv6]";
}
*/
];
relay_settings = {
normal = {
location.only.custom_list.list_id = "00000000-0000-0000-0000-000000000000"; # This selects all countries by default, so you get a random exit country every time you connect to your VPN.
openvpn_constraints.port = "any";
ownership = "any";
providers = "any";
tunnel_protocol = "any";
wireguard_constraints = {
entry_location.only.custom_list.list_id = "00000000-0000-0000-0000-000000000000"; # This will be ignored if DAITA is enabled, or if `use_multihop` is set to false.
ip_version = "any";
port = "any";
use_multihop = true;
};
};
};
obfuscation_settings = {
selected_obfuscation = "auto"; # This can be set to `shadowsocks`, for instance.
udp2tcp.port = "any";
};
tunnel_options = {
openvpn.mssfix = null;
generic.enable_ipv6 = true;
wireguard = {
mtu = null;
quantum_resistant = "auto";
rotation_interval = null;
# DAITA (https://mullvad.net/en/vpn/daita) can be configured here.
daita = {
enabled = true;
use_multihop_if_necessary = true;
};
};
dns_options = {
state = "default";
custom_options.addresses = [ ]; # Configure custom DNS providers here.
default_options = {
# Configure your DNS blocking. (Only usable if the option above is an empty list.)
block_ads = true;
block_trackers = true;
block_malware = true;
block_g*mbling = true; # Yes, this is exactly what you think it is. The Wiki's protection filter blocks this word. TODO: Someone in the trusted group needs to add the word.
block_adult_content = true;
block_social_media = true;
};
};
};
settings_version = 10; # This configuration is up to date as of Mullvad VPN 2025.2. Usually, Mullvad will automatically migrate your configuration imperatively, but you may occasionally need to edit it here.
show_beta_releases = false;
}
);
in
{
systemd = {
services."mullvad-daemon".environment.MULLVAD_SETTINGS_DIR = "/var/lib/mullvad-vpn"; # This is necessary if `system.etc.overlay.mutable` is set to false, because Mullvad expects the settings directory to be writable.
tmpfiles.settings."10-mullvad-settings"."/var/lib/mullvad-vpn/settings.json"."C+" = {
group = "root";
mode = "0700"; # This isn't necessary for the settings file itself, but Mullvad will store your WireGuard private key on the same directory as the settings file, so ensure that /var/lib/mullvad-vpn or /etc/mullvad-vpn is owned by `root` and has -rwx------ permissions.
user = "root";
argument = "${mullvadConfig}";
};
};
}
Autostarting the GUI application
If you don't want to rely on Mullvad's autostart file in ~/.config/autostart
, (Perhaps because your configuration is stateless) you can set up an autostart file with makeAutostartItem
:

configuration.nix
{ pkgs, ... }:
let
mullvad-autostart = pkgs.makeAutostartItem {
name = "mullvad-vpn";
package = pkgs.mullvad-vpn;
};
in
{
environment.systemPackages = [ mullvad-autostart ];
}