13 cfg = config.networking.wireless;
14 opt = options.networking.wireless;
23 hasWPA3 = !mutuallyExclusive opts.authProtocols wpa3Protocols;
24 others = subtractLists wpa3Protocols opts.authProtocols;
26 hasWPA3 && others != [ ];
28 # Gives a WPA3 network higher priority
29 increaseWPA3Priority =
32 // optionalAttrs (hasMixedWPA opts) {
33 priority = if opts.priority == null then 1 else opts.priority + 1;
36 # Creates a WPA2 fallback network
37 mkWPA2Fallback = opts: opts // { authProtocols = subtractLists wpa3Protocols opts.authProtocols; };
39 # Networks attrset as a list
40 networkList = mapAttrsToList (ssid: opts: opts // { inherit ssid; }) cfg.networks;
42 # List of all networks (normal + generated fallbacks)
44 if cfg.fallbackToWPA2 then
45 map increaseWPA3Priority networkList ++ map mkWPA2Fallback (filter hasMixedWPA networkList)
49 # Content of wpa_supplicant.conf
50 generatedConfig = concatStringsSep "\n" (
51 (map mkNetwork allNetworks)
52 ++ optional cfg.userControlled.enable (
53 concatStringsSep "\n" [
54 "ctrl_interface=/run/wpa_supplicant"
55 "ctrl_interface_group=${cfg.userControlled.group}"
60 ++ optional (cfg.secretsFile != null) "ext_password_backend=file:${cfg.secretsFile}"
61 ++ optional cfg.scanOnLowSignal ''bgscan="simple:30:-70:3600"''
62 ++ optional (cfg.extraConfig != "") cfg.extraConfig
65 configIsGenerated = with cfg; networks != { } || extraConfig != "" || userControlled.enable;
67 # the original configuration file
69 if configIsGenerated then
70 pkgs.writeText "wpa_supplicant.conf" generatedConfig
72 "/etc/wpa_supplicant.conf";
74 # Creates a network block for wpa_supplicant.conf
78 quote = x: ''"${x}"'';
81 pskString = if opts.psk != null then quote opts.psk else opts.pskRaw;
85 "ssid=${quote opts.ssid}"
87 if pskString != null || opts.auth != null then
88 "key_mgmt=${concatStringsSep " " opts.authProtocols}"
93 ++ optional opts.hidden "scan_ssid=1"
94 ++ optional (pskString != null) "psk=${pskString}"
95 ++ optionals (opts.auth != null) (filter (x: x != "") (splitString "\n" opts.auth))
96 ++ optional (opts.priority != null) "priority=${toString opts.priority}"
97 ++ optional (opts.extraConfig != "") opts.extraConfig;
101 ${concatMapStringsSep "\n" indent options}
105 # Creates a systemd unit for wpa_supplicant bound to a given (or any) interface
109 deviceUnit = optional (
111 ) "sys-subsystem-net-devices-${utils.escapeSystemdPath iface}.device";
113 if cfg.allowAuxiliaryImperativeNetworks then
114 "-c /etc/wpa_supplicant.conf -I ${configFile}"
119 description = "WPA Supplicant instance" + optionalString (iface != null) " for interface ${iface}";
122 before = [ "network.target" ];
123 wants = [ "network.target" ];
124 requires = deviceUnit;
125 wantedBy = [ "multi-user.target" ];
126 stopIfChanged = false;
128 path = [ pkgs.wpa_supplicant ];
129 # if `userControl.enable`, the supplicant automatically changes the permissions
130 # and owning group of the runtime dir; setting `umask` ensures the generated
131 # config file isn't readable (except to root); see nixpkgs#267693
132 serviceConfig.UMask = "066";
133 serviceConfig.RuntimeDirectory = "wpa_supplicant";
134 serviceConfig.RuntimeDirectoryMode = "700";
137 ${optionalString (configIsGenerated && !cfg.allowAuxiliaryImperativeNetworks) ''
138 if [ -f /etc/wpa_supplicant.conf ]; then
139 echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
143 # ensure wpa_supplicant.conf exists, or the daemon will fail to start
144 ${optionalString cfg.allowAuxiliaryImperativeNetworks ''
145 touch /etc/wpa_supplicant.conf
148 iface_args="-s ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}"
151 if iface == null then
153 # detect interfaces automatically
155 # check if there are no wireless interfaces
156 if ! find -H /sys/class/net/* -name wireless | grep -q .; then
157 # if so, wait until one appears
158 echo "Waiting for wireless interfaces"
159 grep -q '^ACTION=add' < <(stdbuf -oL -- udevadm monitor -s net/wlan -pu)
160 # Note: the above line has been carefully written:
161 # 1. The process substitution avoids udevadm hanging (after grep has quit)
162 # until it tries to write to the pipe again. Not even pipefail works here.
163 # 2. stdbuf is needed because udevadm output is buffered by default and grep
164 # may hang until more udev events enter the pipe.
167 # add any interface found to the daemon arguments
168 for name in $(find -H /sys/class/net/* -name wireless | cut -d/ -f 5); do
169 echo "Adding interface $name"
170 args+="''${args:+ -N} -i$name $iface_args"
175 # add known interface to the daemon arguments
176 args="-i${iface} $iface_args"
180 # finally start daemon
181 exec wpa_supplicant $args
185 systemctl = "/run/current-system/systemd/bin/systemctl";
190 networking.wireless = {
191 enable = mkEnableOption "wpa_supplicant";
193 interfaces = mkOption {
194 type = types.listOf types.str;
201 The interfaces {command}`wpa_supplicant` will use. If empty, it will
202 automatically use all wireless interfaces.
205 A separate wpa_supplicant instance will be started for each interface.
212 default = "nl80211,wext";
213 description = "Force a specific wpa_supplicant driver.";
216 allowAuxiliaryImperativeNetworks =
217 mkEnableOption "support for imperative & declarative networks"
220 Whether to allow configuring networks "imperatively" (e.g. via
221 `wpa_supplicant_gui`) and declaratively via
222 [](#opt-networking.wireless.networks).
224 Please note that this adds a custom patch to `wpa_supplicant`.
228 scanOnLowSignal = mkOption {
232 Whether to periodically scan for (better) networks when the signal of
233 the current one is low. This will make roaming between access points
234 faster, but will consume more power.
238 fallbackToWPA2 = mkOption {
242 Whether to fall back to WPA2 authentication protocols if WPA3 failed.
243 This allows old wireless cards (that lack recent features required by
244 WPA3) to connect to mixed WPA2/WPA3 access points.
246 To avoid possible downgrade attacks, disable this options.
250 secretsFile = mkOption {
251 type = types.nullOr types.path;
253 example = "/run/secrets/wireless.conf";
255 File consisting of lines of the form `varname=value`
256 to define variables for the wireless configuration.
258 Secrets (PSKs, passwords, etc.) can be provided without adding them to
259 the world-readable Nix store by defining them in the secrets file and
260 referring to them in option [](#opt-networking.wireless.networks)
261 with the syntax `ext:secretname`. Example:
264 # content of /run/secrets/wireless.conf
266 psk_other=6a381cea59c7a2d6b30736ba0e6f397f7564a044bcdb7a327a1d16a1ed91b327
267 pass_work=myworkpassword
269 # wireless-related configuration
270 networking.wireless.secretsFile = "/run/secrets/wireless.conf";
271 networking.wireless.networks = {
272 home.pskRaw = "ext:psk_home";
273 other.pskRaw = "ext:psk_other";
276 identity="my-user@example.com"
277 password=ext:pass_work
284 networks = mkOption {
285 type = types.attrsOf (
289 type = types.nullOr (types.strMatching "[[:print:]]{8,63}");
292 The network's pre-shared key in plaintext defaulting
293 to being a network without any authentication.
296 Be aware that this will be written to the Nix store
297 in plaintext! Use {var}`pskRaw` with an external
298 reference to keep it safe.
302 Mutually exclusive with {var}`pskRaw`.
308 type = types.nullOr (types.strMatching "([[:xdigit:]]{64})|(ext:[^=]+)");
310 example = "ext:name_of_the_secret_here";
312 Either the raw pre-shared key in hexadecimal format
313 or the name of the secret (as defined inside
314 [](#opt-networking.wireless.secretsFile) and prefixed
315 with `ext:`) containing the network pre-shared key.
318 Be aware that this will be written to the Nix store
319 in plaintext! Always use an external reference.
323 The external secret can be either the plaintext
324 passphrase or the raw pre-shared key.
328 Mutually exclusive with {var}`psk` and {var}`auth`.
333 authProtocols = mkOption {
339 # 802.11r variants of the above
344 # The list can be obtained by running this command
346 # /^# key_mgmt: /{ run=1 }
348 # /^# [A-Z0-9-]{2,}/{ if(run){printf("\"%s\"\n", $2)} }
349 # ' /run/current-system/sw/share/doc/wpa_supplicant/wpa_supplicant.conf.example
350 type = types.listOf (
365 "WPA-EAP-SUITE-B-192"
376 The list of authentication protocols accepted by this network.
377 This corresponds to the `key_mgmt` option in wpa_supplicant.
382 type = types.nullOr types.str;
386 identity="user@example.com"
387 password=ext:example_password
390 Use this option to configure advanced authentication methods
391 like EAP. See {manpage}`wpa_supplicant.conf(5)` for example
395 Be aware that this will be written to the Nix store
396 in plaintext! Use an external reference like
397 `ext:secretname` for secrets.
401 Mutually exclusive with {var}`psk` and {var}`pskRaw`.
410 Set this to `true` if the SSID of the network is hidden.
412 example = literalExpression ''
421 priority = mkOption {
422 type = types.nullOr types.int;
425 By default, all networks will get same priority group (0). If
426 some of the networks are more desirable, this field can be used
427 to change the order in which wpa_supplicant goes through the
428 networks when selecting a BSS. The priority groups will be
429 iterated in decreasing priority (i.e., the larger the priority
430 value, the sooner the network is matched against the scan
431 results). Within each priority group, networks will be selected
432 based on security policy, signal strength, etc.
436 extraConfig = mkOption {
440 bssid_blacklist=02:11:22:33:44:55 02:22:aa:44:55:66
443 Extra configuration lines appended to the network block.
444 See {manpage}`wpa_supplicant.conf(5)` for available options.
452 The network definitions to automatically connect to when
453 {command}`wpa_supplicant` is running. If this
454 parameter is left empty wpa_supplicant will use
455 /etc/wpa_supplicant.conf as the configuration file.
458 example = literalExpression ''
459 { echelon = { # SSID with no spaces or special characters
460 psk = "abcdefgh"; # (password will be written to /nix/store!)
463 echelon = { # safe version of the above: read PSK from the
464 pskRaw = "ext:psk_echelon"; # variable psk_echelon, defined in secretsFile,
465 }; # this won't leak into /nix/store
467 "echelon's AP" = { # SSID with spaces and/or special characters
468 psk = "ijklmnop"; # (password will be written to /nix/store!)
471 "free.wifi" = {}; # Public wireless network
481 Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli.
482 This is useful for laptop users that switch networks a lot and don't want
483 to depend on a large package such as NetworkManager just to pick nearby
486 When using a declarative network specification you cannot persist any
487 settings via wpa_gui or wpa_cli.
495 description = "Members of this group can control wpa_supplicant.";
499 dbusControlled = mkOption {
501 default = lib.length cfg.interfaces < 2;
502 defaultText = literalExpression "length config.${opt.interfaces} < 2";
504 Whether to enable the DBus control interface.
505 This is only needed when using NetworkManager or connman.
509 extraConfig = mkOption {
516 Extra lines appended to the configuration file.
518 {manpage}`wpa_supplicant.conf(5)`
519 for available options.
526 (mkRemovedOptionModule [ "networking" "wireless" "environmentFile" ] ''
527 Secrets are now handled by the `networking.wireless.secretsFile` and
528 `networking.wireless.networks.<name>.pskRaw` options.
529 The change is motivated by a mechanism recently added by wpa_supplicant
530 itself to separate secrets from configuration, making the previous
533 The syntax of the `secretsFile` is the same as before, except the
534 values are interpreted literally, unlike environment variables.
535 To update, remove quotes or character escapes, if necessary, and
536 apply the following changes to your configuration:
538 home.psk = "@psk_home@"; → home.pskRaw = "ext:psk_home";
539 other.pskRaw = "@psk_other@"; → other.pskRaw = "ext:psk_other";
542 identity="my-user@example.com"
543 password=@pass_work@ → password=ext:pass_work
549 config = mkIf cfg.enable {
551 flip mapAttrsToList cfg.networks (
555 count (x: x != null) [
560 message = ''options networking.wireless."${name}".{psk,pskRaw,auth} are mutually exclusive'';
565 assertion = length cfg.interfaces > 1 -> !cfg.dbusControlled;
569 if config.networking.networkmanager.enable then
571 else if config.services.connman.enable then
575 n = toString (length cfg.interfaces);
578 It's not possible to run multiple wpa_supplicant instances with DBus support.
579 Note: you're seeing this error because `networking.wireless.interfaces` has
580 ${n} entries, implying an equal number of wpa_supplicant instances.
582 + optionalString (daemon != null) ''
583 You don't need to change `networking.wireless.interfaces` when using ${daemon}:
584 in this case the interfaces will be configured automatically for you.
589 hardware.wirelessRegulatoryDatabase = true;
591 environment.systemPackages = [ pkgs.wpa_supplicant ];
592 services.dbus.packages = optional cfg.dbusControlled pkgs.wpa_supplicant;
595 if cfg.interfaces == [ ] then
596 { wpa_supplicant = mkUnit null; }
598 listToAttrs (map (i: nameValuePair "wpa_supplicant-${i}" (mkUnit i)) cfg.interfaces);
600 # Restart wpa_supplicant after resuming from sleep
601 powerManagement.resumeCommands = concatStringsSep "\n" (
602 optional (cfg.interfaces == [ ]) "${systemctl} try-restart wpa_supplicant"
603 ++ map (i: "${systemctl} try-restart wpa_supplicant-${i}") cfg.interfaces
606 # Restart wpa_supplicant when a wlan device appears or disappears. This is
607 # only needed when an interface hasn't been specified by the user.
608 services.udev.extraRules = optionalString (cfg.interfaces == [ ]) ''
609 ACTION=="add|remove", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", \
610 RUN+="${systemctl} try-restart wpa_supplicant.service"
614 meta.maintainers = with lib.maintainers; [ rnhmjoj ];