Linux·8 min read·

NixOS Servers: Reproducible Declarative Configuration in Nix

NixOS rebuilds a whole server from one declarative file: packages, services, users, and firewall in code, with rollbacks that make changes boring.

NB

Netbay Developer Relations

Netbay Engineering

On this page

NixOS is built around a different premise: the entire machine, packages, services, users, firewall, and boot configuration, is described in a set of Nix files, and the installed state of the machine is a function of those files. There are no imperative tweaks that quietly accumulate; the configuration is the truth, and every change produces a new generation you can boot back to. For a small VPS fleet managed by one or two people that property is transformative, because it makes a server reproducible in exactly the way a build pipeline makes software reproducible.

The Machine as a Function

At its core Nix is a functional package manager: every package is built into an immutable store path under /nix/store, with every dependency hashed into the path name, so nothing is installed into shared locations where two versions can fight. A configuration imports the packages it needs and wires them together as services, and nixos-rebuild evaluates the whole machine description and produces a new activation state. If anything fails, the previous generation is still there, complete and bootable. That is the mental model: rebuild the machine, rather than editing it.

Rebuild, switch, roll back configuration.nix packages + services nix build closure in /nix/store activate switch generation previous generation one rollback away Every generation stays resident until garbage is collected

A Minimal Server Configuration

A server that needs only SSH and nginx is around fifteen lines of expression. The Nix language is small and deliberately typed; most of what you write is attribute sets describing services, users, and the firewall.

config
# /etc/nixos/configuration.nix (excerpt)
{ config, pkgs, lib, ... }:
{
  system.stateVersion = "24.11";
  networking.hostName = "web01";
  networking.firewall.allowedTCPPorts = [ 80 443 ];

  users.users.deploy = {
    isNormalUser = true;
    extraGroups = [ "wheel" ];
    openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAA..." ];
  };
  security.sudo.extraRules = [{
    users = [ "deploy" ];
    commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ];
  }];

  services.openssh.enable = true;
  services.nginx.enable = true;
  services.nginx.virtualHosts."example.com" = {
    root = "/var/www/site";
    listen = [ { addr = "0.0.0.0"; port = 80; } ];
  };
}

Every concept you are used to configuring through a series of manual steps — a user here, a firewall rule there, an nginx vhost somewhere else — arrives in this file as a typed option with validation.

Rebuild, Switch, Roll Back

The suite of nixos-rebuild commands maps to the lifecycle. Edit the configuration, then mutate the running machine:

bash
sudo nixos-rebuild switch
sudo nixos-rebuild build          # evaluate and realise without activating
sudo nixos-rebuild --rollback     # atomically flip back a generation
ls -la /nix/var/nix/profiles/system

Because activation is atomic, a broken configuration typically fails before it replaces the running system, and runtime problems are answered by --rollback rather than by archaeology. That single command changes how you approach risky updates entirely.

Locked Inputs Make It Reproducible

Reproducibility is only as strong as the pins, which is what flakes are for. A flake records the exact nixpkgs commit and every other input in flake.lock, so the same flake evaluates to the same store paths on any machine and any day.

config
# flake.nix — inputs are locked to precise revisions
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
  outputs = { self, nixpkgs }: {
    nixosConfigurations.web01 = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [ ./configuration.nix ];
    };
  };
}

Two rebuilds from the same lock produce the same closure hashes. That single fact makes your Git history a decidable timeline of machine states instead of an approximation of what someone once ran. Providers offer NixOS cloud images, and the pattern for a VPS is the same as anywhere else: a flake, a configuration, and a rebuild command.

Takeaway

NixOS trades one evening of learning curve for a server you can destroy and recreate from Git, and it changes incident response from archaeology into redeployment. If you run more than a couple of identical boxes, the declarative model is less magic than it sounds.

Test NixOS on a disposable Netbay VPS in under 60 seconds, rebuild it a dozen ways, and decide whether the learning curve buys your fleet anything — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance