Static IP configuration on Raspberry Pi 3b+

I am trying to configure a static v4 & v6 on my Raspberry Pi 3b+

With the following relatively straight forward config:

  networking.useDHCP = false;
  networking.interfaces.eth0 = {
    useDHCP = false;
    ipv4.addresses = [
        { address = "192.168.XX.6"; prefixLength = 24; }
    ];
    ipv6.addresses = [
        { address = "XXXX:XXXX:XXXX::6"; prefixLength = 64; }
    ];
  };
  networking.defaultGateway = { address = "192.168.XXX.1"; interface = "eth0"; };
  networking.defaultGateway6 = { address = "XXXX:XXXX:XXX::1"; interface = "eth0"; };
  networking.nameservers = [ "192.168.XX.5" "XXXX:XXXX:XXXX::5" "192.168.XX.6" ];

This fails on reboot with these errors:

systemd[1]: Found device 7800.
systemd[1]: Starting Address configuration of eth0...
network-addresses-eth0-start[662]: Cannot find device "eth0"
systemd[1]: network-addresses-eth0.service: Main process exited, code=exited, status=1/FAILURE
network-setup-start[744]: Cannot find device "eth0"
systemd[1]: network-setup.service: Main process exited, code=exited, status=1/F>
systemd[1]: network-setup.service: Failed with result 'exit-code'.
systemd[1]: Failed to start Networking Setup.

network-addresses-eth0.service fails and in turn the network-setup.service.

If i restart them after the boot manually the eth0 interface is present and the service starts and configures the IP correctly. Which leads me to believe that it is some kind of race condition between the interfaces start/renaming and when the network service is starting.

I experimented around with different systemd dependencies like this: (with no success)

systemd.services."network-setup" = {
  after = [ "sys-subsystem-net-devices-eth0.device" ];
  wants = [ "sys-subsystem-net-devices-eth0.device" ];
};

I also tried to disable/enable usePredictableInterfaceNames with no success or change the interface was still renamed to eth0.

What am i missing how can i configure a static IP on my Raspberry Pi?
And what is renaming my interface? (I was not able to find a udev rule for that)

Full config:

{ config, pkgs, lib, ... }:
{
  nixpkgs.overlays = [
    (final: super: {
      makeModulesClosure = x:
        super.makeModulesClosure (x // { allowMissing = true; });
    })
  ];
  system.stateVersion = lib.mkDefault "25.05";

  imports = [
    <nixpkgs/nixos/modules/installer/sd-card/sd-image-aarch64.nix>
  ];

  nixpkgs.hostPlatform.system = "aarch64-linux";                                                           
  sdImage.compressImage = false;

  # NixOS wants to enable GRUB by default
  boot.loader.grub.enable = false;
  # Enables the generation of /boot/extlinux/extlinux.conf
  boot.loader.generic-extlinux-compatible.enable = true;
 
  # !!! Set to specific linux kernel version
  boot.kernelPackages = pkgs.linuxPackages_rpi3;

  # !!! Needed for the virtual console to work on the RPi 3, as the default of 16M doesn't seem to be enough.
  # If X.org behaves weirdly (I only saw the cursor) then try increasing this to 256M.
  # On a Raspberry Pi 4 with 4 GB, you should either disable this parameter or increase to at least 64M if you want the USB ports to work.
  boot.kernelParams = ["cma=256M"];

  # Settings
  networking.hostName = "XXX";
  time.timeZone = "Europe/Zurich";

  # Select internationalisation properties.
  i18n.defaultLocale = "en_US.UTF-8";
  # Define a user account. Don't forget to set a password with ‘passwd’.
  users.users.l33tname = {
     isNormalUser = true;
     extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
     hashedPassword = "XXXX";
  };

  # List packages installed in system profile. To search, run:
  # $ nix search wget
  environment.systemPackages = with pkgs; [
     vim
     inetutils # ping
  ];
  
  networking.useDHCP = false;
  networking.interfaces.eth0 = {
    useDHCP = false;
    ipv4.addresses = [
        { address = "192.168.XX.6"; prefixLength = 24; }
    ];
    ipv6.addresses = [
        { address = "XXX:XXX:XX::6"; prefixLength = 64; }
    ];
  };
  networking.defaultGateway = { address = "192.168.XX.1"; interface = "eth0"; };
  networking.defaultGateway6 = { address = "XXX:XX:XX::1"; interface = "eth0"; };
  networking.nameservers = [ "192.168.XX.5" "XXX:XXX:XXX::5" "192.168.XXX.6" ];

  networking.hostFiles = [(pkgs.fetchurl {
    url = "XXX";
    sha256 = "bb06dfaabe376f919141567f467f8b86cb9e6eee8946990afbc1ab759682687b";
  })];

  # List services that you want to enable:
  services.dnsmasq.enable = true;
  services.dnsmasq.alwaysKeepRunning = true;
  services.dnsmasq.settings.server = [ "85.214.73.63" "208.67.222.222" "62.141.58.13" ];
  services.dnsmasq.settings = { cache-size = 500; };

  # Enable the OpenSSH daemon.
  services.openssh.enable = true;
  # Add key to root user for deployment
  users.users.root.openssh.authorizedKeys.keys = ["ssh-ed25519 XXXX"];

  # Open ports in the firewall.
  # networking.firewall.allowedTCPPorts = [ ... ];
  # networking.firewall.allowedUDPPorts = [ ... ];
  # Or disable the firewall altogether.
  networking.firewall.enable = false;

  # Copy the NixOS configuration file and link it from the resulting system
  # (/run/current-system/configuration.nix). This is useful in case you
  # accidentally delete configuration.nix.
  system.copySystemConfiguration = true;
}

Same config with only changing boot.kernelPackages = pkgs.linuxPackages_rpi4; to run it on a RPi 4 seems to work. (The interface is named end0 there by default)

This leads me to the conclusion something for the 3b+ is broken in NixOS.

1 Like

What I usually do to get a predicatable name for an interface is to set it via a systemd.link. The nix wiki also states that these rules are applied via udev and therefore become active after a reboot.

For an example you can consult the nix wiki.