Jump to content

NixOS system configuration

From NixOS Wiki

NixOS uses a declarative configuration system that allows users to manage their entire operating system setup including installed packages, system services, user accounts, hardware settings, and more through configuration files. This page serves as an overview of how to work with and manage NixOS system configurations.

For an introduction to declarative configuration, see the Overview of the NixOS Linux distribution#Declarative Configuration and the NixOS official manual.

Usage

When you install NixOS, a default system configuration template is generated by the nixos-generate-config tool. This creates a basic configuration.nix file along with a corresponding hardware-configuration.nix, which captures detected hardware settings and filesystem definitions. After making changes to configuration.nix, you can apply them using nixos-rebuild:

# nixos-rebuild switch

Here is a simple example of a NixOS system configuration:

❄︎ /etc/nixos/configuration.nix
{ config, pkgs, ... }: 

{
    # Import other configuration modules
    # (hardware-configuration.nix is autogenerated upon installation)
    # paths in nix expressions are always relative the file which defines them
    imports = [
        ./hardware-configuration.nix
        ./my-dev-tools.nix
        ./my-desktop-env.nix
        ./etc.nix
    ];

    # Name your host machine
    networking.hostName = "mymachine"; 

    # Set your time zone.
    time.timeZone = "Europe/Utrecht";

    # Enter keyboard layout
    services.xserver.layout = "us";
    services.xserver.xkbVariant = "altgr-intl";

    # Define user accounts
    users.users.myuser = {
      extraGroups = [ "wheel" "networkmanager" ];
      isNormalUser = true;
    };
    
    # Install some packages
    environment.systemPackages = with pkgs; [
      ddate
      testdisk
    ]; 
 
    # Enable the OpenSSH daemon
    services.openssh.enable = true;
    
}

To find NixOS module options, see https://search.nixos.org/options.

Defining NixOS as a flake

Flakes offer an alternative, modern way to configure NixOS systems using a standardized and reproducible structure. With flakes, the system configuration — along with any additional inputs such as Nixpkgs or external modules — is managed declaratively within a flake.nix file.

Unlike the traditional configuration model, which relies on /etc/nixos/configuration.nix and channels, flakes explicitly define their dependencies and configurations, making it easier to share, version, and reproduce complete NixOS system setups.

By default, nixos-rebuild switch will read its configuration from /etc/nixos/flake.nix if it is present.

An example basic NixOS flake:

❄︎ flake.nix
{
  inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
  outputs = { self, nixpkgs }: {
    # replace 'joes-desktop' with your hostname here.
    nixosConfigurations.joes-desktop = nixpkgs.lib.nixosSystem {
      modules = [ ./configuration.nix ];
    };
  };
}

Accessing flake inputs

If you want to pass on the flake inputs to external configuration files, you can use the specialArgs attribute:

❄︎ flake.nix
{
  inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
  inputs.home-manager.url = github:nix-community/home-manager;

  outputs = { self, nixpkgs, ... }@inputs: {
    nixosConfigurations.fnord = nixpkgs.lib.nixosSystem {
      specialArgs = { inherit inputs; };
      modules = [ ./configuration.nix ];
    };
  };
}

Then, you can access the flake inputs from your system configuration:

❄︎ configuration.nix
{ config, lib, inputs, ... }: {
  # do something with home-manager here, for instance:
  imports = [ inputs.home-manager.nixosModules.default ];
  ...
}

Specifying hostname

By default nixos-rebuild will use the current system hostname to look up the right NixOS configuration in nixosConfigurations. You can also override this by using appending it to the flake parameter:

# nixos-rebuild switch --flake /etc/nixos#joes-desktop

To switch a remote host you can use:

$ nixos-rebuild --flake .#mymachine \
  --target-host mymachine-hostname \
  --build-host mymachine-hostname --fast \
  switch
Note: Remote building has an issue that's resolved by setting the --fast flag.

Tips and tricks

Using a non-default configuration location

By default, NixOS expects system configuration files to be located in /etc/nixos/, with the primary configuration file at /etc/nixos/configuration.nix. However, it is possible to manage your configuration from a different location.

You can specify a different configuration file when rebuilding the system by providing the path via the -I option:

# nixos-rebuild switch -I nixos-config=/path/to/configuration.nix

If you are using Flakes, specify the flake path instead:

# nixos-rebuild switch --flake /path/to/flake.nix

To set a custom configuration location declaratively, you can configure the nix.nixPath option in your system configuration:

❄︎ /etc/nixos/configuration.nix
{
  nix.nixPath = [ "path to config" ];
  ...
}

Alternatively, you may also set the location with NIX_PATH="nixos-config=/path/to/configuration.nix" environment variable or by symlinking /etc/nixos/configuration.nix.

Modularizing your configuration with modules

