1 { config, lib, options, pkgs, ... }:
7 cfg = config.networking.wireguard;
8 opt = options.networking.wireguard;
10 kernel = config.boot.kernelPackages;
14 interfaceOpts = { ... }: {
19 example = [ "192.168.2.1/24" ];
21 type = with types; listOf str;
22 description = "The IP addresses of the interface.";
25 privateKey = mkOption {
26 example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
27 type = with types; nullOr str;
30 Base64 private key generated by {command}`wg genkey`.
32 Warning: Consider using privateKeyFile instead if you do not
33 want to store the key in the world-readable Nix store.
37 generatePrivateKeyFile = mkOption {
41 Automatically generate a private key with
42 {command}`wg genkey`, at the privateKeyFile location.
46 privateKeyFile = mkOption {
47 example = "/private/wireguard_key";
48 type = with types; nullOr str;
51 Private key file as generated by {command}`wg genkey`.
55 listenPort = mkOption {
57 type = with types; nullOr int;
60 16-bit port for listening. Optional; if not specified,
61 automatically generated based on interface name.
66 example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
68 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
70 Commands called at the start of the interface setup.
74 postSetup = mkOption {
75 example = literalExpression ''
76 '''printf "nameserver 10.200.100.1" | ''${pkgs.openresolv}/bin/resolvconf -a wg0 -m 0'''
79 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
80 description = "Commands called at the end of the interface setup.";
83 preShutdown = mkOption {
84 example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"'';
86 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
88 Commands called before shutting down the interface.
92 postShutdown = mkOption {
93 example = literalExpression ''"''${pkgs.openresolv}/bin/resolvconf -d wg0"'';
95 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
96 description = "Commands called after shutting down the interface.";
103 The kernel routing table to add this interface's
104 associated routes to. Setting this is useful for e.g. policy routing
105 ("ip rule") or virtual routing and forwarding ("ip vrf"). Both
106 numeric table IDs and table names (/etc/rt_tables) can be used.
113 description = "Peers linked to the interface.";
114 type = with types; listOf (submodule peerOpts);
117 allowedIPsAsRoutes = mkOption {
122 Determines whether to add allowed IPs as routes or not.
126 socketNamespace = mkOption {
128 type = with types; nullOr str;
129 example = "container";
130 description = ''The pre-existing network namespace in which the
131 WireGuard interface is created, and which retains the socket even if the
132 interface is moved via {option}`interfaceNamespace`. When
133 `null`, the interface is created in the init namespace.
134 See [documentation](https://www.wireguard.com/netns/).
138 interfaceNamespace = mkOption {
140 type = with types; nullOr str;
142 description = ''The pre-existing network namespace the WireGuard
143 interface is moved to. The special value `init` means
144 the init namespace. When `null`, the interface is not
146 See [documentation](https://www.wireguard.com/netns/).
152 type = with types; nullOr str;
153 example = "0x6e6978";
155 Mark all wireguard packets originating from
156 this interface with the given firewall mark. The firewall mark can be
157 used in firewalls or policy routing to filter the wireguard packets.
158 This can be useful for setup where all traffic goes through the
159 wireguard tunnel, because the wireguard packets need to be routed
166 type = with types; nullOr int;
169 Set the maximum transmission unit in bytes for the wireguard
170 interface. Beware that the wireguard packets have a header that may
171 add up to 80 bytes to the mtu. By default, the MTU is (1500 - 80) =
172 1420. However, if the MTU of the upstream network is lower, the MTU
173 of the wireguard network has to be adjusted as well.
179 type = with types; nullOr int;
182 Set the metric of routes related to this Wireguard interface.
186 dynamicEndpointRefreshSeconds = mkOption {
189 type = with types; int;
191 Periodically refresh the endpoint hostname or address for all peers.
192 Allows WireGuard to notice DNS and IPv4/IPv6 connectivity changes.
193 This option can be set or overridden for individual peers.
195 Setting this to `0` disables periodic refresh.
198 When {option}`networking.wireguard.useNetworkd` is enabled, this
199 option deletes the Wireguard interface and brings it back up by
200 reconfiguring the network with `networkctl reload` on every refresh.
201 This could have adverse effects on your network and cause brief
202 connectivity blips. See [systemd/systemd#9911](https://github.com/systemd/systemd/issues/9911)
203 for an upstream feature request that can make this less hacky.
220 [ "/" "-" " " "+" "=" ]
221 [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ]
222 self.config.publicKey;
223 defaultText = literalExpression "publicKey";
226 description = "Name used to derive peer unit name.";
229 publicKey = mkOption {
230 example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
231 type = types.singleLineStr;
232 description = "The base64 public key of the peer.";
235 presharedKey = mkOption {
237 example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
238 type = with types; nullOr str;
240 Base64 preshared key generated by {command}`wg genpsk`.
241 Optional, and may be omitted. This option adds an additional layer of
242 symmetric-key cryptography to be mixed into the already existing
243 public-key cryptography, for post-quantum resistance.
245 Warning: Consider using presharedKeyFile instead if you do not
246 want to store the key in the world-readable Nix store.
250 presharedKeyFile = mkOption {
252 example = "/private/wireguard_psk";
253 type = with types; nullOr str;
255 File pointing to preshared key as generated by {command}`wg genpsk`.
256 Optional, and may be omitted. This option adds an additional layer of
257 symmetric-key cryptography to be mixed into the already existing
258 public-key cryptography, for post-quantum resistance.
262 allowedIPs = mkOption {
263 example = [ "10.192.122.3/32" "10.192.124.1/24" ];
264 type = with types; listOf str;
265 description = ''List of IP (v4 or v6) addresses with CIDR masks from
266 which this peer is allowed to send incoming traffic and to which
267 outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
268 be specified for matching all IPv4 addresses, and ::/0 may be specified
269 for matching all IPv6 addresses.'';
272 endpoint = mkOption {
274 example = "demo.wireguard.io:12913";
275 type = with types; nullOr str;
277 Endpoint IP or hostname of the peer, followed by a colon,
278 and then a port number of the peer.
280 Warning for endpoints with changing IPs:
281 The WireGuard kernel side cannot perform DNS resolution.
282 Thus DNS resolution is done once by the `wg` userspace
283 utility, when setting up WireGuard. Consequently, if the IP address
284 behind the name changes, WireGuard will not notice.
285 This is especially common for dynamic-DNS setups, but also applies to
286 any other DNS-based setup.
287 If you do not use IP endpoints, you likely want to set
288 {option}`networking.wireguard.dynamicEndpointRefreshSeconds`
289 to refresh the IPs periodically.
293 dynamicEndpointRefreshSeconds = mkOption {
295 defaultText = literalExpression "config.networking.wireguard.interfaces.<name>.dynamicEndpointRefreshSeconds";
297 type = with types; nullOr int;
299 Periodically re-execute the `wg` utility every
300 this many seconds in order to let WireGuard notice DNS / hostname
303 Setting this to `0` disables periodic reexecution.
306 This peer-level setting is not available when {option}`networking.wireguard.useNetworkd`
307 is enabled. The interface-level setting may be used instead.
312 dynamicEndpointRefreshRestartSeconds = mkOption {
315 type = with types; nullOr ints.unsigned;
317 When the dynamic endpoint refresh that is configured via
318 dynamicEndpointRefreshSeconds exits (likely due to a failure),
319 restart that service after this many seconds.
321 If set to `null` the value of
322 {option}`networking.wireguard.dynamicEndpointRefreshSeconds`
323 will be used as the default.
327 persistentKeepalive = mkOption {
329 type = with types; nullOr int;
331 description = ''This is optional and is by default off, because most
332 users will not need it. It represents, in seconds, between 1 and 65535
333 inclusive, how often to send an authenticated empty packet to the peer,
334 for the purpose of keeping a stateful firewall or NAT mapping valid
335 persistently. For example, if the interface very rarely sends traffic,
336 but it might at anytime receive traffic from a peer, and it is behind
337 NAT, the interface might benefit from having a persistent keepalive
338 interval of 25 seconds; however, most users will not need this.'';
345 generateKeyServiceUnit = name: values:
346 assert values.generatePrivateKeyFile;
347 nameValuePair "wireguard-${name}-key"
349 description = "WireGuard Tunnel - ${name} - Key Generator";
350 wantedBy = [ "wireguard-${name}.service" ];
351 requiredBy = [ "wireguard-${name}.service" ];
352 before = [ "wireguard-${name}.service" ];
353 path = with pkgs; [ wireguard-tools ];
357 RemainAfterExit = true;
363 # If the parent dir does not already exist, create it.
364 # Otherwise, does nothing, keeping existing permissions intact.
365 mkdir -p --mode 0755 "${dirOf values.privateKeyFile}"
367 if [ ! -f "${values.privateKeyFile}" ]; then
368 # Write private key file with atomically-correct permissions.
369 (set -e; umask 077; wg genkey > "${values.privateKeyFile}")
374 peerUnitServiceName = interfaceName: peerName: dynamicRefreshEnabled:
376 refreshSuffix = optionalString dynamicRefreshEnabled "-refresh";
378 "wireguard-${interfaceName}-peer-${peerName}${refreshSuffix}";
380 dynamicRefreshSeconds = interfaceCfg: peer:
381 if peer.dynamicEndpointRefreshSeconds != null
382 then peer.dynamicEndpointRefreshSeconds
383 else interfaceCfg.dynamicEndpointRefreshSeconds;
385 generatePeerUnit = { interfaceName, interfaceCfg, peer }:
388 if peer.presharedKey != null
389 then pkgs.writeText "wg-psk" peer.presharedKey
390 else peer.presharedKeyFile;
391 src = interfaceCfg.socketNamespace;
392 dst = interfaceCfg.interfaceNamespace;
393 ip = nsWrap "ip" src dst;
394 wg = nsWrap "wg" src dst;
395 dynamicEndpointRefreshSeconds = dynamicRefreshSeconds interfaceCfg peer;
396 dynamicRefreshEnabled = dynamicEndpointRefreshSeconds != 0;
397 # We generate a different name (a `-refresh` suffix) when `dynamicEndpointRefreshSeconds`
398 # to avoid that the same service switches `Type` (`oneshot` vs `simple`),
399 # with the intent to make scripting more obvious.
400 serviceName = peerUnitServiceName interfaceName peer.name dynamicRefreshEnabled;
401 in nameValuePair serviceName
403 description = "WireGuard Peer - ${interfaceName} - ${peer.name}"
404 + optionalString (peer.name != peer.publicKey) " (${peer.publicKey})";
405 requires = [ "wireguard-${interfaceName}.service" ];
406 wants = [ "network-online.target" ];
407 after = [ "wireguard-${interfaceName}.service" "network-online.target" ];
408 wantedBy = [ "wireguard-${interfaceName}.service" ];
409 environment.DEVICE = interfaceName;
410 environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity";
411 path = with pkgs; [ iproute2 wireguard-tools ];
414 if !dynamicRefreshEnabled
418 RemainAfterExit = true;
422 Type = "simple"; # re-executes 'wg' indefinitely
423 # Note that `Type = "oneshot"` services with `RemainAfterExit = true`
424 # cannot be used with systemd timers (see `man systemd.timer`),
425 # which is why `simple` with a loop is the best choice here.
426 # It also makes starting and stopping easiest.
428 # Restart if the service exits (e.g. when wireguard gives up after "Name or service not known" dns failures):
430 RestartSec = if null != peer.dynamicEndpointRefreshRestartSeconds
431 then peer.dynamicEndpointRefreshRestartSeconds
432 else dynamicEndpointRefreshSeconds;
434 unitConfig = lib.optionalAttrs dynamicRefreshEnabled {
435 StartLimitIntervalSec = 0;
439 wg_setup = concatStringsSep " " (
440 [ ''${wg} set ${interfaceName} peer "${peer.publicKey}"'' ]
441 ++ optional (psk != null) ''preshared-key "${psk}"''
442 ++ optional (peer.endpoint != null) ''endpoint "${peer.endpoint}"''
443 ++ optional (peer.persistentKeepalive != null) ''persistent-keepalive "${toString peer.persistentKeepalive}"''
444 ++ optional (peer.allowedIPs != []) ''allowed-ips "${concatStringsSep "," peer.allowedIPs}"''
447 optionalString interfaceCfg.allowedIPsAsRoutes
448 (concatMapStringsSep "\n"
450 ''${ip} route replace "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}" ${optionalString (interfaceCfg.metric != null) "metric ${toString interfaceCfg.metric}"}''
456 ${optionalString (dynamicEndpointRefreshSeconds != 0) ''
457 # Re-execute 'wg' periodically to notice DNS / hostname changes.
458 # Note this will not time out on transient DNS failures such as DNS names
459 # because we have set 'WG_ENDPOINT_RESOLUTION_RETRIES=infinity'.
460 # Also note that 'wg' limits its maximum retry delay to 20 seconds as of writing.
461 while ${wg_setup}; do
462 sleep "${toString dynamicEndpointRefreshSeconds}";
468 route_destroy = optionalString interfaceCfg.allowedIPsAsRoutes
469 (concatMapStringsSep "\n"
471 ''${ip} route delete "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}"''
474 ${wg} set "${interfaceName}" peer "${peer.publicKey}" remove
479 # the target is required to start new peer units when they are added
480 generateInterfaceTarget = name: values:
482 mkPeerUnit = peer: (peerUnitServiceName name peer.name (dynamicRefreshSeconds values peer != 0)) + ".service";
484 nameValuePair "wireguard-${name}"
486 description = "WireGuard Tunnel - ${name}";
487 wantedBy = [ "multi-user.target" ];
488 wants = [ "wireguard-${name}.service" ] ++ map mkPeerUnit values.peers;
492 generateInterfaceUnit = name: values:
493 # exactly one way to specify the private key must be set
494 #assert (values.privateKey != null) != (values.privateKeyFile != null);
495 let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey;
496 src = values.socketNamespace;
497 dst = values.interfaceNamespace;
498 ipPreMove = nsWrap "ip" src null;
499 ipPostMove = nsWrap "ip" src dst;
500 wg = nsWrap "wg" src dst;
501 ns = if dst == "init" then "1" else dst;
504 nameValuePair "wireguard-${name}"
506 description = "WireGuard Tunnel - ${name}";
507 after = [ "network-pre.target" ];
508 wants = [ "network.target" ];
509 before = [ "network.target" ];
510 environment.DEVICE = name;
511 path = with pkgs; [ kmod iproute2 wireguard-tools ];
515 RemainAfterExit = true;
518 script = concatStringsSep "\n" (
519 optional (!config.boot.isContainer) "modprobe wireguard || true"
522 ''${ipPreMove} link add dev "${name}" type wireguard''
524 ++ optional (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''
525 ++ optional (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}''
527 ''${ipPostMove} address add "${ip}" dev "${name}"''
530 (concatStringsSep " " (
531 [ ''${wg} set "${name}" private-key "${privKey}"'' ]
532 ++ optional (values.listenPort != null) ''listen-port "${toString values.listenPort}"''
533 ++ optional (values.fwMark != null) ''fwmark "${values.fwMark}"''
535 ''${ipPostMove} link set up dev "${name}"''
541 ${values.preShutdown}
542 ${ipPostMove} link del dev "${name}"
543 ${values.postShutdown}
547 nsWrap = cmd: src: dst:
549 nsList = filter (ns: ns != null) [ src dst ];
552 if (length nsList > 0 && ns != "init") then ''ip netns exec "${ns}" "${cmd}"'' else cmd;
561 networking.wireguard = {
565 Whether to enable WireGuard.
568 By default, this module is powered by a script-based backend. You can
569 enable the networkd backend with {option}`networking.wireguard.useNetworkd`.
573 # 2019-05-25: Backwards compatibility.
574 default = cfg.interfaces != {};
575 defaultText = literalExpression "config.${opt.interfaces} != { }";
579 interfaces = mkOption {
581 WireGuard interfaces.
586 ips = [ "192.168.20.4/24" ];
587 privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
589 { allowedIPs = [ "192.168.20.1/32" ];
590 publicKey = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
591 endpoint = "demo.wireguard.io:12913"; }
595 type = with types; attrsOf (submodule interfaceOpts);
603 ###### implementation
605 config = mkIf cfg.enable (let
607 (mapAttrsToList (interfaceName: interfaceCfg:
608 map (peer: { inherit interfaceName interfaceCfg peer;}) interfaceCfg.peers
612 assertions = (attrValues (
613 mapAttrs (name: value: {
614 assertion = (value.privateKey != null) != (value.privateKeyFile != null);
615 message = "Either networking.wireguard.interfaces.${name}.privateKey or networking.wireguard.interfaces.${name}.privateKeyFile must be set.";
618 mapAttrs (name: value: {
619 assertion = value.generatePrivateKeyFile -> (value.privateKey == null);
620 message = "networking.wireguard.interfaces.${name}.generatePrivateKeyFile must not be set if networking.wireguard.interfaces.${name}.privateKey is set.";
622 ++ map ({ interfaceName, peer, ... }: {
623 assertion = (peer.presharedKey == null) || (peer.presharedKeyFile == null);
624 message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used.";
627 boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
628 boot.kernelModules = [ "wireguard" ];
629 environment.systemPackages = [ pkgs.wireguard-tools ];
631 systemd.services = mkIf (!cfg.useNetworkd) (
632 (mapAttrs' generateInterfaceUnit cfg.interfaces)
633 // (listToAttrs (map generatePeerUnit all_peers))
634 // (mapAttrs' generateKeyServiceUnit
635 (filterAttrs (name: value: value.generatePrivateKeyFile) cfg.interfaces)));
637 systemd.targets = mkIf (!cfg.useNetworkd) (mapAttrs' generateInterfaceTarget cfg.interfaces);