Difference between revisions of "WireGuard"

From NixOS Wiki
Jump to: navigation, search
m (dead link, the extra slash does not redirect rightly.)
 
(41 intermediate revisions by 25 users not shown)
Line 1: Line 1:
=Setting up Wireguard=
+
=Setting up WireGuard=
 
==Generate keypair==
 
==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</code> to <code>environment.systemPackages</code> or by running <code>nix-env -iA wireguard</code>.
+
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.  
  
 
Creating a keypair is simple:
 
Creating a keypair is simple:
Line 14: Line 14:
 
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.
 
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.
  
==Server setup==
+
Alternatively, you can use <tt>networking.wireguard.interfaces.[name].generatePrivateKeyFile</tt> option.
Enable Wireguard on the server via <tt>/etc/nixos/configuration.nix</tt>:
+
 
 +
===Server setup===
 +
Enable WireGuard on the server via <tt>/etc/nixos/configuration.nix</tt>:
 
<syntaxHighlight lang="nix">
 
<syntaxHighlight lang="nix">
 
{
 
{
Line 24: Line 26:
 
   networking.nat.externalInterface = "eth0";
 
   networking.nat.externalInterface = "eth0";
 
   networking.nat.internalInterfaces = [ "wg0" ];
 
   networking.nat.internalInterfaces = [ "wg0" ];
 +
  networking.firewall = {
 +
    allowedUDPPorts = [ 51820 ];
 +
  };
  
 
   networking.wireguard.interfaces = {
 
   networking.wireguard.interfaces = {
Line 31: Line 36:
 
       ips = [ "10.100.0.1/24" ];
 
       ips = [ "10.100.0.1/24" ];
  
       # The port that Wireguard listens to. Must be accessible by the client.
+
       # The port that WireGuard listens to. Must be accessible by the client.
 
       listenPort = 51820;
 
       listenPort = 51820;
 +
 +
      # This allows the wireguard server to route your traffic to the internet and hence be like a VPN
 +
      # For this to work you have to set the dnsserver IP of your router (or dnsserver of choice) in your clients
 +
      postSetup = ''
 +
        ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -o eth0 -j MASQUERADE
 +
      '';
 +
 +
      # This undoes the above command
 +
      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.
 
       # Path to the private key file.
Line 60: Line 76:
 
</syntaxHighlight>
 
</syntaxHighlight>
  
==Client setup==
+
===Client setup===
 
<syntaxHighlight lang="nix">
 
<syntaxHighlight lang="nix">
 
{
 
{
 
   ...
 
   ...
   # Enable Wireguard
+
  networking.firewall = {
 +
    allowedUDPPorts = [ 51820 ]; # Clients and peers can use the same port, see listenport
 +
  };
 +
   # Enable WireGuard
 
   networking.wireguard.interfaces = {
 
   networking.wireguard.interfaces = {
 
     # "wg0" is the network interface name. You can name the interface arbitrarily.
 
     # "wg0" is the network interface name. You can name the interface arbitrarily.
Line 70: Line 89:
 
       # Determines the IP address and subnet of the client's end of the tunnel interface.
 
       # Determines the IP address and subnet of the client's end of the tunnel interface.
 
       ips = [ "10.100.0.2/24" ];
 
       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.
 
       # Path to the private key file.
Line 80: Line 100:
 
       peers = [
 
       peers = [
 
         # For a client configuration, one peer entry for the server will suffice.
 
         # For a client configuration, one peer entry for the server will suffice.
 +
 
         {
 
         {
 
           # Public key of the server (not a file path).
 
           # Public key of the server (not a file path).
Line 90: Line 111:
  
 
           # Set this to the server IP and port.
 
           # Set this to the server IP and port.
           endpoint = "{server ip}:51820";
+
           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
  
 
           # Send keepalives every 25 seconds. Important to keep NAT tables alive.
 
           # Send keepalives every 25 seconds. Important to keep NAT tables alive.
Line 104: Line 125:
 
Multiple connections can be configured by configuring multiple interfaces under {{nixos:option|networking.wireguard.interfaces}}.
 
Multiple connections can be configured by configuring multiple interfaces under {{nixos:option|networking.wireguard.interfaces}}.
  
 +
==Setting up WireGuard server/client with wg-quick and dnsmasq==
 +
===Server setup===
 +
DNS requires opening TCP/UDP port 53.
 +
<syntaxHighlight lang="nix">
 +
{
 +
  ...
 +
  # Enable NAT
 +
  networking.nat = {
 +
    enable = true;
 +
    enableIPv6 = true;
 +
    externalInterface = "eth0";
 +
    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";
 +
 +
      # 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
 +
      '';
 +
 +
      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>
 +
 +
To enable dnsmasq and only serve DNS requests to the WireGuard interface add the following:
 +
<syntaxHighlight lang="nix">
 +
{
 +
  ...
 +
  services = {
 +
    ...
 +
    dnsmasq = {
 +
      enable = true;
 +
      extraConfig = ''
 +
        interface=wg0
 +
      '';
 +
    };
 +
    ...
 +
  };
 +
  ...
 +
}
 +
</syntaxHighlight>
 +
 +
===Client setup===
 +
The client will now point DNS to the server.
 +
<syntaxHighlight lang="nix">
 +
{
 +
  ...
 +
  networking.wg-quick.interfaces = {
 +
    wg0 = {
 +
      address = [ "10.0.0.2/24" "fdc9:281f:04d7:9ee9::2/64" ];
 +
      dns = [ "10.0.0.1" "fdc9:281f:04d7:9ee9::1" ];
 +
      privateKeyFile = "/root/wireguard-keys/privatekey";
 +
     
 +
      peers = [
 +
        {
 +
          publicKey = "{server public key}";
 +
          presharedKeyFile = "/root/wireguard-keys/preshared_from_peer0_key";
 +
          allowedIPs = [ "0.0.0.0/0" "::/0" ];
 +
          endpoint = "{server ip}:51820";
 +
          persistentKeepalive = 25;
 +
        }
 +
      ];
 +
    };
 +
  };
 +
  ...
 +
}
 +
</syntaxHighlight>
 +
 +
==Setting up WireGuard with systemd-networkd==
  
=Setting up Wireguard with systemd-networkd=
+
===Server setup===
  
Please note, that networkd support in NixOS is still [https://nixos.org/nixos/options.html#usenetworkd experimental].
+
<syntaxHighlight lang="nix">
 +
{
 +
  config,
 +
  pkgs,
 +
  lib,
 +
  ...
 +
}: {
 +
  networking.firewall.allowedUDPPorts = [51820];
 +
  networking.useNetworkd = true; 
 +
  systemd.network = {
 +
    enable = true;
 +
    netdevs = {
 +
      "50-wg0" = {
 +
        netdevConfig = {
 +
          Kind = "wireguard";
 +
          Name = "wg0";
 +
          MTUBytes = "1300";
 +
        };
 +
        wireguardConfig = {
 +
          PrivateKeyFile = "/run/keys/wireguard-privkey";
 +
          ListenPort = 51820;
 +
        };
 +
        wireguardPeers = [
 +
          {
 +
            wireguardPeerConfig = {
 +
              PublicKey = "L4msD0mEG2ctKDtaMJW2y3cs1fT2LBRVV7iVlWZ2nZc=";
 +
              AllowedIPs = ["10.100.0.2"];
 +
            };
 +
          }
 +
        ];
 +
      };
 +
    };
 +
    networks.wg0 = {
 +
      matchConfig.Name = "wg0";
 +
      address = ["10.100.0.1/24"];
 +
      networkConfig = {
 +
        IPMasquerade = "ipv4";
 +
        IPForward = true;
 +
      };
 +
    };
 +
  };
 +
}
 +
</syntaxHighlight>
  
==Client setup==
+
===Client setup===
  
 
<syntaxHighlight lang="nix">
 
<syntaxHighlight lang="nix">
{ config, pkgs, lib, ... }:{
+
{
   boot.extraModulePackages = [ config.boot.kernelPackages.wireguard ];
+
  config,
 +
  pkgs,
 +
  lib,
 +
  ...
 +
}: {
 +
   boot.extraModulePackages = [config.boot.kernelPackages.wireguard];
 
   systemd.network = {
 
   systemd.network = {
 
     enable = true;
 
     enable = true;
Line 120: Line 299:
 
         netdevConfig = {
 
         netdevConfig = {
 
           Kind = "wireguard";
 
           Kind = "wireguard";
 +
          Name = "wg0";
 
           MTUBytes = "1300";
 
           MTUBytes = "1300";
          Name = "wg0";
 
 
         };
 
         };
         # See also man systemd.netdev
+
         # See also man systemd.netdev (also contains info on the permissions of the key files)
         extraConfig = ''
+
         wireguardConfig = {
          [WireGuard]
+
           # Don't use a file from the Nix store as these are world readable. Must be readable by the systemd.network user
           # Currently, the private key must be world readable, as the resulting netdev file will reside in the Nix store.
+
           PrivateKeyFile = "/run/keys/wireguard-privkey";
           PrivateKey=EMlybyTmXI/4z311xU9S3m82mC2OOMRfRM0Okiik83o=
+
           ListenPort = 9918;
           ListenPort=9918
+
        };
 
+
        wireguardPeers = [
          [WireGuardPeer]
+
           {
           PublicKey=OhApdFoOYnKesRVpnYRqwk3pdM247j8PPVH5K7aIKX0=
+
            wireguardPeerConfig = {
          AllowedIPs=fc00::1/64, 10.100.0.1
+
              PublicKey = "OhApdFoOYnKesRVpnYRqwk3pdM247j8PPVH5K7aIKX0=";
          Endpoint={set this to the server ip}:51820
+
              AllowedIPs = ["fc00::1/64" "10.100.0.1"];
         '';
+
              Endpoint = "{set this to the server ip}:51820";
 +
            };
 +
          }
 +
         ];
 
       };
 
       };
 
     };
 
     };
     networks = {
+
     networks.wg0 = {
 
       # See also man systemd.network
 
       # See also man systemd.network
       "40-wg0".extraConfig = ''
+
       matchConfig.Name = "wg0";
        [Match]
+
      # IP addresses the client interface will have
        Name=wg0
+
      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 = {
 +
        IPv6AcceptRA = false;
 +
      };
 +
    };
 +
  };
 +
}
 +
 
 +
</syntaxHighlight>
 +
 
 +
==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).
 +
 
 +
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:
 +
<syntaxHighlight lang="nix">
 +
{ config, pkgs, lib, ... }:{
 +
  networking.firewall.checkReversePath = false;
 +
}
 +
</syntaxHighlight>
 +
In some cases not '''false''' but '''"loose"''' (with quotes) can work:
 +
<syntaxHighlight lang="nix">
 +
{ config, pkgs, lib, ... }:{
 +
  networking.firewall.checkReversePath = "loose";
 +
}
 +
</syntaxHighlight>
 +
Or you can adapt the rpfilter to ignore wireguard related traffic (replace 51820 by the port of your wireguard endpoint):
 +
<syntaxHighlight lang="nix">
 +
{ 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
 +
  '';
 +
  };
 +
}
 +
</syntaxHighlight>
 +
 
 +
{{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:
 +
<pre>
 +
[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
 +
</pre>
 +
 
 +
Then run
 +
{{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.
 +
 
 +
=Troubleshooting=
 +
==Tunnel does not automatically connect despite persistentKeepalive being set==
 +
 
 +
When using the <i>privateKeyFile</i> instead of <i>privateKey</i> setting, the generated WireGuard config file sets <i>PersistentKeepalive</i> as normal, but instead uses the generated <i>PostUp</i> 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 <i>PersistentKeepalive</i> through the PostUp script using the <i>wg</i> command:
  
        [Network]
+
<syntaxHighlight lang="nix">
        DHCP=none
+
networking.wg-quick.interfaces = let
        IPv6AcceptRA=false
+
  publicKey = "...";
        Gateway=fc00::1
+
in {
        Gateway=10.100.0.1
+
  wg0 = {
        DNS=fc00::53
+
    # ...
        NTP=fc00::123
+
    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
 +
      # ...
  
        # IP addresses the client interface will have
+
      # Use postUp instead of this setting because otherwise it doesn't auto
        [Address]
+
      # connect to the peer, apparently that doesn't happen if the private
        Address=fe80::3/64
+
      # key is set after the PersistentKeepalive setting which happens if
        [Address]
+
      # we load it from a file
        Address=fc00::3/120
+
      #persistentKeepalive = 25;
        [Address]
+
     }];
        Address=10.100.0.2/24
 
      '';
 
     };
 
 
   };
 
   };
 
};
 
};
 +
 
</syntaxHighlight>
 
</syntaxHighlight>
 
  
 
=See also=
 
=See also=
* [https://www.wireguard.com/ Wireguard homepage]
+
* [https://www.wireguard.com/ WireGuard homepage]
* [https://nixos.org/nixos/options.html#wireguard List of Wireguard options supported by NixOS]
+
* [https://wiki.archlinux.org/index.php/WireGuard Arch Wiki] has an exhaustive guide, including troubleshooting tips
* [https://www.youtube.com/watch?v=us7V2NvsQRA Talk by @fpletz at NixCon 2018 about networkd and his Wireguard setup]
+
* [https://search.nixos.org/options?query=wireguard List of WireGuard options supported by NixOS]
 +
* [https://www.youtube.com/watch?v=us7V2NvsQRA Talk by @fpletz at NixCon 2018 about networkd and his WireGuard setup]
 +
* [https://web.archive.org/web/20210101230654/https://www.the-digital-life.com/wiki/wireguard-troubleshooting/ WireGuard Troubleshooting (on Web Archive)] shows how to enable debug logs
  
 
[[Category:Configuration]]
 
[[Category:Configuration]]

Latest revision as of 11:50, 15 April 2024

Setting up WireGuard

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 wg utility. If WireGuard isn't installed yet, it can be made available by adding wireguard-tools to environment.systemPackages or by running nix-env -iA nixos.wireguard-tools for NixOS based systems and nix-env -iA nixpkgs.wireguard-tools for non-NixOS systems.

Creating a keypair is simple:

umask 077
mkdir ~/wireguard-keys
wg genkey > ~/wireguard-keys/private
wg pubkey < ~/wireguard-keys/private > ~/wireguard-keys/public

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.

Alternatively, you can use networking.wireguard.interfaces.[name].generatePrivateKeyFile option.

Server setup

Enable WireGuard on the server via /etc/nixos/configuration.nix:

{
  ...

  # enable NAT
  networking.nat.enable = true;
  networking.nat.externalInterface = "eth0";
  networking.nat.internalInterfaces = [ "wg0" ];
  networking.firewall = {
    allowedUDPPorts = [ 51820 ];
  };

  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.
      listenPort = 51820;

      # This allows the wireguard server to route your traffic to the internet and hence be like a VPN
      # For this to work you have to set the dnsserver IP of your router (or dnsserver of choice) in your clients
      postSetup = ''
        ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -o eth0 -j MASQUERADE
      '';

      # This undoes the above command
      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.
      #
      # 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 = [
        # List of allowed peers.
        { # Feel free to give a meaning full name
          # Public key of the peer (not a file path).
          publicKey = "{client public key}";
          # List of IPs assigned to this peer within the tunnel subnet. Used to configure routing.
          allowedIPs = [ "10.100.0.2/32" ];
        }
        { # John Doe
          publicKey = "{john doe's public key}";
          allowedIPs = [ "10.100.0.3/32" ];
        }
      ];
    };
  };
  ...
}

Client setup

{
  ...
  networking.firewall = {
    allowedUDPPorts = [ 51820 ]; # Clients and peers can use the same port, see listenport
  };
  # Enable WireGuard
  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.
      #
      # 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 = [
        # For a client configuration, one peer entry for the server will suffice.

        {
          # Public key of the server (not a file path).
          publicKey = "{server public key}";

          # Forward all the traffic via VPN.
          allowedIPs = [ "0.0.0.0/0" ];
          # Or forward only particular subnets
          #allowedIPs = [ "10.100.0.1" "91.108.12.0/22" ];

          # Set this to the server IP and port.
          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

          # Send keepalives every 25 seconds. Important to keep NAT tables alive.
          persistentKeepalive = 25;
        }
      ];
    };
  };
  ...
}

Multiple connections can be configured by configuring multiple interfaces under networking.wireguard.interfaces.

Setting up WireGuard server/client with wg-quick and dnsmasq

Server setup

DNS requires opening TCP/UDP port 53.

{
  ...
  # Enable NAT
  networking.nat = {
    enable = true;
    enableIPv6 = true;
    externalInterface = "eth0";
    internalInterfaces = [ "wg0" ];
  };
  # Open ports in the firewall
  networking.firewall = {
    allowedTCPPorts = [ 53 ];
    allowedUDPPorts = [ 53 51820 ];
  };
  ...
}

The wg-quick setup is similar to the previous setup.

{
  ...
  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";

      # 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
      '';

      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.
      ];
    };
  };
  ...
}

To enable dnsmasq and only serve DNS requests to the WireGuard interface add the following:

{
  ...
  services = {
    ...
    dnsmasq = {
      enable = true;
      extraConfig = ''
        interface=wg0
      '';
    };
    ...
  };
  ...
}

Client setup

The client will now point DNS to the server.

{
  ...
  networking.wg-quick.interfaces = {
    wg0 = {
      address = [ "10.0.0.2/24" "fdc9:281f:04d7:9ee9::2/64" ];
      dns = [ "10.0.0.1" "fdc9:281f:04d7:9ee9::1" ];
      privateKeyFile = "/root/wireguard-keys/privatekey";
      
      peers = [
        {
          publicKey = "{server public key}";
          presharedKeyFile = "/root/wireguard-keys/preshared_from_peer0_key";
          allowedIPs = [ "0.0.0.0/0" "::/0" ];
          endpoint = "{server ip}:51820";
          persistentKeepalive = 25;
        }
      ];
    };
  };
  ...
}

Setting up WireGuard with systemd-networkd

Server setup

{
  config,
  pkgs,
  lib,
  ...
}: {
  networking.firewall.allowedUDPPorts = [51820];
  networking.useNetworkd = true;  
  systemd.network = {
    enable = true;
    netdevs = {
      "50-wg0" = {
        netdevConfig = {
          Kind = "wireguard";
          Name = "wg0";
          MTUBytes = "1300";
        };
        wireguardConfig = {
          PrivateKeyFile = "/run/keys/wireguard-privkey";
          ListenPort = 51820;
        };
        wireguardPeers = [
          {
            wireguardPeerConfig = {
              PublicKey = "L4msD0mEG2ctKDtaMJW2y3cs1fT2LBRVV7iVlWZ2nZc=";
              AllowedIPs = ["10.100.0.2"];
            };
          }
        ];
      };
    };
    networks.wg0 = {
      matchConfig.Name = "wg0";
      address = ["10.100.0.1/24"];
      networkConfig = {
        IPMasquerade = "ipv4";
        IPForward = true;
      };
    };
  };
}

Client setup

{
  config,
  pkgs,
  lib,
  ...
}: {
  boot.extraModulePackages = [config.boot.kernelPackages.wireguard];
  systemd.network = {
    enable = true;
    netdevs = {
      "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 = [
          {
            wireguardPeerConfig = {
              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 = {
        IPv6AcceptRA = false;
      };
    };
  };
}

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).

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: 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;
    }];
  };
};

See also