WireGuard: Difference between revisions
→Server setup: Change systemd-networkd networkConfig from deprecated IPForwarding option to new IPv4/IPv6Forwarding |
refactor; add ipv6 support |
||
Line 1: | Line 1: | ||
= | = Configuration Modules = | ||
In NixOS, there are several configuration modules for WireGuard. | |||
Depending on how your network is currently managed, refer to the | |||
relevant section for details. | |||
They have different options and capabilities. For example, | |||
< | <code>systemd.network</code> allows you to redirect network traffic | ||
based on the user, such as redirecting torrenting traffic, with | |||
RoutingPolicyRule option. See ArchWiki for further details. | |||
wg | |||
* NetworkManager | |||
* wg-quick | |||
* networking.wireguard | |||
* systemd.network | |||
= Use cases = | |||
This page describes how to set up WireGuard for two use cases. | |||
The first use case is Virtual Private Network, which makes several peers | |||
available on a private subnet. This is the basis for further | |||
configuration. | |||
The second use case is Internet proxy, which allows you to access the | |||
Internet via another peer. This use case depends on the first use | |||
case working correctly. | |||
== | == Network address translation == | ||
NAT maps the internal private IP address of the VPN to the public IP | |||
address of another peer. For all proxying setups, enable the | |||
following configuration | |||
<syntaxhighlight lang="nix"> | <syntaxhighlight lang="nix"> | ||
{ | { | ||
. | networking.nat = { | ||
enable = true; | |||
enableIPv6 = true; | |||
externalInterface = "ens6"; | |||
internalInterfaces = [ "wg0" ]; | |||
}; | }; | ||
} | |||
</syntaxhighlight> | |||
== External DNS with dnscrypt == | |||
You can use an external, encrypted DNS such as | |||
<syntaxhighlight lang="nix"> | |||
{ | |||
services.dnscrypt-proxy2 = { | |||
enable = true; | |||
upstreamDefaults = true; | |||
settings = { | |||
ipv6_servers = true; | |||
}; | |||
}; | |||
networking.nameservers = [ "127.0.0.1" ]; | |||
} | |||
</syntaxhighlight> | |||
== Proxy DNS with dnsmasq == | |||
On the proxy server, use the following config | |||
<syntaxhighlight lang="nix"> | |||
{ | |||
networking.firewall = { | |||
allowedTCPPorts = [ 53 ]; | |||
allowedUDPPorts = [ 53 ]; | |||
}; | |||
services = { | |||
dnsmasq = { | |||
enable = true; | |||
settings.interface = "wg0"; | |||
}; | }; | ||
}; | }; | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | On the proxy client, configure DNS options. For wg-quick, use the | ||
following | |||
<syntaxhighlight lang="nix"> | |||
{ | |||
networking.wg-quick.interfaces.wg0.dns = | |||
[ {internal v4 & v6 ip addr of server} ]; | |||
} | |||
</syntaxhighlight> | |||
= AllowedIPs = | |||
"Allowed IPs" are IP addresses or ranges. It is specified on a | |||
per-peer basis. All traffic to these addresses and ranges will be | |||
redirected to the peer. Common forms of allowed IPs are the | |||
following. | |||
* 192.168.26.9/32, a single internal IPv4 address | |||
* 192.168.26.0/24, a subnet | |||
* fd31:bf08:57cb::9/128, a single internal IPv6 address | |||
* fd31:bf08:57cb::/60, a subnet | |||
* 0.0.0.0/0, entire IPv4 address space, for proxying | |||
* ::/0, entire IPv6 address space, for proxying | |||
Notice that, in specifiying its subnet mask, some configuration | |||
modules can automatically configure network routes. | |||
Allowed IPs are unique to each peer. If there are peers with the same | |||
allowed IPs, network traffic will only be redirected to one of them. | |||
= WireGuard UDP Port = | |||
The default port is 51820. Some literature recommends changing this | |||
port to circumvent intentional blocking of WireGuard traffic. | |||
= Generate keys = | |||
WireGuard works with public-private key pairs. Computers, called peers | |||
in WireGuard, are identified by their unique public keys. Data is | |||
encrypted with the corresponding private key before transmission. | |||
Peers can only connect to a computer, if its public key is known to | |||
this computer. | |||
To generate a private key, and then derive the public key from it, you | |||
need the <code>wg</code> utility, available in | |||
<code>wireguard-tools</code> package. | |||
After installation, use the following commands to generate keys: | |||
<syntaxHighlight> | |||
$ umask 077 | |||
$ wg genkey > privatekey | |||
$ wg pubkey < privatekey > publickey | |||
</syntaxHighlight> | |||
You need to generate a new key for each peer. If you are setting up | |||
multiple WireGuard interfaces on the same computer, you can reuse the | |||
same key. | |||
Pay attention to the permission of the file. File permission may | |||
cause the WireGuard service to fail. Check system log to rule out | |||
this scenario. | |||
You can use ryamtm/agenix to declaratively store and manage the | |||
WireGuard key. | |||
= networking.wireguard = | |||
Note: does not automatically configure routes. Use | |||
<code>wg-quick</code> instead. | |||
== Peer setup == | |||
<syntaxhighlight lang="nix"> | <syntaxhighlight lang="nix"> | ||
{ config, ... }: | |||
{ | { | ||
.. | age.secrets.wg-key-peer0 = { | ||
file = "./secrets/wg-key-peer0.age"; | |||
}; | }; | ||
networking.firewall.allowedUDPPorts = [ 51820 ]; | |||
networking.wireguard = { | |||
# | enable = true; | ||
interfaces = { | |||
# network interface name. | |||
# You can name the interface arbitrarily. | |||
wg0 = { | |||
# the IP address and subnet of this peer | |||
ips = [ "fd31:bf08:57cb::9/128" "192.168.26.9/32" ]; | |||
# WireGuard Port | |||
# Must be accessible by peers | |||
listenPort = 51820; | |||
# Path to the private key file. | |||
# | |||
# Note: can also be included inline via the privateKey option, | |||
# but this makes the private key world-readable; | |||
# using privateKeyFile is recommended. | |||
privateKeyFile = config.age.secrets.wg-key-laptop.path; | |||
peers = [ | |||
{ | |||
name = "home nas"; | |||
publicKey = "ejmbag/fcc9OLp8K62zfV0NCbp056DnA0qpNixLXwCo="; | |||
allowedIPs = [ | |||
"fd31:bf08:57cb::8/128" | |||
"192.168.26.8/32" | |||
]; | |||
endpoint = "192.168.1.56:51820"; | |||
# ToDo: route to endpoint not automatically configured | |||
# https://wiki.archlinux.org/index.php/WireGuard#Loop_routing | |||
# https://discourse.nixos.org/t/solved-minimal-firewall-setup-for-wireguard-client/7577 | |||
# Send keepalives every 25 seconds. Important to keep NAT tables alive. | |||
# persistentKeepalive = 25; | |||
} | |||
]; | |||
}; | |||
}; | }; | ||
} | } | ||
# it’s not imperative but it does not know how to do it : | |||
# sudo ip route add 11.111.11.111 via 192.168.1.11 dev wlo1 | |||
# the ip adresse 11: external and 192: local. | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== Proxy server setup == | |||
Same as peer setup, skip the endpoint option, with the following | |||
addition, Remember to update the internal IP addresses in the script: | |||
< | <syntaxhighlight lang="nix"> | ||
{ | { | ||
# enable NAT | |||
# | |||
networking.nat = { | networking.nat = { | ||
enable = true; | enable = true; | ||
enableIPv6 = true; | enableIPv6 = true; | ||
externalInterface = " | externalInterface = "ens6"; | ||
internalInterfaces = [ "wg0" ]; | internalInterfaces = [ "wg0" ]; | ||
}; | }; | ||
networking.wireguard.interfaces.wg0 = { | |||
# This allows the wireguard server to route your traffic to the internet and hence be like a VPN | # This allows the wireguard server to route your traffic to the internet and hence be like a VPN | ||
postSetup = '' | |||
${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -j ACCEPT | ${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -j ACCEPT | ||
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE | ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE | ||
Line 220: | Line 234: | ||
# Undo the above | # Undo the above | ||
postShutdown = '' | |||
${pkgs.iptables}/bin/iptables -D FORWARD -i wg0 -j ACCEPT | ${pkgs.iptables}/bin/iptables -D FORWARD -i wg0 -j ACCEPT | ||
${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE | ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE | ||
Line 226: | Line 240: | ||
${pkgs.iptables}/bin/ip6tables -t nat -D POSTROUTING -s fdc9:281f:04d7:9ee9::1/64 -o eth0 -j MASQUERADE | ${pkgs.iptables}/bin/ip6tables -t nat -D POSTROUTING -s fdc9:281f:04d7:9ee9::1/64 -o eth0 -j MASQUERADE | ||
''; | ''; | ||
}; | }; | ||
} | } | ||
</ | </syntaxhighlight> | ||
== Proxy client setup == | |||
Same as peer setup, specify proxy server ip or domain in the endpoint | |||
option. Use <code>[ "0.0.0.0/0" "::/0" ]</code> as allowed IPs. | |||
= wg-quick = | |||
== Peer setup == | |||
<syntaxhighlight lang="nix"> | <syntaxhighlight lang="nix"> | ||
{ | { | ||
.. | networking.wg-quick.interfaces = { | ||
wg0 = { | |||
address = [ | |||
"fd31:bf08:57cb::9/128" | |||
"192.168.26.9/32" | |||
]; | |||
# use dnscrypt, or proxy dns as described above | |||
dns = [ "127.0.0.1" ]; | |||
privateKeyFile = config.age.secrets.wg-key-laptop.path; | |||
peers = [ | |||
{ | |||
# bt wg conf | |||
publicKey = "ejmbag/fcc9OLp8K62zfV0NCbp056DnA0qpNixLXwCo="; | |||
allowedIPs = [ | |||
"fd31:bf08:57cb::8/128" | |||
"192.168.26.8/32" | |||
]; | |||
endpoint = "192.168.1.56:51820"; | |||
} | |||
]; | |||
}; | }; | ||
}; | }; | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== | == Proxy server setup == | ||
< | Same as peer setup, skip the endpoint option, with the following | ||
addition: | |||
<syntaxhighlight lang="nix"> | |||
{ | { | ||
networking.wg-quick.interfaces = { | networking.wg-quick.interfaces = { | ||
wg0 = { | wg0 = { | ||
# This allows the wireguard server to route your traffic to the internet and hence be like a VPN | |||
postUp = '' | |||
${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -j ACCEPT | |||
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE | |||
${pkgs.iptables}/bin/ip6tables -A FORWARD -i wg0 -j ACCEPT | |||
{ | ${pkgs.iptables}/bin/ip6tables -t nat -A POSTROUTING -s fdc9:281f:04d7:9ee9::1/64 -o eth0 -j MASQUERADE | ||
''; | |||
# Undo the above | |||
preDown = '' | |||
${pkgs.iptables}/bin/iptables -D FORWARD -i wg0 -j ACCEPT | |||
} | ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE | ||
${pkgs.iptables}/bin/ip6tables -D FORWARD -i wg0 -j ACCEPT | |||
${pkgs.iptables}/bin/ip6tables -t nat -D POSTROUTING -s fdc9:281f:04d7:9ee9::1/64 -o eth0 -j MASQUERADE | |||
''; | |||
}; | }; | ||
}; | }; | ||
} | } | ||
</ | </syntaxhighlight> | ||
== Proxy client setup == | |||
Same as peer setup, specify proxy server ip or domain in the endpoint | |||
option. Use <code>[ "0.0.0.0/0" "::/0" ]</code> as allowed IPs. | |||
Optionally, configure proxy server as DNS server as described above. | |||
== Manually start and stop wg-quick == | |||
The above steps will set up a <tt>wg-quick-wg0.service</tt> systemd unit. | The above steps will set up a <tt>wg-quick-wg0.service</tt> systemd unit. | ||
Line 299: | Line 332: | ||
</syntaxHighlight> | </syntaxHighlight> | ||
If you have WireGuard configuration files that you want to use as-is (similarly how you would [https://wiki.debian.org/WireGuard#Step_2_-_Configuration configure WireGuard e.g. in Debian], without converting them to a declarative NixOS configuration, you can also configure <code>wg-quick</code> to use them. For example, if you have a configuration file <code>/etc/nixos/wireguard/wg0.conf</code>, add the following line to your <code>configuration.nix</code>: | == Reuse existing wg-quick config file == | ||
If you have WireGuard configuration files that you want to use as-is | |||
(similarly how you would | |||
[https://wiki.debian.org/WireGuard#Step_2_-_Configuration configure | |||
WireGuard e.g. in Debian], without converting them to a declarative | |||
NixOS configuration, you can also configure <code>wg-quick</code> to | |||
use them. For example, if you have a configuration file | |||
<code>/etc/nixos/wireguard/wg0.conf</code>, add the following line to | |||
your <code>configuration.nix</code>: | |||
<syntaxHighlight lang="nix"> | <syntaxHighlight lang="nix"> | ||
Line 307: | Line 349: | ||
This will set up a <code>wg-quick-wg0.service</code> systemd unit. | This will set up a <code>wg-quick-wg0.service</code> systemd unit. | ||
= | = systemd.network = | ||
== | == Peer setup == | ||
<syntaxhighlight lang="nix"> | <syntaxhighlight lang="nix"> | ||
{ | { | ||
networking.firewall.allowedUDPPorts = [ 51820 ]; | |||
networking.useNetworkd = true; | |||
networking.firewall.allowedUDPPorts = [51820]; | |||
networking.useNetworkd = true; | |||
systemd.network = { | systemd.network = { | ||
enable = true; | enable = true; | ||
networks."50-wg0" = { | |||
matchConfig.Name = "wg0"; | |||
address = [ | |||
# /32 and /128 specifies a single address | |||
# for use on this wg peer machine | |||
"fd31:bf08:57cb::7/128" | |||
"192.168.26.7/32" | |||
]; | |||
}; | |||
netdevs."50-wg0" = { | |||
netdevConfig = { | |||
Kind = "wireguard"; | |||
Name = "wg0"; | |||
}; | }; | ||
wireguardConfig = { | |||
ListenPort = 51820; | |||
# routing table identifier for addresses in AllowedIP | |||
# if empty, no route is configured. | |||
# see systemd netdev man page | |||
RouteTable = "main"; | |||
PrivateKeyFile = config.age.secrets.wg-key-vps.path; | |||
}; | }; | ||
wireguardPeers = [ | |||
{ | |||
# laptop wg conf | |||
PublicKey = "ronr+8v670J0CPb0xT5QLGMWDfE7+1g7HmC6YMdCIDk="; | |||
AllowedIPs = [ | |||
"fd31:bf08:57cb::9/128" | |||
"192.168.26.9/32" | |||
]; | |||
Endpoint = "192.168.1.26:51820"; | |||
} | |||
]; | |||
}; | }; | ||
}; | }; | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== | == Proxy server setup == | ||
Same as peer setup, skip the endpoint option, with the following | |||
addition: | |||
<syntaxhighlight lang="nix"> | <syntaxhighlight lang="nix"> | ||
{ | { | ||
networking.nat = { | |||
enable = true; | |||
enableIPv6 = true; | |||
externalInterface = "ens6"; | |||
internalInterfaces = [ "wg0" ]; | |||
}; | |||
systemd.network = { | systemd.network = { | ||
enable = true; | enable = true; | ||
networks."50-wg0" = { | |||
networkConfig = { | networkConfig = { | ||
IPv4Forwarding = true; | |||
IPv6Forwarding = true; | |||
}; | }; | ||
}; | }; | ||
}; | }; | ||
} | } | ||
</syntaxhighlight> | |||
</ | == Proxy client setup == | ||
Same as peer setup, specify proxy server ip or domain in the endpoint | |||
option. Use <code>[ "0.0.0.0/0" "::/0" ]</code> as allowed IPs. | |||
Optionally, configure proxy server as DNS server as described above. | |||
Note, systemd.network client seems to have issues. Use wg-quick | |||
client instead. | |||
= NetworkManager Proxy client setup = | |||
This is probably only useful on clients. Functionality is present in NetworkManager since version 1.20 but network-manager-applet can show and control wireguard connections since version 1.22 only (available since NixOS 21.05). | This is probably only useful on clients. Functionality is present in NetworkManager since version 1.20 but network-manager-applet can show and control wireguard connections since version 1.22 only (available since NixOS 21.05). | ||
Line 468: | Line 488: | ||
{{Commands|nmcli connection import type wireguard file thefile.conf}} | {{Commands|nmcli connection import type wireguard file thefile.conf}} | ||
The new VPN connection should be available, you still have to click on it to activate it. | The new VPN connection should be available, you still have to click on | ||
it to activate it. | |||
=Troubleshooting= | =Troubleshooting= |
Revision as of 12:21, 18 September 2025
Configuration Modules
In NixOS, there are several configuration modules for WireGuard. Depending on how your network is currently managed, refer to the relevant section for details.
They have different options and capabilities. For example,
systemd.network
allows you to redirect network traffic
based on the user, such as redirecting torrenting traffic, with
RoutingPolicyRule option. See ArchWiki for further details.
- NetworkManager
- wg-quick
- networking.wireguard
- systemd.network
Use cases
This page describes how to set up WireGuard for two use cases.
The first use case is Virtual Private Network, which makes several peers available on a private subnet. This is the basis for further configuration.
The second use case is Internet proxy, which allows you to access the Internet via another peer. This use case depends on the first use case working correctly.
Network address translation
NAT maps the internal private IP address of the VPN to the public IP address of another peer. For all proxying setups, enable the following configuration
{
networking.nat = {
enable = true;
enableIPv6 = true;
externalInterface = "ens6";
internalInterfaces = [ "wg0" ];
};
}
External DNS with dnscrypt
You can use an external, encrypted DNS such as
{
services.dnscrypt-proxy2 = {
enable = true;
upstreamDefaults = true;
settings = {
ipv6_servers = true;
};
};
networking.nameservers = [ "127.0.0.1" ];
}
Proxy DNS with dnsmasq
On the proxy server, use the following config
{
networking.firewall = {
allowedTCPPorts = [ 53 ];
allowedUDPPorts = [ 53 ];
};
services = {
dnsmasq = {
enable = true;
settings.interface = "wg0";
};
};
}
On the proxy client, configure DNS options. For wg-quick, use the following
{
networking.wg-quick.interfaces.wg0.dns =
[ {internal v4 & v6 ip addr of server} ];
}
AllowedIPs
"Allowed IPs" are IP addresses or ranges. It is specified on a per-peer basis. All traffic to these addresses and ranges will be redirected to the peer. Common forms of allowed IPs are the following.
- 192.168.26.9/32, a single internal IPv4 address
- 192.168.26.0/24, a subnet
- fd31:bf08:57cb::9/128, a single internal IPv6 address
- fd31:bf08:57cb::/60, a subnet
- 0.0.0.0/0, entire IPv4 address space, for proxying
- ::/0, entire IPv6 address space, for proxying
Notice that, in specifiying its subnet mask, some configuration modules can automatically configure network routes.
Allowed IPs are unique to each peer. If there are peers with the same allowed IPs, network traffic will only be redirected to one of them.
WireGuard UDP Port
The default port is 51820. Some literature recommends changing this port to circumvent intentional blocking of WireGuard traffic.
Generate keys
WireGuard works with public-private key pairs. Computers, called peers in WireGuard, are identified by their unique public keys. Data is encrypted with the corresponding private key before transmission.
Peers can only connect to a computer, if its public key is known to this computer.
To generate a private key, and then derive the public key from it, you
need the wg
utility, available in
wireguard-tools
package.
After installation, use the following commands to generate keys:
$ umask 077
$ wg genkey > privatekey
$ wg pubkey < privatekey > publickey
You need to generate a new key for each peer. If you are setting up multiple WireGuard interfaces on the same computer, you can reuse the same key.
Pay attention to the permission of the file. File permission may cause the WireGuard service to fail. Check system log to rule out this scenario.
You can use ryamtm/agenix to declaratively store and manage the WireGuard key.
networking.wireguard
Note: does not automatically configure routes. Use
wg-quick
instead.
Peer setup
{ config, ... }:
{
age.secrets.wg-key-peer0 = {
file = "./secrets/wg-key-peer0.age";
};
networking.firewall.allowedUDPPorts = [ 51820 ];
networking.wireguard = {
enable = true;
interfaces = {
# network interface name.
# You can name the interface arbitrarily.
wg0 = {
# the IP address and subnet of this peer
ips = [ "fd31:bf08:57cb::9/128" "192.168.26.9/32" ];
# WireGuard Port
# Must be accessible by peers
listenPort = 51820;
# Path to the private key file.
#
# Note: can also be included inline via the privateKey option,
# but this makes the private key world-readable;
# using privateKeyFile is recommended.
privateKeyFile = config.age.secrets.wg-key-laptop.path;
peers = [
{
name = "home nas";
publicKey = "ejmbag/fcc9OLp8K62zfV0NCbp056DnA0qpNixLXwCo=";
allowedIPs = [
"fd31:bf08:57cb::8/128"
"192.168.26.8/32"
];
endpoint = "192.168.1.56:51820";
# ToDo: route to endpoint not automatically configured
# https://wiki.archlinux.org/index.php/WireGuard#Loop_routing
# https://discourse.nixos.org/t/solved-minimal-firewall-setup-for-wireguard-client/7577
# Send keepalives every 25 seconds. Important to keep NAT tables alive.
# persistentKeepalive = 25;
}
];
};
};
}
# it’s not imperative but it does not know how to do it :
# sudo ip route add 11.111.11.111 via 192.168.1.11 dev wlo1
# the ip adresse 11: external and 192: local.
Proxy server setup
Same as peer setup, skip the endpoint option, with the following addition, Remember to update the internal IP addresses in the script:
{
# enable NAT
networking.nat = {
enable = true;
enableIPv6 = true;
externalInterface = "ens6";
internalInterfaces = [ "wg0" ];
};
networking.wireguard.interfaces.wg0 = {
# This allows the wireguard server to route your traffic to the internet and hence be like a VPN
postSetup = ''
${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -j ACCEPT
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE
${pkgs.iptables}/bin/ip6tables -A FORWARD -i wg0 -j ACCEPT
${pkgs.iptables}/bin/ip6tables -t nat -A POSTROUTING -s fdc9:281f:04d7:9ee9::1/64 -o eth0 -j MASQUERADE
'';
# Undo the above
postShutdown = ''
${pkgs.iptables}/bin/iptables -D FORWARD -i wg0 -j ACCEPT
${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE
${pkgs.iptables}/bin/ip6tables -D FORWARD -i wg0 -j ACCEPT
${pkgs.iptables}/bin/ip6tables -t nat -D POSTROUTING -s fdc9:281f:04d7:9ee9::1/64 -o eth0 -j MASQUERADE
'';
};
}
Proxy client setup
Same as peer setup, specify proxy server ip or domain in the endpoint
option. Use [ "0.0.0.0/0" "::/0" ]
as allowed IPs.
wg-quick
Peer setup
{
networking.wg-quick.interfaces = {
wg0 = {
address = [
"fd31:bf08:57cb::9/128"
"192.168.26.9/32"
];
# use dnscrypt, or proxy dns as described above
dns = [ "127.0.0.1" ];
privateKeyFile = config.age.secrets.wg-key-laptop.path;
peers = [
{
# bt wg conf
publicKey = "ejmbag/fcc9OLp8K62zfV0NCbp056DnA0qpNixLXwCo=";
allowedIPs = [
"fd31:bf08:57cb::8/128"
"192.168.26.8/32"
];
endpoint = "192.168.1.56:51820";
}
];
};
};
}
Proxy server setup
Same as peer setup, skip the endpoint option, with the following addition:
{
networking.wg-quick.interfaces = {
wg0 = {
# This allows the wireguard server to route your traffic to the internet and hence be like a VPN
postUp = ''
${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -j ACCEPT
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE
${pkgs.iptables}/bin/ip6tables -A FORWARD -i wg0 -j ACCEPT
${pkgs.iptables}/bin/ip6tables -t nat -A POSTROUTING -s fdc9:281f:04d7:9ee9::1/64 -o eth0 -j MASQUERADE
'';
# Undo the above
preDown = ''
${pkgs.iptables}/bin/iptables -D FORWARD -i wg0 -j ACCEPT
${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE
${pkgs.iptables}/bin/ip6tables -D FORWARD -i wg0 -j ACCEPT
${pkgs.iptables}/bin/ip6tables -t nat -D POSTROUTING -s fdc9:281f:04d7:9ee9::1/64 -o eth0 -j MASQUERADE
'';
};
};
}
Proxy client setup
Same as peer setup, specify proxy server ip or domain in the endpoint
option. Use [ "0.0.0.0/0" "::/0" ]
as allowed IPs.
Optionally, configure proxy server as DNS server as described above.
Manually start and stop wg-quick
The above steps will set up a wg-quick-wg0.service systemd unit.
You can start it by typing the following in your terminal:
sudo systemctl start wg-quick-wg0.service
To stop the service:
sudo systemctl stop wg-quick-wg0.service
Reuse existing wg-quick config file
If you have WireGuard configuration files that you want to use as-is
(similarly how you would
[https://wiki.debian.org/WireGuard#Step_2_-_Configuration configure
WireGuard e.g. in Debian], without converting them to a declarative
NixOS configuration, you can also configure wg-quick
to
use them. For example, if you have a configuration file
/etc/nixos/wireguard/wg0.conf
, add the following line to
your configuration.nix
:
networking.wg-quick.interfaces.wg0.configFile = "/etc/nixos/files/wireguard/wg0.conf";
This will set up a wg-quick-wg0.service
systemd unit.
systemd.network
Peer setup
{
networking.firewall.allowedUDPPorts = [ 51820 ];
networking.useNetworkd = true;
systemd.network = {
enable = true;
networks."50-wg0" = {
matchConfig.Name = "wg0";
address = [
# /32 and /128 specifies a single address
# for use on this wg peer machine
"fd31:bf08:57cb::7/128"
"192.168.26.7/32"
];
};
netdevs."50-wg0" = {
netdevConfig = {
Kind = "wireguard";
Name = "wg0";
};
wireguardConfig = {
ListenPort = 51820;
# routing table identifier for addresses in AllowedIP
# if empty, no route is configured.
# see systemd netdev man page
RouteTable = "main";
PrivateKeyFile = config.age.secrets.wg-key-vps.path;
};
wireguardPeers = [
{
# laptop wg conf
PublicKey = "ronr+8v670J0CPb0xT5QLGMWDfE7+1g7HmC6YMdCIDk=";
AllowedIPs = [
"fd31:bf08:57cb::9/128"
"192.168.26.9/32"
];
Endpoint = "192.168.1.26:51820";
}
];
};
};
}
Proxy server setup
Same as peer setup, skip the endpoint option, with the following addition:
{
networking.nat = {
enable = true;
enableIPv6 = true;
externalInterface = "ens6";
internalInterfaces = [ "wg0" ];
};
systemd.network = {
enable = true;
networks."50-wg0" = {
networkConfig = {
IPv4Forwarding = true;
IPv6Forwarding = true;
};
};
};
}
Proxy client setup
Same as peer setup, specify proxy server ip or domain in the endpoint
option. Use [ "0.0.0.0/0" "::/0" ]
as allowed IPs.
Optionally, configure proxy server as DNS server as described above.
Note, systemd.network client seems to have issues. Use wg-quick client instead.
NetworkManager Proxy client setup
This is probably only useful on clients. Functionality is present in NetworkManager since version 1.20 but network-manager-applet can show and control wireguard connections since version 1.22 only (available since NixOS 21.05).
If you intend to route all your traffic through the wireguard tunnel, the default configuration of the NixOS firewall will block the traffic because of rpfilter. You can either disable rpfilter altogether:
{ config, pkgs, lib, ... }:{
networking.firewall.checkReversePath = false;
}
In some cases not false but "loose" (with quotes) can work:
{ config, pkgs, lib, ... }:{
networking.firewall.checkReversePath = "loose";
}
Or you can adapt the rpfilter to ignore wireguard related traffic (replace 51820 by the port of your wireguard endpoint):
{ config, pkgs, lib, ... }:{
networking.firewall = {
# if packets are still dropped, they will show up in dmesg
logReversePathDrops = true;
# wireguard trips rpfilter up
extraCommands = ''
ip46tables -t mangle -I nixos-fw-rpfilter -p udp -m udp --sport 51820 -j RETURN
ip46tables -t mangle -I nixos-fw-rpfilter -p udp -m udp --dport 51820 -j RETURN
'';
extraStopCommands = ''
ip46tables -t mangle -D nixos-fw-rpfilter -p udp -m udp --sport 51820 -j RETURN || true
ip46tables -t mangle -D nixos-fw-rpfilter -p udp -m udp --dport 51820 -j RETURN || true
'';
};
}
Adding a wireguard connection to NetworkManager is not straightforward to do fully in gui, it is simpler to reuse a configuration file for wg-guick. For example:
[Interface] # your own IP on the wireguard network Address = 10.0.0.3/24, fd4:8e3:226:2e0::3/64 Table = auto PrivateKey = 0000000000000000000000000000000000000000000= [Peer] PublicKey = 1111111111111111111111111111111111111111111= # restrict this to the wireguard subnet if you don't want to route everything to the tunnel AllowedIPs = 0.0.0.0/0, ::/0 # ip and port of the peer Endpoint = 1.2.3.4:51820
Then run
nmcli connection import type wireguard file thefile.conf
The new VPN connection should be available, you still have to click on it to activate it.
Troubleshooting
Tunnel does not automatically connect despite persistentKeepalive being set
When using the privateKeyFile instead of privateKey setting, the generated WireGuard config file sets PersistentKeepalive as normal, but instead uses the generated PostUp script to set the private key for the tunnel after the tunnel has been started. Apparently the tunnel only automatically connects when the keepalive is set at the same time (i.e. through the config file) as the private key, or afterwards. A workaround is to also set PersistentKeepalive through the PostUp script using the wg command:
networking.wg-quick.interfaces = let
publicKey = "...";
in {
wg0 = {
# ...
privateKeyFile = "/path/to/keyfile";
# this is what we use instead of persistentKeepalive, the resulting PostUp
# script looks something like the following:
# wg set wg0 private-key <(cat /path/to/keyfile)
# wg set wg0 peer <public key> persistent-keepalive 25
postUp = ["wg set wgnet0 peer ${publicKey} persistent-keepalive 25"];
peers = [{
inherit publicKey; # set publicKey to the publicKey we've defined above
# ...
# Use postUp instead of this setting because otherwise it doesn't auto
# connect to the peer, apparently that doesn't happen if the private
# key is set after the PersistentKeepalive setting which happens if
# we load it from a file
#persistentKeepalive = 25;
}];
};
};
Server is reachable, but only some services are working
It might be, that the MTU of the network connecting the endpoints is smaller than the default (1500). By default the "option is set to" 1420, with an additional 80 due to wireguard overhead. Try adjusting it to something smaller:
networking.wireguard.interfaces.wg0.mtu = 1000;
#this is extremely small, bigger values can yield better performance.
#networking.wg-quick.interfaces.wg0.mtu = 1000; #if you use wq-quick
wg-quick issues with NetworkManager
Try systemd-resolved
This fixed the issue of wg connecting to the peer but not being able to access the internet or LAN.
networking.networkmanager.dns = "systemd-resolved";
services.resolved.enable = true;
See also
- WireGuard homepage
- Arch Wiki has an exhaustive guide, including troubleshooting tips
- List of WireGuard options supported by NixOS
- Blogpost by uint.one on replicating wg-quick with networkd
- Talk by @fpletz at NixCon 2018 about networkd and his WireGuard setup
- WireGuard Troubleshooting (on Web Archive) shows how to enable debug logs