First steps on NixOS: the whole system is one file, and every change is a boot entry
Coming to NixOS from RHEL or Ubuntu, the instinct is to install a package, edit something under /etc, and restart a service. NixOS supports a version of that, but it isn't the model. The model is one file describing the whole machine, a command that builds that description into a new system, and a boot menu that keeps every previous build around. This entry covers those first moves, checked against the NixOS 26.05 manual, plus the one setting the manual tells you never to touch.
The configuration this entry walks through is also a working repo, dar-nixos: one commented configuration.nix you can boot as a VM on any Linux machine with Nix, plus a NixOS VM test that boots it and checks it on every push.
The machine is /etc/nixos/configuration.nix
Per the manual: "The file /etc/nixos/configuration.nix contains the current configuration of your machine." Users, services, the firewall, the bootloader, the packages: all of it is declared there, in the Nix language. A minimal change looks like this:
{ config, pkgs, ... }:
{
environment.systemPackages = [ pkgs.htop pkgs.git ];
services.openssh.enable = true;
}
Setting services.openssh.enable is not the same as installing the openssh package. It turns on a NixOS module, which writes the systemd unit, the config file and the firewall hole. The manual says so directly for packages in general: "Some packages require additional global configuration such as D-Bus or systemd service registration so adding them to environment.systemPackages might not be sufficient." So check whether a services.<name> option exists before reaching for systemPackages. Adding a daemon's package alone usually gets you the binary and nothing running.
Applying it: switch, test, and boot aren't interchangeable
Nothing happens when you save the file. You build it:
sudo nixos-rebuild switch
The manual describes that as: "build the new configuration, make it the default configuration for booting, and try to realise the configuration in the running system." Two siblings do only part of that, and the difference matters the first time you change something risky:
| Command | Activates now | Becomes the boot default |
|---|---|---|
nixos-rebuild switch |
Yes | Yes |
nixos-rebuild test |
Yes | No |
nixos-rebuild boot |
No | Yes |
test is the safe one for network or bootloader experiments. In the manual's words: "if (say) the configuration locks up your machine, you can just reboot to get back to a working configuration." The reboot itself is the undo button.
There's one caveat worth knowing before you rely on switch to restart everything: "This command doesn't start/stop user services automatically. nixos-rebuild only runs a daemon-reload for each user with running user services." System services get restarted. User-level units, like the rootless Quadlet containers covered elsewhere on this site, don't.
Every build is a generation, and rollback is one command
Each successful rebuild becomes a numbered generation: a symlink under /nix/var/nix/profiles/, and an entry in the boot menu. Nothing is overwritten in place. To go back while the system is running:
sudo nixos-rebuild switch --rollback
Per the manual, that's equivalent to running /nix/var/nix/profiles/system-N-link/bin/switch-to-configuration switch for the previous generation. To see what you have:
ls -l /nix/var/nix/profiles/system-*-link
If the new generation won't boot at all, pick an older one from the bootloader menu. That's the practical difference from a traditional distro, where rollback means snapshots, backups, or remembering what you changed.
flowchart LR
EDIT["edit configuration.nix"] --> BUILD["nixos-rebuild"]
BUILD -->|switch| G3["generation N+1<br/>active + boot default"]
BUILD -->|test| LIVE["active until reboot,<br/>boot default unchanged"]
BUILD -->|boot| NEXT["boot default,<br/>active after reboot"]
G3 -->|"switch --rollback"| G2["generation N"]
G3 -.->|"boot menu"| G1["any older generation"]
Old generations cost disk until you collect them
Keeping every build has a price: old generations pin their packages in /nix/store. The manual's command for cleaning up is nix-collect-garbage, with an important distinction. The plain form "do[es] not remove garbage collector roots, such as old system configurations. Thus they do not remove the ability to roll back." The -d form does:
sudo nix-collect-garbage -d # deletes old generations, then collects
After that, the rollback menu is gone. To automate the safe part, the manual gives:
{
nix.gc.automatic = true;
nix.gc.dates = "03:15";
}
One more gotcha from the same section: if /boot fills up, clearing old profiles isn't enough on its own. You "must rebuild your system with nixos-rebuild boot or nixos-rebuild switch to update the /boot partition."
nix-env exists, and it's the other model
nix-env -iA nixos.thunderbird installs a package imperatively, into a per-user profile. The manual calls this ad hoc package management and spells out how it differs: packages installed this way are upgraded individually, whereas "running nixos-rebuild switch causes all packages to be updated to their current versions in the NixOS channel." Mixing the two means some software on the machine isn't in configuration.nix, which defeats the reason to use NixOS. For a first system, put everything in the file. For a one-off tool you need for five minutes, nix-shell -p <package> gives you a temporary shell without installing anything.
Channels are per user, and upgrades come from them
A fresh install is subscribed to the channel matching the ISO. On 26.05 that's nixos-26.05, which "only get[s] conservative bug fixes and package upgrades." To upgrade within it:
sudo nixos-rebuild switch --upgrade
That's the manual's shorthand for nix-channel --update nixos; nixos-rebuild switch. The trap is that "channels are set per user." Running nix-channel --add without sudo changes your user's channel, not the one configuration.nix is built from. The upgrade appears to do nothing, because the system never saw it.
The one line you should leave alone: system.stateVersion
The generated config ends with something like system.stateVersion = "26.05";. It looks like a version number to bump on each upgrade. It isn't. From the option's own definition in nixpkgs:
Most users should never change this value after the initial install, for any reason, even if you've upgraded your system to a new NixOS release.
It records which NixOS release first created the machine's data, so modules for things like databases keep defaults compatible with data that can't migrate itself. The same description states the misconception outright: "This value does not affect the Nixpkgs version your packages and OS are pulled from, so changing it will not upgrade your system." The upgrade comes from the channel. Leave stateVersion at whatever the installer wrote.
Flakes: you'll see them everywhere, and they're still experimental
Most NixOS guides online use flake.nix rather than configuration.nix alone. The installer supports it (nixos-generate-config --flake), but in the Nix 2.34 reference manual both flakes and the new nix subcommands are still listed as experimental features you have to opt into. You can learn NixOS without them. Everything above works with the plain configuration.nix and channels, and flakes make more sense once that model is familiar.
How this compares to tools already on this site
This is the same declarative idea as Terraform/OpenTofu, applied to one machine's whole OS instead of cloud resources. It shares one property with Terraform: there's no agent watching for drift. Nothing reapplies configuration.nix until you run nixos-rebuild. The difference is that the previous state is still sitting on the boot menu, which Terraform's state file can't give you.
Created 2026-09-25T20:02:38+02:00 · Edit