Installing NixOS

Updated

September 12, 2026

Installing NixOS

Nix the package manager is not NixOS. NixOS is a Linux distribution whose kernel, initrd, systemd units, users, and /etc are one derivation. The boring default is: install once on a disposable VM, generate hardware config, write a tiny configuration.nix, and treat nixos-rebuild switch as the only way the machine changes.

Do not practise on the only disk that holds your photographs.

Mental model

The installer ISO is a live NixOS. You boot it, partition a disk, generate a hardware snapshot, write a system expression, and run nixos-install (plain, or --flake .#host once a flake exists). That produces generation 1. From then on you edit code and rebuild. You do not apt install. You do not systemctl enable by hand.

The 26.05 live ISO already has flakes. You do not need to nix-env -iA nixos.git just to clone the flake. nixos-install --flake /mnt/etc/nixos#desk-vm is the same install as the classic /mnt/etc/nixos/configuration.nix path, with a lockfile.

File Role
hardware-configuration.nix Disk UUIDs, file systems, kernel modules. Generated. Rarely edited.
configuration.nix Hostname, users, packages, services, bootloader. You write this.
/nix/var/nix/profiles/system Pointer at the current generation
Bootloader menu Every previous generation you can roll back to
installer ISO
    │
    ▼
partition + mount  →  /mnt  and  /mnt/boot
    │
    ▼
nixos-generate-config --root /mnt
    │
    ▼
edit /mnt/etc/nixos/configuration.nix
    │
    ▼
nixos-install   →   reboot   →   generation 1

This book targets NixOS 26.05+. After install, nixos-version should agree.

Worked examples

Case 1: Disposable VM, not a daily driver

Create a UEFI VM:

Resource Starting point
Firmware UEFI
RAM 4 GB (8 GB is more comfortable)
Disk 40 GB
CPU 2 cores

Download the graphical or minimal NixOS 26.05 ISO from the NixOS download page. Attach it. Boot. You are in a live environment with a root shell (minimal ISO) or a desktop (graphical ISO).

Check firmware:

ls /sys/firmware/efi

If that directory exists, you are in UEFI mode. Match the VM firmware to the bootloader you will enable (systemd-boot for UEFI).

Case 2: Partition, mount, generate config

On a single empty virtual disk /dev/vda (names vary: vda, sda, nvme0n1):

lsblk

A boring GPT layout for UEFI:

Partition Size Type Mount
/dev/vda1 1 GB EFI System /mnt/boot
/dev/vda2 rest Linux filesystem /mnt

Using parted and mkfs by hand is acceptable for this first machine. Disko (later in the book) replaces this for fleets. For one VM:

parted /dev/vda -- mklabel gpt
parted /dev/vda -- mkpart ESP fat32 1MiB 1GiB
parted /dev/vda -- set 1 esp on
parted /dev/vda -- mkpart primary ext4 1GiB 100%

mkfs.fat -F32 /dev/vda1
mkfs.ext4 /dev/vda2

mount /dev/vda2 /mnt
mkdir -p /mnt/boot
mount /dev/vda1 /mnt/boot

Generate the hardware snapshot:

nixos-generate-config --root /mnt
ls /mnt/etc/nixos/

Output:

configuration.nix
hardware-configuration.nix

Open hardware-configuration.nix. You should see the file-system UUIDs for / and /boot. Do not invent UUIDs. Leave this file as generated.

Case 3: A tiny first configuration.nix

Replace the generated configuration.nix with something you can read in one screen. Save as /mnt/etc/nixos/configuration.nix:

# configuration.nix
{ config, pkgs, ... }:

{
  imports = [ ./hardware-configuration.nix ];

  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;
  boot.loader.systemd-boot.configurationLimit = 8;

  networking.hostName = "desk-vm";
  time.timeZone = "UTC";
  i18n.defaultLocale = "en_US.UTF-8";

  users.users.deskadmin = {
    isNormalUser = true;
    extraGroups = [ "wheel" ];
    # Lab only. Hashed at eval; the *hash* still lands in the store.
    # After first login: passwd, then delete this option.
    initialPassword = "changeme";
  };

  environment.systemPackages = with pkgs; [
    git
    vim
    curl
  ];

  services.openssh.enable = true;

  system.stateVersion = "26.05";
}

stateVersion is not “the release I want to be on.” It is “the release whose defaults this machine was born with.” Leave it at the release you installed. Changing it later without reading the release notes is how databases and state dirs break.

configurationLimit = 8 keeps the ESP from filling with old kernels. The first-machine chapter adds GC; this is the bootloader side of the same hygiene.

Set a real password after first login (passwd). Then remove initialPassword. That option hashes at eval time; the hash is still world-readable in /nix/store. Production uses hashedPasswordFile (sops) or users.mutableUsers = true plus a one-shot passwd. hashedPassword = "…" in the module is the same store leak as initialPassword, just pre-hashed.

Install from the generated files (no flake yet):

nixos-install --no-root-passwd
nixos-enter --root /mnt -c 'passwd deskadmin'
reboot

--no-root-passwd skips the interactive root prompt when you already declared a wheel user. nixos-enter sets the password inside the installed root, not in the live ISO. nixos-install without that flag still works; it will ask for a root password.

Flake install (26.05 default once git exists). After nixos-generate-config, drop a flake next to the two Nix files. Save as /mnt/etc/nixos/flake.nix:

# flake.nix
{
  description = "Desk VM";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";

  outputs = { self, nixpkgs }: {
    nixosConfigurations.desk-vm = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [ ./configuration.nix ];
    };
  };
}
nixos-install --flake /mnt/etc/nixos#desk-vm --no-root-passwd

