You Will Break It. Then You Will Roll Back. Then You Will Understand.
There’s a specific kind of Linux user who ends up on NixOS. You’ve already done Arch, you’ve already done Gentoo “for the experience,” you’ve run Fedora Silverblue for a month and liked the immutability concept but missed your package manager, and now someone in a Discord server said “just try NixOS, it’s reproducible” and you said sure, fine, how bad can it be.
Pretty bad. Also pretty good. Let’s talk about it.
This isn’t a love letter to the Nix ecosystem or another “NixOS changed my life” post. This is the honest experience of taking a home lab machine — a repurposed workstation running a few services — and migrating it to NixOS using flakes. What broke, what didn’t, what’s genuinely impressive, and most importantly: when does NixOS actually make sense versus when should you just stay on Debian and have a life.
What NixOS Actually Promises
The pitch is seductive: describe your entire system in a single config file (or a git repo of them), and NixOS builds exactly that system. Every package, every service, every dotfile. Want the same setup on three machines? Same config, three identical systems. Broke something? Roll back to the last working generation with one command. New machine? Clone the repo, run the installer, done.
This is not vaporware. It actually works like that. The catch is getting there.
The Nix language — which you use to write these configs — is a pure, lazy, functional language designed specifically for package management. It looks like someone bet that Haskell wasn’t confusing enough and decided to invent something new. Coming from YAML, shell scripts, or even Ansible, it feels alien. You’re not writing instructions (“install nginx, then enable it”). You’re declaring state (“the system should have nginx enabled”). The distinction matters and takes time to internalize.
Flakes vs. Classic Configuration: Use Flakes
When you first install NixOS you get configuration.nix — a single file that describes your system. That’s the classic approach. It works, and for a single machine with no interest in reproducibility across environments, it’s fine.
But if you want the real power — locked dependency versions, composable configs, proper module system, the ability to share a config between machines — you want flakes. Flakes are still technically “experimental” (they’ve been experimental for several years, which tells you something about Nix project governance), but the entire serious NixOS community uses them. Enable them.
A minimal flake for a home lab server looks like this:
{ description = "My home lab NixOS config";
inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; home-manager = { url = "github:nix-community/home-manager/release-24.11"; inputs.nixpkgs.follows = "nixpkgs"; }; };
outputs = { self, nixpkgs, home-manager, ... }: { nixosConfigurations = { homelab = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ ./hosts/homelab/configuration.nix home-manager.nixosModules.homeManager { home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; home-manager.users.youruser = import ./home/youruser.nix; } ]; }; }; };}That flake.lock file that gets generated? That’s your reproducibility guarantee. It pins every input — nixpkgs, home-manager, everything — to exact git commits. Check it into git. Now anyone with your repo gets the exact same packages you tested with.
configuration.nix: The Good Part
Once you get past the syntax shock, configuration.nix is genuinely pleasant for server configs. Here’s what enabling SSH and nginx looks like in practice:
{ config, pkgs, ... }:
{ imports = [ ./hardware-configuration.nix ];
# Boot boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "homelab"; networking.networkmanager.enable = true;
# SSH — declarative, no forgetting to enable the service services.openssh = { enable = true; settings = { PasswordAuthentication = false; PermitRootLogin = "no"; }; };
# Nginx with a simple reverse proxy services.nginx = { enable = true; virtualHosts."homelab.local" = { locations."/" = { proxyPass = "http://127.0.0.1:8080"; proxyWebsockets = true; }; }; };
# Open firewall ports networking.firewall.allowedTCPPorts = [ 22 80 443 ];
# System packages environment.systemPackages = with pkgs; [ vim git curl htop ];
# Required: set this to the release version system.stateVersion = "24.11";}Notice what’s missing: systemctl enable nginx, systemctl start nginx, editing /etc/ssh/sshd_config by hand, forgetting to reload after config changes. All of that happens automatically when you run nixos-rebuild switch. You declare what you want; NixOS figures out how to get there.
This is the part where experienced sysadmins either fall in love or get deeply suspicious. Both reactions are valid.
Home Manager: Your Dotfiles, Finally Under Control
Home Manager is a separate project that brings the same declarative approach to user-level configuration — dotfiles, shell config, development tools, the works. It integrates with your flake and manages everything from .bashrc to VS Code extensions to git config.
The killer feature is that your entire user environment is reproducible. New machine? Run home-manager switch (or fold it into nixos-rebuild as shown above) and you get your exact shell, your exact tools, your exact config. No more “I know I had this working on my other machine but I can’t remember how.”
This matters more than it sounds. If you’ve ever spent 45 minutes trying to remember how you configured your tmux status bar on the old machine, you get it.
Rollbacks: The Feature That Makes You Feel Invincible
Here’s the thing that actually converts people. You update your config, something breaks — maybe you enabled a service that conflicts with something else, maybe you fat-fingered a port number, maybe an upstream package has a regression. On any other distro, you’re now debugging. On NixOS:
# List available generationssudo nix-env --list-generations --profile /nix/var/nix/profiles/system
# Roll back to the previous generationsudo nixos-rebuild switch --rollbackOr reboot into the previous generation from the bootloader menu, which lists every generation you haven’t garbage-collected. The old system is still there, intact, in the Nix store. You haven’t mutated anything. You just switched which generation is active.
This saved me twice during the migration. Once when an nginx config change broke TLS termination, and once when I accidentally disabled NetworkManager while testing a config change over SSH. The second one required booting from the GRUB menu since I’d lost my connection, but the point is: I booted back into a working system in about 90 seconds. No reinstall, no restore from backup, no reconstructing what I’d changed.
On Debian or Ubuntu, that second scenario is “enjoy your recovery console.”
The Rough Parts (And There Are Several)
Let’s not pretend NixOS is all atomic upgrades and declarative joy. Here’s what’s genuinely painful:
The Nix language. It’s functional. Variables are immutable. Functions are first-class. There’s no mutation, no loops, no familiar control flow. If you’re coming from YAML or Ansible, the mental model shift is significant. Budget a few evenings just to get comfortable reading configs, let alone writing them.
Error messages. When something goes wrong during nixos-rebuild, the error messages range from “cryptic but eventually useful” to “completely unhelpful string of attribute path garbage.” You will learn to search for the exact error text. The community is helpful; the tooling’s error UX is not.
Package overrides and overlays. Want to use a package but with a compile flag changed? Welcome to overlays. Want to pin one specific package to a different version than the rest of nixpkgs? Prepare to understand overrideAttrs and override. This is powerful but the learning curve is steep and the documentation assumes you already understand functional programming concepts.
Build times. If a package isn’t cached in the Nix binary cache, it gets built from source. Most things are cached. Some things aren’t, especially if you’re overriding something. Building LLVM or Firefox from source is not a quick operation.
The “where’s my config file” problem. On traditional Linux, you know where things live: nginx config in /etc/nginx/, systemd units in /etc/systemd/system/. On NixOS, configs are generated into the Nix store and symlinked. Tools that want to edit their own config files get confused. Software that hardcodes /etc/ paths sometimes works, sometimes doesn’t.
When NixOS Is the Right Answer
After living with it for a while, here’s my honest decision rule:
Use NixOS when:
- You’re running servers or CI workers where reproducibility actually matters
- You manage more than two machines and want them to be identical
- You want to version-control your entire system config in git (and actually do it)
- You’ve already broken enough Arch installs that “atomic rollback” sounds like heaven
- You want to define secret rotation and service configuration in one place with proper secret management (agenix or sops-nix work well with flakes)
- You enjoy the functional programming model and want to apply it to infrastructure
Don’t use NixOS when:
- You install random AppImages, proprietary software with weird installers, or games from itch.io
- You need vendor-supported configurations (some enterprise software explicitly won’t support NixOS)
- You want to follow a tutorial that says “edit
/etc/nginx/nginx.conf” without spending 20 minutes figuring out where NixOS actually put that - You just need a desktop that works and you’d rather spend time on the actual work than the meta-work of configuring the system that does the work
For daily-driver desktop use, Fedora Silverblue gives you a lot of the immutability benefits with dramatically less friction. Or just use Arch — at least the ArchWiki answers will work.
For servers, CI workers, dotfile management, and “I want five identical home lab machines from one git repo” scenarios? NixOS earns its reputation.
The Cult Is Real, and Here’s Why
There’s a reason NixOS users evangelize. Once you’ve experienced atomic rollbacks on a production-adjacent machine, the idea of imperative system management starts to feel barbaric. “You mean you just… run commands and hope the state is what you expect?” Yes. That’s what the rest of us do. The Nix people are not wrong that this is insane.
The learning cliff is front-loaded and steep, but it levels off. After a few weeks of actual use, reading configs becomes natural, writing them gets faster, and the benefits compound. Your infrastructure becomes a git repo. Your machines are disposable. Your rollbacks are instant.
Is it for everyone? No. But if your use case aligns with what NixOS actually does well, it’s one of the more honest pieces of infrastructure software out there — it does what it says on the tin, and it says some ambitious things.
Boot into the installer. Break something on purpose. Roll it back. That’s the moment you’ll understand what the fuss is about.