Home Manager and the Module System

Updated

September 12, 2026

Home Manager and the Module System

NixOS configures the machine (kernel, systemd, users, /etc). Home Manager configures one user (dotfiles, user packages, user services). The boring default is: home.packages for binaries, programs.<name> for software that has a module, and home.stateVersion left at the birth release.

Mental model

Home Manager is the NixOS module system aimed at $HOME:

Field Effect
home.username / home.homeDirectory Who this config is for
home.stateVersion Birth release. Do not bump casually.
home.packages On the user PATH (~/.nix-profile/bin)
programs.<name>.enable Typed module: writes dotfiles and maybe a user systemd unit
home.file / xdg.configFile Raw files into $HOME / ~/.config

Two ways to apply it:

Mode Command When
Standalone home-manager switch --flake .#deskadmin Nix-on-Linux, macOS, or a user who must not touch NixOS
NixOS module nixos-rebuild switch The desk workstation is NixOS (next chapters in this part)

Standalone is how you start on a foreign distro. The NixOS module is how you stay once the machine is yours.

home.nix
   ├── home.packages        →  ~/.nix-profile
   ├── programs.git         →  ~/.config/git/config
   └── xdg.configFile       →  ~/.config/…
                │
                ▼
        home-manager switch

Worked examples

Case 1: A standalone home.nix

Save as home.nix:

# home.nix
{ pkgs, ... }:

{
  home.username = "deskadmin";
  home.homeDirectory = "/home/deskadmin";
  home.stateVersion = "26.05";

  home.packages = with pkgs; [
    bat
    fzf
    jq
    ripgrep
  ];

  programs.bash.enable = true;
  programs.home-manager.enable = true;

  # Existing ~/.config/git/config is renamed, not clobbered.
  home.backupFileExtension = "hmbak";
}

On a machine with the home-manager CLI:

home-manager switch -f home.nix

Output:

Starting Home Manager activation
Activating checkFilesChanged
Activating installPackages
…
Home Manager switch complete!
which jq
jq --version

jq resolves under ~/.nix-profile/bin or ~/.local/state/nix/profiles/home-manager/….

Case 2: A flake with homeConfigurations

Save as flake.nix:

# flake.nix
{
  description = "Desk user environment";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
    home-manager = {
      url = "github:nix-community/home-manager/release-26.05";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, home-manager }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in
    {
      homeConfigurations.deskadmin = home-manager.lib.homeManagerConfiguration {
        inherit pkgs;
        modules = [ ./home.nix ];
      };
    };
}

inputs.nixpkgs.follows prevents Home Manager from pulling a second nixpkgs.

home-manager switch --flake .#deskadmin

Pin both inputs in flake.lock. The same lock on a second laptop is the same jq.

Case 3: User packages are not system services

home.packages = [ pkgs.nginx ]; puts nginx on your PATH. It does not open port 80 and it does not start a daemon. Daemons belong in NixOS services.* (or a user systemd module you wrote on purpose).

# wrong mental model
home.packages = [ pkgs.openssh ];  # a binary, not sshd
# NixOS, not Home Manager
services.openssh.enable = true;

Case 4: Generation rollback for $HOME

home-manager generations
home-manager switch --rollback

Output:

2026-09-09 10:11 : id 42 -> /nix/store/…-home-manager-generation
2026-09-08 18:02 : id 41 -> /nix/store/…-home-manager-generation
Switching to generation 41

Broken programs.neovim? Roll back. You do not rm ~/.config/nvim.

Case 5: stateVersion is a birth certificate

home.stateVersion = "26.05";

This is not “track 26.05.” It tells Home Manager which defaults your state dirs were created with. When you upgrade nixpkgs to a newer release, you keep stateVersion until a release note says a specific option needs a bump.

The trap

The trap is managing the same program in both NixOS and Home Manager (environment.systemPackages = [ git ] and programs.git.enable = true with conflicting userName). Two git binaries, two configs, a week of “why is my email wrong.”

Pick a layer: git config for humans → Home Manager. git as a system tool for root scripts → NixOS. Not both with different emails.

The other trap is running standalone Home Manager against a different nixpkgs than NixOS on the same box. follows in the flake, or the NixOS module with useGlobalPkgs (later chapter).

A third: home.file.".gitconfig".text = … and programs.git.enable = true. Two writers, last activation wins. Use the programs.git module; home.file is for files that have no module.

The boring rule

  • Home Manager owns $HOME. NixOS owns the machine.
  • home.packages for extra CLIs. programs.<name> when a module exists. home.file only when no module exists.
  • home.backupFileExtension so the first switch does not refuse a pre-existing dotfile.
  • Flake + follows = "nixpkgs" so there is one nixpkgs.
  • home.stateVersion stays at the birth release.
  • Roll back with home-manager switch --rollback, not by deleting dotfiles.

Try this

  1. Add pkgs.hello to home.packages, switch, run hello, remove it, switch again, and confirm which hello fails.
  2. readlink -f $(which jq) and confirm the path is under /nix/store.
  3. Run home-manager generations and identify the current id.
  4. In the flake, omit inputs.nixpkgs.follows on purpose, nix flake metadata, and notice a second nixpkgs. Put follows back.