+ The default networking stack on NixOS uses NetworkManager. + While it's a great choice for many users, I prefer a lighter setup built around systemd-networkd, systemd-resolved, and iwd. It integrates well with the rest of the systemd stack, has fewer moving pieces, and exposes powerful networking features without an additional network management daemon. +
+ ++ This article focuses on laptops that connect to wifi. + If you're configuring a server, you probably don't need iwd at all; systemd-networkd and systemd-resolved are usually sufficient. +
+ + + +The default stack
+If not configured otherwise, NixOS uses the following stack:
+-
+
- NetworkManager for wifi +
- dhcpcd for DHCP +
- resolvconf for DNS +
- a "legacy script based system" for network configuration[1] +
We'll be replacing it with:
+-
+
- iwd for wifi +
- systemd-networkd for DHCP and network configuration +
- systemd-resolved for DNS +
+ This has a few benefits. + Firstly, switching to resolved allows you to enable features like encrypted DNS over TLS (DoT) and DNSSEC validation, which reduces the potential for DNS spoofing and man-in-the-middle attacks[2]. + Additionally, the default stack uses 3 extra packages (NM, dhcpcd, resolvconf) and some bash scripts, while this setup only uses one extra package (iwd). + Also, I'm kind of a systemd nerd, and this is a way to dig into systemd a bit more. +
+ +So the first thing we can do is disable all of the default stuff:
+{
+ networking = {
+ networkmanager .enable = false ;
+ dhcpcd .enable = false ;
+ resolvconf .enable = false ;
+ networking .useDHCP = false ;
+ };
+}
+
+ And let's get to configuring!
+ +iwd
++ First up is iwd. + The important parts of the configuration are delegating network configuration to networkd, delegating name resolution (DNS) to resolved, and configuring MAC randomization for some extra privacy. +
+{
+ networking .wireless .iwd = {
+ enable = true ;
+ settings = {
+ General = {
+ EnableNetworkConfiguration = false ; # delegated to networkd
+ AddressRandomization = "once" ;
+ AddressRandomizationRange = "full" ;
+ };
+ Network .NameResolvingService = "systemd" ; # use systemd-resolved
+ };
+ };
+}
+
+ + At this point, we don't have any network configuration yet, so internet access won't work. However, you can verify that iwd is functioning correctly: +
+-
+
: Scans for new wifi networksiwctl station wlan0 scan
+ : lists the networks that it foundiwctl station wlan0 get-networks
+ : connects to the network, prompting for a PSK if necessaryiwctl station wlan0 connect "<SSID>"
+
systemd-networkd
++ Next up is systemd-networkd. + Networkd has lots of advanced options for things like static IP config, Wireguard tunnels, VLANs, domain-based DNS routing, and more. + However, here we'll just do a basic configuration to enable DHCP and deny DHCP-advertised DNS. +
+{
+ systemd .network = {
+ enable = true ;
+
+ networks ."11-default" = {
+ name = "wl*" ;
+ networkConfig .DHCP = "yes" ;
+
+ # never accept dhcp dns
+ dhcpV4Config .UseDNS = "no" ;
+ dhcpV6Config .UseDNS = "no" ;
+ };
+ };
+ networking .useNetworkd = true ;
+}
+
+
+ The name = "wl*"
+ refers to the network interface name that this configuration applies to.
+ According to the systemd docs, wireless LAN devices should start with the two-character prefix "wl"[3].
+ Here we use a glob (*) to match all wireless interfaces.
+
You can verify that your configuration worked with a few commands:
+-
+
: make sure that there's an IP address associated with your wireless interface. That means DHCP is working.ip addr
+ : make sure there's no significant errors in the logs.networkctl status
+
systemd-resolved
++ Now all we have left is DNS configuration, then we're done. + This is probably the part where you can nerd out the hardest, but I try to keep it pretty minimal. + The important parts are selecting upstream DNS servers that support secure DNS protocols (like DNS over TLS and DNSSEC) and that respect your privacy. + For these reasons, I use quad9, but do your own research and find the best upstream DNS server for you! + Some other options include Cloudflare DNS, Google's DNS, or AdGuard Public DNS. +
+{
+ networking .nameservers = [
+ "9.9.9.9#dns.quad9.net"
+ "149.112.112.112#dns.quad9.net"
+ ];
+
+ services .resolved = {
+ enable = true ;
+ settings .Resolve = {
+ DNSOverTLS = true ;
+ DNSSEC = "allow-downgrade" ;
+
+ LLMNR = false ;
+ MulticastDNS = false ;
+ };
+ };
+}
+
+ You can verify your DNS configuration by using the
+ command or by just opening a website.
P.S. Captive portals
++ Captive portals are those wifi login pages that pop up when you connect to some public wifi networks. + Some captive portals don't behave correctly when you're using an encrypted third-party DNS server instead of the network's advertised resolver. + To get around this, I use Captive Browser + (nixpkgs), which launches an instance of Chromium through a SOCKS5 proxy, avoiding the system DNS configuration altogether. + This allows you to access a captive portal for long enough to log in and gain internet access. + Pretty neat! +
+ +P.P.S. I miss nm-applet
++ Probably the biggest downside to this setup is the lack of desktop integration. + GNOME and Plasma only support NetworkManager in their network configuration menu, so you're kinda out of luck. + For tiling window manager people, I can recommend iwmenu, which is a launcher (dmenu, rofi, fuzzel)-based menu for connecting to wifi networks via iwd. +
+
why disable DHCP-advertised DNS?
+ By default, most networks advertise their own DNS resolver via DHCP. + Since we'll configure our own encrypted upstream DNS servers in the next section, we tell networkd to ignore the ones provided by the network. +