Combining NixOS and Home Manager

Updated

September 12, 2026

Combining NixOS and Home Manager

Two rebuild commands (nixos-rebuild and home-manager switch) means two nixpkgs, two lock files, and a rollback that restores the kernel but not ~/.bashrc. The boring default on a NixOS workstation is: Home Manager as a NixOS module, one flake, one nixos-rebuild switch.

Mental model

flake.nix
  nixosConfigurations.desk-vm
       ├── NixOS modules (boot, users, services)
       └── home-manager.nixosModules.home-manager
              └── home-manager.users.deskadmin = { … };

nixos-rebuild switch --flake .#desk-vm then:

  1. Builds the system closure.
  2. Builds the user’s Home Manager generation.
  3. Activates both.
  4. Records one system generation that includes the home generation.

--rollback restores both.

Option Why
home-manager.useGlobalPkgs = true; One nixpkgs: the system’s. No second eval.
home-manager.useUserPackages = true; User packages land in the NixOS user profile, not a disconnected HM profile
home-manager.users.<name> Must match users.users.<name>

Standalone Home Manager remains correct on a foreign distro (next chapter). On NixOS, the module wins.

Worked examples

Case 1: Minimal unified module

Save as unified.nix:

# unified.nix
{ config, pkgs, ... }:

{
  home-manager.useGlobalPkgs = true;
  home-manager.useUserPackages = true;

  home-manager.users.deskadmin = { pkgs, ... }: {
    home.stateVersion = "26.05";
    programs.git = {
      enable = true;
      userName = "Desk Engineer";
      userEmail = "desk@corp.internal";
    };
    home.packages = [ pkgs.jq ];
  };
}

The user deskadmin must already exist in NixOS (users.users.deskadmin.isNormalUser = true;). Home Manager will not create a system user.

Case 2: Flake wiring

Save as flake.nix:

# flake.nix
{
  description = "Desk workstation";

  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 }: {
    nixosConfigurations.desk-vm = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
        home-manager.nixosModules.home-manager
        ./unified.nix
      ];
    };
  };
}
sudo nixos-rebuild switch --flake .#desk-vm

Output:

building the system configuration...
activating the configuration...
Starting Home Manager activation
…

One command. jq is on the user’s PATH after login.

Case 3: Split files per user

Save as home/deskadmin.nix:

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

{
  home.stateVersion = "26.05";
  programs.bash.enable = true;
  home.packages = with pkgs; [ ripgrep jq ];
}

In unified.nix:

# unified.nix
{
  home-manager.useGlobalPkgs = true;
  home-manager.useUserPackages = true;
  home-manager.users.deskadmin = import ./home/deskadmin.nix;
}

Do not invent a second flake output homeConfigurations.deskadmin on the same NixOS host unless you have a reason to apply it separately (you probably do not).

Shared user modules (git, editor) that every human should get:

# unified.nix fragment
{
  home-manager.sharedModules = [ ./home/common.nix ];
  home-manager.extraSpecialArgs = { deskDomain = "desk.internal"; };
}

extraSpecialArgs is how a home module receives flake inputs (inputs.nixpkgs is already pkgs when useGlobalPkgs is on). Do not import the system config into home unless you must — it is a cycle magnet.

Case 4: Backup files on clobber

{
  home-manager.backupFileExtension = "bak";
}

If a real file is sitting where Home Manager wants a symlink, the switch copies it to *.bak instead of aborting. Useful once during migration. If you see new .bak files every week, something is still writing in place.

Case 5: Rollback includes $HOME

sudo nixos-rebuild switch --rollback

The previous system generation’s Home Manager generation comes back. ~/.config/git/config points at the old store path. You do not run home-manager switch --rollback in addition.

The trap

The trap is keeping a standalone home-manager switch --flake .#deskadmin cron on a NixOS box that also imports the module. Two tools write ~/.bashrc. The last one to run wins. Disable the standalone CLI in muscle memory once the module is on.

The other trap is useGlobalPkgs = false “so I can pin a newer git.” You now evaluate nixpkgs twice and can mix library ABIs in one user session. If you need a newer git, overlay it on the system nixpkgs.

The boring rule

  • On NixOS: Home Manager as a NixOS module. One flake. One rebuild.
  • useGlobalPkgs = true; useUserPackages = true;.
  • home-manager.users.<name> matches users.users.<name>.
  • follows = "nixpkgs" on the home-manager input. sharedModules for team defaults.
  • backupFileExtension at the home-manager. NixOS option (not only inside home.nix) so clobber policy is one place.
  • Rollback is nixos-rebuild switch --rollback. Stop running two CLIs.

Try this

  1. Wire Case 2 on a lab VM, add pkgs.hello to the user’s home.packages, switch, run hello as deskadmin.
  2. ls /nix/var/nix/profiles/system and find the home-manager generation referenced from the current system.
  3. Change git userEmail, switch, then --rollback and confirm the old email is back.
  4. Grep your flake for homeConfigurations. If it exists and nixosConfigurations applies the same user, delete the duplicate output.