Why this matters
configuration.nix is one module passed to the same evalModules you already know, with the NixOS module tree imported for you. You can evaluate it on Arch today, without NixOS, and inspect the result. That is how to learn NixOS before you install it on a LUKS laptop or a VPS with no console.
Concept
A minimal, real configuration
{ pkgs, ... }:
{
boot.loader.grub.enable = false;
fileSystems."/" = { device = "/dev/disk/by-label/nixos"; fsType = "ext4"; };
networking.hostName = "vps";
services.openssh.enable = true;
services.openssh.settings.PasswordAuthentication = false;
users.users.dawit = {
isNormalUser = true;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAA... dawit@laptop" ];
};
environment.systemPackages = [ pkgs.git pkgs.htop ];
system.stateVersion = "25.11";
}Line by line:
importsis absent here. A real install addsimports = [ ./hardware-configuration.nix ];. That generated file holdsfileSystems,boot.initrdkernel modules andboot.loaderfor your hardware. You do not hand-write it;nixos-generate-configdoes. Here the two linesboot.loader.grub.enable = falseandfileSystems."/"stand in so the config evaluates.boot.loader: on an EFI laptop it isboot.loader.systemd-boot.enable = true;andboot.loader.efi.canTouchEfiVariables = true;. Limine exists too:boot.loader.limine.enable. Verified innixos/modules/system/boot/loader/.networking.hostNameis a plain string.services.openssh.enablestarts sshd and, becauseservices.openssh.openFirewalldefaults to true, opens port 22.settingsis an attrset rendered tosshd_config.users.users.<name>:isNormalUsergives a home and a UID in the normal range.wheelmembership plus the defaultsecurity.sudois how you get sudo.environment.systemPackagesis the system-wide package list. Per-user things belong in home-manager.system.stateVersionis explained below.
Evaluate it on Arch
nix eval --impure --json --expr '
(import ((builtins.getFlake "nixpkgs") + "/nixos/lib/eval-config.nix") {
system = "x86_64-linux";
modules = [ ./vps.nix ];
}).config.networking.firewall.allowedTCPPorts'[22]That took about 17 seconds and downloaded nothing. eval-config.nix is what nixosSystem and nixos-rebuild call under the hood. Everything is lazy, so asking for one option evaluates only what it depends on.
system.stateVersion
It does not pin your NixOS version. The nixpkgs input does that. stateVersion tells modules which release's on-disk formats you started with, so an upgrade does not silently migrate a database or change a default that would break existing state. Set it to the release you installed with. Never bump it as part of an upgrade. Leave it out and you get a warning at every eval:
evaluation warning: system.stateVersion is not set, defaulting to 26.11. Read why this matters on https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion.Build, then activate
A configuration evaluates to one derivation, config.system.build.toplevel. On this machine evaluating its path gives:
/nix/store/pbx5w7vj3wzj1r9rkbrhxb69k9f1yg8i-nixos-system-vps-26.11pre1082290.b6c8664de9b6.drvBuilding it produces a directory with the kernel, initrd, /etc contents, systemd units and an activate script. nixos-rebuild is a wrapper that builds that derivation and then runs the activation you asked for. Actions, verified in nixos-rebuild-ng:
| Action | Builds | Activates now | Boot entry |
|---|---|---|---|
build |
yes, to ./result |
no | no |
test |
yes | yes | no |
boot |
yes | no | yes |
switch |
yes | yes | yes |
dry-activate |
yes | prints what would change | no |
build-vm |
yes, a QEMU script | no | no |
list-generations |
no | no | lists entries |
Run on NixOS later, as root:
sudo nixos-rebuild test
sudo nixos-rebuild switchGenerations and rollback
Each switch or boot creates a system profile generation, /nix/var/nix/profiles/system-N-link, and a bootloader entry. The old generations stay. If the new one does not boot, pick the previous entry in systemd-boot or Limine. If it boots but is wrong:
sudo nixos-rebuild switch --rollback--rollback is a flag, not an action; it activates the previous generation without rebuilding. Old generations are only removed by nix-collect-garbage -d or nix.gc. Until then every rollback target exists on disk.
Where to find option names
The NixOS manual has them, but the source is faster and always matches your pinned revision:
nix eval --raw nixpkgs#pathOn Nix 2.35 the path printed by nix flake metadata can be a lazy reference that does not exist on disk; nix eval --raw nixpkgs#path copies it to the store and prints the real location. Then grep -rn "PasswordAuthentication" <path>/nixos/modules/services/networking/ssh/sshd.nix.
Try it
- Save the configuration as
vps.nixand evaluate the rendered sshd settings:
nix eval --impure --json --expr '(import ((builtins.getFlake "nixpkgs") + "/nixos/lib/eval-config.nix") { system = "x86_64-linux"; modules = [ ./vps.nix ]; }).config.services.openssh.settings.PasswordAuthentication'falseRemove the
system.stateVersionline and evaluate.config.system.stateVersion. You get the warning above and"26.11".Check the user's groups:
.config.users.users.dawit.extraGroupsgives["wheel"].
Exercise
Add time.timeZone = "Africa/Addis_Ababa"; and networking.firewall.allowedTCPPorts = [ 80 443 ]; to vps.nix. Evaluate allowedTCPPorts and explain where the third value comes from.
Solution
{ pkgs, ... }:
{
boot.loader.grub.enable = false;
fileSystems."/" = { device = "/dev/disk/by-label/nixos"; fsType = "ext4"; };
networking.hostName = "vps";
time.timeZone = "Africa/Addis_Ababa";
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.openssh.enable = true;
services.openssh.settings.PasswordAuthentication = false;
users.users.dawit = {
isNormalUser = true;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAA... dawit@laptop" ];
};
environment.systemPackages = [ pkgs.git pkgs.htop ];
system.stateVersion = "25.11";
}[22,80,443]Port 22 is contributed by the openssh module (openFirewall default). allowedTCPPorts is a listOf port, so the two definitions concatenate. The order is the module system's merge order, not numeric and not the order in your file; never rely on it.
Trap
Running nixos-rebuild switch as the first test of a change to networking, sshd or the display manager. If the change is wrong you lose the connection or the screen, and the config is also the next boot entry. Use test first: same activation, no boot entry, so a power cycle brings back the last known good generation. Only switch once test behaved.