Jump to content

WireGuard: Difference between revisions

From NixOS Wiki
Server setup: Change systemd-networkd networkConfig from deprecated IPForwarding option to new IPv4/IPv6Forwarding
Tie-ling (talk | contribs)
refactor; add ipv6 support
Line 1: Line 1:
[https://www.wireguard.com/ WireGuard] is a simple yet fast and modern VPN that utilizes state-of-the-art cryptography.


=Setting up WireGuard=
= Configuration Modules =
==Generate keypair==


Each peer needs to have a public-private keypair. The keys can be generated on any machine that already has WireGuard installed using the <code>wg</code> utility. If WireGuard isn't installed yet, it can be made available by adding <code>wireguard-tools</code> to <code>environment.systemPackages</code> or by running <code>nix-env -iA nixos.wireguard-tools</code> for NixOS based systems and <code>nix-env -iA nixpkgs.wireguard-tools</code> for non-NixOS systems.  
In NixOS, there are several configuration modules for WireGuard.
Depending on how your network is currently managed, refer to the
relevant section for details.


Creating a keypair is simple:
They have different options and capabilities.  For example,
<syntaxHighlight lang="nix">
<code>systemd.network</code> allows you to redirect network traffic
umask 077
based on the user, such as redirecting torrenting traffic, with
mkdir ~/wireguard-keys
RoutingPolicyRule option.  See ArchWiki for further details.
wg genkey > ~/wireguard-keys/private
 
wg pubkey < ~/wireguard-keys/private > ~/wireguard-keys/public
* NetworkManager
</syntaxHighlight>
* wg-quick
* networking.wireguard
* systemd.network
 
= Use cases =


You can create as many keypairs as you like for different connections or roles; it is also possible to reuse the same keypair for every connection.
This page describes how to set up WireGuard for two use cases.


Alternatively, you can use <tt>networking.wireguard.interfaces.[name].generatePrivateKeyFile</tt> option.
The first use case is Virtual Private Network, which makes several peers
available on a private subnet. This is the basis for further
configuration.


If you decide to use files for storing your private keys and also use networkd, you'll need to modify the private key file permissions.
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.


==== Troubleshooting Private Key File Resources ====
== Network address translation ==


* https://discourse.nixos.org/t/wg0-failed-to-read-private-key/31461/8
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


===Server setup===
Enable WireGuard on the server via <tt>/etc/nixos/configuration.nix</tt>:
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{
{
   ...
   networking.nat = {
 
    enable = true;
  # enable NAT
    enableIPv6 = true;
  networking.nat.enable = true;
    externalInterface = "ens6";
  networking.nat.externalInterface = "eth0";
    internalInterfaces = [ "wg0" ];
  networking.nat.internalInterfaces = [ "wg0" ];
  networking.firewall = {
    allowedUDPPorts = [ 51820 ];
   };
   };
}
</syntaxhighlight>


  networking.wireguard.enable = true;
== External DNS with dnscrypt ==
  networking.wireguard.interfaces = {
    # "wg0" is the network interface name. You can name the interface arbitrarily.
    wg0 = {
      # Determines the IP address and subnet of the server's end of the tunnel interface.
      ips = [ "10.100.0.1/24" ];


      # The port that WireGuard listens to. Must be accessible by the client.
You can use an external, encrypted DNS such as
      listenPort = 51820;


      # This allows the wireguard server to route your traffic to the internet and hence be like a VPN
<syntaxhighlight lang="nix">
      # For this to work you have to set the dnsserver IP of your router (or dnsserver of choice) in your clients
{
       postSetup = ''
  services.dnscrypt-proxy2 = {
        ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -o eth0 -j MASQUERADE
    enable = true;
      '';
    upstreamDefaults = true;
    settings = {
       ipv6_servers = true;
    };
  };
  networking.nameservers = [ "127.0.0.1" ];
}
</syntaxhighlight>


      # This undoes the above command
== Proxy DNS with dnsmasq ==
      postShutdown = ''
        ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.100.0.0/24 -o eth0 -j MASQUERADE
      '';


      # Path to the private key file.
On the proxy server, use the following config
      #
      # Note: The private key can also be included inline via the privateKey option,
      # but this makes the private key world-readable; thus, using privateKeyFile is
      # recommended.
      privateKeyFile = "path to private key file";


      peers = [
<syntaxhighlight lang="nix">
        # List of allowed peers.
{
        {  
  networking.firewall = {
          # Feel free to give a meaningful name
    allowedTCPPorts = [ 53 ];
          name = "Jane Doe";
    allowedUDPPorts = [ 53 ];
          # Public key of the peer (not a file path).
  };
          publicKey = "{client public key}";
  services = {
          # List of IPs assigned to this peer within the tunnel subnet. Used to configure routing.
    dnsmasq = {
          allowedIPs = [ "10.100.0.2/32" ];
      enable = true;
        }
      settings.interface = "wg0";
        {  
          name = "John Doe";
          publicKey = "{john doe's public key}";
          allowedIPs = [ "10.100.0.3/32" ];
        }
      ];
     };
     };
   };
   };
  ...
}
}
</syntaxhighlight>
</syntaxhighlight>


===Client setup===
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 = {
  networking.firewall = {
     file = "./secrets/wg-key-peer0.age";
     allowedUDPPorts = [ 51820 ]; # Clients and peers can use the same port, see listenport
   };
   };
  # Enable WireGuard
  networking.wireguard.enable = true;
  networking.wireguard.interfaces = {
    # "wg0" is the network interface name. You can name the interface arbitrarily.
    wg0 = {
      # Determines the IP address and subnet of the client's end of the tunnel interface.
      ips = [ "10.100.0.2/24" ];
      listenPort = 51820; # to match firewall allowedUDPPorts (without this wg uses random port numbers)


      # Path to the private key file.
  networking.firewall.allowedUDPPorts = [ 51820 ];
      #
      # Note: The private key can also be included inline via the privateKey option,
      # but this makes the private key world-readable; thus, using privateKeyFile is
      # recommended.
      privateKeyFile = "path to private key file";


       peers = [
  networking.wireguard = {
         # For a client configuration, one peer entry for the server will suffice.
    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
          # Public key of the server (not a file path).
        # Must be accessible by peers
          publicKey = "{server public key}";
        listenPort = 51820;


          # Forward all the traffic via VPN.
        # Path to the private key file.
          allowedIPs = [ "0.0.0.0/0" ];
        #
          # Or forward only particular subnets
        # Note: can also be included inline via the privateKey option,
          #allowedIPs = [ "10.100.0.1" "91.108.12.0/22" ];
        # but this makes the private key world-readable;
        # using privateKeyFile is recommended.
        privateKeyFile = config.age.secrets.wg-key-laptop.path;


           # Set this to the server IP and port.
        peers = [
          endpoint = "{server ip}: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
           {
 
            name = "home nas";
          # Send keepalives every 25 seconds. Important to keep NAT tables alive.
            publicKey = "ejmbag/fcc9OLp8K62zfV0NCbp056DnA0qpNixLXwCo=";
          persistentKeepalive = 25;
            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>


Multiple connections can be configured by configuring multiple interfaces under {{nixos:option|networking.wireguard.interfaces}}.
== Proxy server setup ==


===== Configuration example =====
Same as peer setup, skip the endpoint option, with the following
<syntaxhighlight lang="nixos">
addition, Remember to update the internal IP addresses in the script:
# Enable WireGuard
  networking.wireguard.enable = true;
  networking.wireguard.interfaces = {
    #"wg0" is the network interface name. You can name the interface arbitrarily.
    wgl0 = {
      # Determines the IP address and subnet of the client's end of the tunnel interface.
      ips = [ "192.168.27.88/32" ];
      listenPort = 1235; # to match firewall allowedUDPPorts (without this wg uses random port numbers)
      mtu = 1360;
      # Path to the private key file.
      # Note: The private key can also be included inline via the privateKey option,
      # but this makes the private key world-readable; thus, using privateKeyFile is
      # recommended.
      privateKeyFile = "/etc/nixos/workmachine/orbitingstar/wireguard_privatekey.key";
      peers = [
        # For a client configuration, one peer entry for the server will suffice.
      {
          # Public key of the server (not a file path).
          publicKey = "Iaaaaa5sUWc756dceJa8SL21X0TXpVFPPGdpNHaaaa=";
          # Forward all the traffic via VPN.
          allowedIPs = [ "192.168.27.64/27" "192.168.2.0/24" ];
          # Or forward only particular subnets
          #allowedIPs = [ "10.100.0.1" "11.111.11.0/22" ];
          # Set this to the server IP and port.
          name = "peer1";
          endpoint = "11.61.111.211:12343";  #  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 lang="nix">
 
==Setting up WireGuard server/client with wg-quick and dnsmasq==
===Server setup===
DNS requires opening TCP/UDP port 53.
<syntaxHighlight lang="nix">
{
{
  ...
   # enable NAT
   # Enable NAT
   networking.nat = {
   networking.nat = {
     enable = true;
     enable = true;
     enableIPv6 = true;
     enableIPv6 = true;
     externalInterface = "eth0";
     externalInterface = "ens6";
     internalInterfaces = [ "wg0" ];
     internalInterfaces = [ "wg0" ];
   };
   };
  # Open ports in the firewall
  networking.firewall = {
    allowedTCPPorts = [ 53 ];
    allowedUDPPorts = [ 53 51820 ];
  };
  ...
}
</syntaxHighlight>
The wg-quick setup is similar to the previous setup.
<syntaxHighlight lang="nix">
{
  ...
  networking.wg-quick.interfaces = {
    # "wg0" is the network interface name. You can name the interface arbitrarily.
    wg0 = {
      # Determines the IP/IPv6 address and subnet of the client's end of the tunnel interface
      address = [ "10.0.0.1/24" "fdc9:281f:04d7:9ee9::1/64" ];
      # The port that WireGuard listens to - recommended that this be changed from default
      listenPort = 51820;
      # Path to the server's private key
      privateKeyFile = "/root/wireguard-keys/privatekey";


  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
       postUp = ''
       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
       preDown = ''
       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
       '';
       '';
      peers = [
        { # peer0
          publicKey = "{client public key}";
          presharedKeyFile = "/root/wireguard-keys/preshared_from_peer0_key";
          allowedIPs = [ "10.0.0.2/32" "fdc9:281f:04d7:9ee9::2/128" ];
        }
        # More peers can be added here.
      ];
    };
   };
   };
  ...
}
}
</syntaxHighlight>
</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 ==


To enable dnsmasq and only serve DNS requests to the WireGuard interface add the following:
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{
{
   ...
   networking.wg-quick.interfaces = {
  services = {
    wg0 = {
    ...
      address = [
    dnsmasq = {
        "fd31:bf08:57cb::9/128"
       enable = true;
        "192.168.26.9/32"
       settings.interface = "wg0";
      ];
      # 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>


===Client setup===
== Proxy server setup ==
The client will now point DNS to the server.
 
<syntaxHighlight lang="nix">
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 = {
       address = [ "10.0.0.2/24" "fdc9:281f:04d7:9ee9::2/64" ];
       # This allows the wireguard server to route your traffic to the internet and hence be like a VPN
      dns = [ "10.0.0.1" "fdc9:281f:04d7:9ee9::1" ];
      postUp = ''
       privateKeyFile = "/root/wireguard-keys/privatekey";
        ${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
       peers = [
        ${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
          publicKey = "{server public key}";
       '';
          presharedKeyFile = "/root/wireguard-keys/preshared_from_peer0_key";
 
          allowedIPs = [ "0.0.0.0/0" "::/0" ];
       # Undo the above
          endpoint = "{server ip}:51820";
       preDown = ''
          persistentKeepalive = 25;
         ${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>
</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 ==


===Client setup (non-declaratively)===
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.


==Setting up WireGuard with systemd-networkd==
= systemd.network =


===Server setup===
== Peer setup ==


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{
{
  config,
   networking.firewall.allowedUDPPorts = [ 51820 ];
  pkgs,
   networking.useNetworkd = true;
  lib,
  ...
}: {
   networking.firewall.allowedUDPPorts = [51820];
   networking.useNetworkd = true;
   systemd.network = {
   systemd.network = {
     enable = true;
     enable = true;
     netdevs = {
     networks."50-wg0" = {
      "50-wg0" = {
      matchConfig.Name = "wg0";
        netdevConfig = {
      address = [
          Kind = "wireguard";
         # /32 and /128 specifies a single address
          Name = "wg0";
         # for use on this wg peer machine
          MTUBytes = "1300";
        "fd31:bf08:57cb::7/128"
         };
        "192.168.26.7/32"
         wireguardConfig = {
      ];
          PrivateKeyFile = "/run/keys/wireguard-privkey";
    };
          ListenPort = 51820;
    netdevs."50-wg0" = {
          RouteTable = "main"; # wg-quick creates routing entries automatically but we must use use this option in systemd.
      netdevConfig = {
        };
        Kind = "wireguard";
        wireguardPeers = [
        Name = "wg0";
          {
            PublicKey = "L4msD0mEG2ctKDtaMJW2y3cs1fT2LBRVV7iVlWZ2nZc=";
            AllowedIPs = ["10.100.0.2"];
          }
        ];
       };
       };
    };
      wireguardConfig = {
    networks.wg0 = {
        ListenPort = 51820;
      matchConfig.Name = "wg0";
        # routing table identifier for addresses in AllowedIP
      address = ["10.100.0.1/24"];
        # if empty, no route is configured.
      networkConfig = {
        # see systemd netdev man page
         IPMasquerade = "ipv4";
         RouteTable = "main";
         IPv4Forwarding = true; # if using IPv4
         PrivateKeyFile = config.age.secrets.wg-key-vps.path;
        IPv6Forwarding = false; # if using IPv6, but above we don't use IPv6
       };
       };
      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>


===Client setup===
== Proxy server setup ==
 
Same as peer setup, skip the endpoint option, with the following
addition:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{
{
   config,
   networking.nat = {
  pkgs,
    enable = true;
  lib,
    enableIPv6 = true;
  ...
    externalInterface = "ens6";
}: {
    internalInterfaces = [ "wg0" ];
  boot.kernelModules = [ "wireguard" ];
  };
 
   systemd.network = {
   systemd.network = {
     enable = true;
     enable = true;
     netdevs = {
     networks."50-wg0" = {
      "10-wg0" = {
        netdevConfig = {
          Kind = "wireguard";
          Name = "wg0";
          MTUBytes = "1300";
        };
        # See also man systemd.netdev (also contains info on the permissions of the key files)
        wireguardConfig = {
          # Don't use a file from the Nix store as these are world readable. Must be readable by the systemd-network user
          PrivateKeyFile = "/run/keys/wireguard-privkey";
          ListenPort = 9918;
        };
        wireguardPeers = [         
          # configuration since nixos-unstable/nixos-24.11
          {
            PublicKey = "OhApdFoOYnKesRVpnYRqwk3pdM247j8PPVH5K7aIKX0=";
            AllowedIPs = ["fc00::1/64" "10.100.0.1"];
            Endpoint = "{set this to the server ip}:51820";
          }
        ];
      };
    };
    networks.wg0 = {
      # See also man systemd.network
      matchConfig.Name = "wg0";
      # IP addresses the client interface will have
      address = [
        "fe80::3/64"
        "fc00::3/120"
        "10.100.0.2/24"
      ];
      DHCP = "no";
      dns = ["fc00::53"];
      ntp = ["fc00::123"];
      gateway = [
        "fc00::1"
        "10.100.0.1"
      ];
       networkConfig = {
       networkConfig = {
         IPv6AcceptRA = false;
         IPv4Forwarding = true;
        IPv6Forwarding = true;
       };
       };
     };
     };
   };
   };
}
}
</syntaxhighlight>


</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 =


==Setting up WireGuard with NetworkManager==
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
   '';
  };
}
Note: For the ip46tables command you need to add the reaction package.
Note: On NixOS 22.05 and earlier, the nixos-fw-rpfilter chain was in the raw table, not in the mangle table

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