The #desk-vm name is the nixosConfigurations attr, not a guess. --root defaults to /mnt. Copy flake.lock into git on first boot; the next rebuilds use --flake .#desk-vm. Do not nixos-install (no --flake) after you moved the machine to a flake — that builds the old channel configuration.nix.

Log in as deskadmin. Confirm:

nixos-version
hostname

Output:

26.05.9440.6aefcda (Yarara)
desk-vm

Case 4: The first rebuild on the installed system

You are now on the machine. Edit /etc/nixos/configuration.nix (or the flake in git). Add ripgrep to environment.systemPackages and activate. If you installed with --flake, keep using it:

sudo nixos-rebuild switch --flake /etc/nixos#desk-vm

Without --flake, this rebuilds the installer-era channel config. After git lives elsewhere, --flake .#desk-vm from the clone.

Output:

building the system configuration...
updating GRUB/systemd-boot...
activating the configuration...
setting up /etc...
reloading user units...
which rg
rg --version

rg is on PATH. It came from the new generation, not from a manual download.

List generations:

sudo nix-env --list-generations --profile /nix/var/nix/profiles/system

You should see generation 1 (install) and generation 2 (ripgrep). The bootloader menu lists both.

Case 5: Rollback without a rescue disk

Break the machine on purpose in the VM. In configuration.nix, set a nonsense timezone or remove boot.loader.systemd-boot.enable — something that still builds but is wrong — or, safer, add a package, switch, then:

sudo nixos-rebuild switch --rollback

Output:

switching to generation 1
activating the configuration...

rg is gone. The bootloader default is generation 1 again. If a rebuild leaves the system unbootable, pick the previous generation in systemd-boot (hold Space at firmware, then select the older NixOS entry).

The trap

The trap is mutating the live system after install. passwd for the first login is fine. useradd, systemctl enable nginx, curl | sh into /usr are not. Those changes vanish on the next rebuild, or worse, they survive in /etc as unmanaged files that fight the next activation.

A second trap is editing hardware-configuration.nix to “clean it up.” The file is ugly because hardware is ugly. If you replace UUIDs with /dev/vda2, the next disk reorder will fail to boot.

A third trap is treating stateVersion as an upgrade knob. Upgrade by changing the flake input (or channel) to a new release and reading the release notes. Keep stateVersion at the original value unless a release note tells you to bump a specific option.

A fourth trap is shipping initialPassword (or hashedPassword = "…") on a host that will ever be copied. nix path-info -r /run/current-system includes the hash. Rotate, then hashedPasswordFile.

The boring rule

  • Practise on a VM. UEFI. One ESP, one root. systemd-boot + configurationLimit.
  • nixos-generate-config owns hardware-configuration.nix. You own configuration.nix (and flake.nix).
  • nixos-install --flake /mnt/etc/nixos#host once a flake exists. Same attr name as nixosConfigurations.
  • system.stateVersion is the birth release. Leave it.
  • After install, the only mutation path is nixos-rebuild switch --flake (or test, or boot).
  • Know --rollback and the bootloader menu before you need them.
  • initialPassword is lab-only. Delete it after passwd.

Try this

  1. After Case 4, run sudo nixos-rebuild test with a hostname change. Confirm hostname changed, reboot, and confirm it reverted (test does not update the bootloader default).
  2. Run sudo nixos-rebuild dry-activate and read which units would restart. No change should be applied.
  3. Add cowsay to environment.systemPackages, switch, run cowsay desk, then rollback and confirm cowsay is missing.
  4. Open hardware-configuration.nix and write down the UUID of /. Compare with lsblk -f. They must match.
  5. Install a second VM with --flake /mnt/etc/nixos#desk-vm. Confirm nixos-version still says 26.05 and ls /etc/nixos/flake.lock exists.