The configuration.nix file itself is an instance of a NixOS module, and the NixOS module system makes it straightforward to split your configuration into multiple files for better organization and reusability. Modules can:

  • Import other modules
  • Declare new configuration options
  • Provide values for existing options (this is what most of a standard configuration.nix does)
  • Reference option values from other modules (via the config attribute passed to all modules)

By declaring options for any values you wish to share between modules, you can structure your configuration into as many files as you like, importing them directly or indirectly from your root configuration.nix file. This makes it easier to manage large or complex configurations by keeping related settings together.

Additionally, you can import modules from remote sources if desired, for example using builtins.fetchTarball or similar functions, which is particularly useful for sharing configurations across multiple machines or pulling in reusable modules maintained elsewhere.

Refer to NixOS modules page and NixOS Manual: Chapter - Package Management for more information on modules.

Flake-specific tips

Importing packages from multiple nixpkgs branches

A NixOS config flake could be as follows (replace <hostname> with your hostname):

❄︎ flake.nix
{
  description = "NixOS configuration with two or more channels";

 inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs =
    { nixpkgs, nixpkgs-unstable, ... }:
    {
      nixosConfigurations."&lt;hostname&gt;" = nixpkgs.lib.nixosSystem {
        modules = [
          {
            nixpkgs.overlays = [
              (final: prev: {
                unstable = nixpkgs-unstable.legacyPackages.${prev.system};
                # use this variant if unfree packages are needed:
                # unstable = import nixpkgs-unstable {
                #   inherit prev;
                #   system = prev.system;
                #   config.allowUnfree = true;
                # };
              })
            ];
          }
          ./configuration.nix
        ];
      };
    };
}
❄︎ flake.nix
# can now use "pkgs.package" or "pkgs.unstable.package"
{ pkgs, ... }:
{
  environment.systemPackages = [
    pkgs.firefox
    pkgs.unstable.chromium
  ];
  # ...
}

If the variable nixpkgs points to the flake, you can also define pkgs with overlays with:

pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ /*the overlay in question*/ ]; };

Pinning the flake registry on NixOS

When you use flakes, Nix resolves flake inputs using the flake registry, a mapping of named flake URLs to specific locations (e.g. what nixpkgs resolves to).

By default, nixpkgs tracks nixpkgs-unstable. This can lead to silent changes in dependency resolution and increase build times as it has to fetch the latest versions.

You can lock the registry to your current system's Nixpkgs version by:

❄︎ configuration.nix
{ inputs, ... }:
{
 nix.registry = {
    nixpkgs.flake = inputs.nixpkgs;
  };
}

To make sure the registry entry is "locked" by checking against a hash, use the following:

❄︎ configuration.nix
  nix.registry = {
    nixpkgs.to = {
      type = "path";
      path = pkgs.path;
      narHash = builtins.readFile
          (pkgs.runCommandLocal "get-nixpkgs-hash"
            { nativeBuildInputs = [ pkgs.nix ]; }
            "nix-hash --type sha256 --sri ${pkgs.path} &gt; $out");
    };
  };

This has the unfortunate side-effect of requiring import-from-derivation and slowing down build times, however it may greatly speed up almost every eval. Full-time flakes users may be able to just use narHash = pkgs.narHash.

Getting Instant System Flakes Repl

How to get a nix repl out of your system flake:

$ nix repl

nix-repl> :lf /path/to/flake
Added 18 variables.

nix-repl> nixosConfigurations.myHost.config.networking.hostName
"myHost"

However, this won't be instant upon evaluation if any file changes have been done since your last configuration rebuild. Instead, if one puts:

nix.nixPath = let path = toString ./.; in [ "repl=${path}/repl.nix" "nixpkgs=${inputs.nixpkgs}" ];

In their system flake.nix configuration file, and includes the following file in their root directory flake as repl.nix:

let
  flake = builtins.getFlake (toString ./.);
  nixpkgs = import <nixpkgs> { };
in
{ inherit flake; }
// flake
// builtins
// nixpkgs
// nixpkgs.lib
// flake.nixosConfigurations

(Don't forget to git add repl.nix && nixos-rebuild switch --flake "/etc/nixos") Then one can run (or bind a shell alias):

$ source /etc/set-environment && nix repl $(echo $NIX_PATH | perl -pe 's|.*(/nix/store/.*-source/repl.nix).*|\1|')

This will launch a repl with access to nixpkgs, lib, and the flake options in a split of a second.

An alternative approach to the above shell alias is omitting repl from nix.nixPath and creating a shell script:

nix.nixPath = [ "nixpkgs=${inputs.nixpkgs}" ];
environment.systemPackages = let
  repl_path = toString ./.;
  my-nix-fast-repl = pkgs.writeShellScriptBin "my-nix-fast-repl" ''
    source /etc/set-environment
    nix repl "${repl_path}/repl.nix" "$@"
  '';
in [
  my-nix-fast-repl
];

See Also

  • Home Manager - A system for per-user configuration environments