NixOS 25.11 Causes Vastly Longer Update Times Compared to 25.05

Good morning, everyone. I hope all is well.

For context, I have been working on for the past few months on my university’s graduation project. This involves, among other things, modifying NixOS to make it more minimal and add our packages and tooling. The configuration for packages is done through flakes while configuration for the target installed system is done through a traditional configuration.nix file. All was well until recently. For context, running “nix-channel –update && nixos-rebuild switch“ usually took around 8 minutes to complete on an HDD and 3 minutes on an SSD. Those numbers are when I was using NixOS 25.05 as my base in my nix flake inputs. Switching to 25.11 as the base increases the update times to 30 minutes both an HDD and SSD! Furthermore, the VM that I was testing in was provided with 4 CPU cores and 6GB to 10GB of RAM, which is more than adequate.

The 30 minute update time is extremely long and will hamper future integration testing efforts. I am completely unsure as to what causes these long update times apart from its relation to NixOS 25.11. I am unsure as to which code snippets I should include exactly, so I will attach specific snippets on request.

Given that Nix memory consumption is totally inadequate, have you checked whether you are hitting swap?

Why are you updating channels, when you are using flakes? Is your problem about evaluation time or are you downloading/rebuilding many things?

2 Likes

All of configuration.nix and flake.nix; what you’re witnessing is extremely unusual, and as @wamserma says your commands don’t make much sense. You’ve got something funky going on, there will probably be multiple misunderstandings leading to this.

1 Like

extremely unusual

you sure? this sounds very much like what i experienced when i switched my config to a flake.

when using channels i remember it working pretty much like a normal linux distro, meaning: run the update command and it downloads a small amount of new stuff and installs it.

since a few months back when i started using flakes, the updates are always huge (last weeks were better but still a few gigabytes), despite minimal or even no change in the config (apart from the lock file), and this causes many issues.

  • takes forever because of download size
  • internet connection usually fails intermittently so it does not continue and update has to be restarted
  • update fails because you run out of disk space
  • you delete old generations which defeats the purpose of nix
  • you garbage collect to actually free up space
  • not sure whether the failed build or the garbace collection causes it but now you have to redownload everything again
  • if for any reason you change the config, you again redownload everything

mind you, this even happended when my flake tracked the 25.11 channel, so it is not just caused by unstable introducing changes which require everything to rebuild…

and what’s the benefit? since it only tracks the version of nixpkgs, you cannot simply say “just update firefox and everything depending on it”. Currently I am seriously considering going back to a non-flakyfied nix config…

PS:

and you unnecessarily evaluate nixpkgs multiple times due to the forSystem pattern. i talked about it here in the templates repo: https://github.com/NixOS/templates/issues/98

This is is extremely unusual. Updates with nix flake update should be on the order of hundreds of megabytes, just like nix-channel --update; both commands should download the same tarballs, and evaluation should result in the same builds.

Don’t get me wrong, I’m critical of flakes; There are some misfeatures that result in excessive disk churn and sometimes unnecessary downloads, but these should be nowhere near gigabytes and barely noticeable. Most likely you’re not using the correct flake URIs, or making other subtle mistakes which cascade into this kind of behavior.

Personally I usually recommend npins, but your experience here is definitely not expected.

1 Like

Could you elaborate on what you mean by “Nix memory consumption is totally inadequate“? Also, yes. I can guarantee with almost absolute certainty that I am not hitting swap for the following reasons:

  1. I tested the entire git commit history for the project. Versions where I added the swapfile were unaffected. It is only the latest version which uses 25.11 as its base that is affected.
  2. I allocated 10GiB of RAM to the VM which is far more than it will ever need.
  3. If I was hitting swap, then testing it on an SSD should have resulted in far better results than when I tested it on an HDD. However, using 25.11 resulted in 30 minute nixos-rebuild switch times for both.

I mean, if it really takes that long it’s almost certainly not eval-only (unless you’re actually swapping).

Odds are that you switched to using a release-* branch, and therefore building a lot of software from scratch gentoo-style. It’s a very common newbie mistake.

You could also be making a bunch of other mistakes that would lead to this. We can’t help without seeing your config.

For the record, my rebuilds take less than 20 seconds (on a Ryzen 5600, other machines are marginally faster, but my point is the order of magnitude), and a lot of that is eval because my projects are intentionally a bit hard to eval.

3 Likes

Thank you for your quick response!

After reading the comment by @wamserma , I realized that mixing between Nix channels and flakes seems to be a terrible idea. To reply to your previous comment, my problems are indeed with evaluation times. Most of the nixos-rebuild switch time is spent on the line “evaulating file: <github:nixos/nixpkgs/HASH/>/flake.nix“. I asked Gemini and it said that it was likely that most of my evaulation time was spent evaluating the flake packages I added since I used builtins.getFlake. Its hunch was correct. I removed the flake packages and the build times dropped to about 1 minute on 25.05 and 4 minutes on 25.11. Would the best course of action in this case be to forgo nix channels and use only flakes?

The problem is about evaluation times. Most of the time, nixos-rebuild switch hangs on the line “evaulating file: <github:nixos/nixpkgs/HASH/>/flake.nix“

Seriously, try to share as much of your config verbatim as you can. It all depends on what you’re intending to do exactly.

Mixing channels and flakes is a terrible idea, you get the worst of both worlds, and there is never a reason to.

1 Like

There are several files that I can send. For now, I will send the configuration.nix and packages.nix present inside the installed OS. This distinction is important because the configuration.nix is slightly edited at install time before it is copied into /etc/nixos in the target device.

I sent those two files as I suspect the builtins.getFlake to be the reason for the slowdown. I can send the entire project as a ZIP if you desire, but most of it would be benign modules and flakes for packages. I assume that would be too much code to review for too little value. However, if you assume that the flake configurations themselves are the culprit, then I can send them as well. I am just trying to avoid a “code overload”.

Furthermore, thank you for your advice regarding mixing flakes and channels, and most importantly, thank you for your time!

configuration.nix

{pkgs, config, atmos-gui, ... }:
let
  atmos-gui = builtins.getFlake "/etc/nixos/packages/gui-application";
in
{
  imports =
    [ 
      ./boot.nix
      ./users.nix
      ./metadata.nix
      ./packages.nix
      ./networking.nix
      ./filesystems.nix
      ./environment.nix
      ./firmware.nix
      <nixpkgs/nixos/modules/profiles/hardened.nix>
    ];

    nix = {
      package = pkgs.nix;
      settings.experimental-features = [ "nix-command" "flakes" ];
      extraOptions = ''
        experimental-features = nix-command flakes
      '';
    };

  i18n.defaultLocale = "en_US.UTF-8";
  console = {
    earlySetup = true;
    font = "ter-932n";
    packages = with pkgs; [ terminus_font ];
    keyMap = "us";
  };

  #services.cage.enable = true;
  #programs.dconf.enable = true;
  hardware.graphics.enable = true;
  fonts.fontconfig.enable = true;
  services.spice-vdagentd.enable = true;
  services.qemuGuest.enable = true;
  security.allowSimultaneousMultithreading = true;
  security.lockKernelModules = false;
  # App armor will be added later
  security.apparmor.enable = false;
  fonts.packages = with pkgs; [
    dejavu_fonts
    noto-fonts
    noto-fonts-cjk-sans
    noto-fonts-color-emoji
  ];

  services.cage = {
    enable = true;
    user = "atm";
    program = "${atmos-gui.defaultPackage.x86_64-linux}/bin/atmos-gui";
    # This unfortunately makes wayland use a software renderer
    # This is ofcourse unacceptable on a bare metal install
    # However, this is the only thing that could get the cursed virtio driver to work
    # This is so that it looks better when ATM-OS is run inside of a VM
    # The QXL driver that causes too much flickering
    environment = {
      WLR_RENDERER = "pixman";
    };
    extraArguments = [
      "-d"
      "-s"
    ];
  };
  time.timeZone = "Africa/Cairo";
}

packages.nix

{ pkgs, self, print-metadata, atmos-gui, modify-update-ssh-keys, ... }: 
let
  print-metadata = builtins.getFlake "/etc/nixos/packages/print-metadata";
  atmos-gui = builtins.getFlake "/etc/nixos/packages/gui-application";
  modify-update-ssh-keys = builtins.getFlake "/etc/nixos/packages/modify-update-ssh-keys";
in
{
  environment.systemPackages = with pkgs; [
    cage
    grub2

    print-metadata.packages.x86_64-linux.print-metadata
    atmos-gui.defaultPackage.x86_64-linux
    modify-update-ssh-keys.packages.x86_64-linux.modify-update-ssh-keys

    neovim 
  ];
}

  1. You pass self and a lot of other stuff in the module args, and are therefore probably using a flake for the config itself, why use getFlake over inputs?
  2. I would probably try to monolithitify the many getFlaked flakes. Or not use flakes at all for those.

edit
3. Doesn’t this setup even rely on --impure? This can be another culprit of weird eval behaviour.

1 Like

It relies on impure because I change some placeholder paths in other files using a BASH wrapper. This is because I didn’t want to commit full paths to GitHub in the nix files themselves.

As for my usage of flakes, the installer is a flake and all packages in the resulting ISO are also configured using flakes

but then whats the point of using nix, if you still have to do some bash magic? why dont you pass the values as variables via specialArgs?

the getFlake stuff can also be specified as inputs in your config’s flake.nix and passed via the specialArgs field

If you rely on impure as a fundamental part of your evaluation, you are throwing away the main reason to use flakes, pure and hermetic evaluation.

If you are just after the pinning, use npins.

Yeah, as the others said, this is what we in the scene call “holding it wrong”. Just in those two files you’re evaluating nixpkgs 5 times; that’s ~2G of memory use, not to speak of the evaluation time impact.

To elaborate on @NobbZ earlier suggestions to fix your flake, the idiomatic approach with flakes would look something like this:

# flake.nix
{
  inputs = {
    nixpkgs.url = "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz";

    atmos-gui = {
      url = "/etc/nixos/packages/gui-application";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    print-metadata = {
      url = "/etc/nixos/packages/print-metadata";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    modify-update-ssh-keys = {
      url = "/etc/nixos/packages/modify-update-ssh-keys";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs =
    { nixpkgs, ... }@inputs:
    {
      nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {
        modules = [ ./configuration.nix ];
        specialArgs.flake-inputs = inputs;
      };
    };
}
# configuration.nix
{
  pkgs,
  lib,
  modulesPath,
  flake-inputs,
  ...
}:
let
  # Note, by the way, that the `default` flake attributes are
  # deprecated. You'd want to update this to be:
  #
  # flake-inputs.atmos-gui.packages.${pkgs.stdenv.hostPlatform.system}.atmos-gui
  atmos-gui = flake-inputs.atmos-gui.defaultPackage.${pkgs.stdenv.hostPlatform.system};
in
{
  imports = [
    ./boot.nix
    ./users.nix
    ./metadata.nix
    ./packages.nix
    ./networking.nix
    ./filesystems.nix
    ./environment.nix
    ./firmware.nix
    (modulesPath + "/profiles/hardened.nix")
  ];

  nix = {
    package = pkgs.nix;
    settings.experimental-features = [
      "nix-command"
      "flakes"
    ];
    extraOptions = ''
      experimental-features = nix-command flakes
    '';
  };

  i18n.defaultLocale = "en_US.UTF-8";
  console = {
    earlySetup = true;
    font = "ter-932n";
    packages = with pkgs; [ terminus_font ];
    keyMap = "us";
  };

  #services.cage.enable = true;
  #programs.dconf.enable = true;
  hardware.graphics.enable = true;
  fonts.fontconfig.enable = true;
  services.spice-vdagentd.enable = true;
  services.qemuGuest.enable = true;
  security.allowSimultaneousMultithreading = true;
  security.lockKernelModules = false;
  # App armor will be added later
  security.apparmor.enable = false;
  fonts.packages = with pkgs; [
    dejavu_fonts
    noto-fonts
    noto-fonts-cjk-sans
    noto-fonts-color-emoji
  ];

  services.cage = {
    enable = true;
    user = "atm";
    program = lib.getExe atmos-gui;
    # This unfortunately makes wayland use a software renderer
    # This is ofcourse unacceptable on a bare metal install
    # However, this is the only thing that could get the cursed virtio driver to work
    # This is so that it looks better when ATM-OS is run inside of a VM
    # The QXL driver that causes too much flickering
    environment = {
      WLR_RENDERER = "pixman";
    };
    extraArguments = [
      "-d"
      "-s"
    ];
  };
  time.timeZone = "Africa/Cairo";
}
# packages.nix
{
  pkgs,
  lib,
  flake-inputs,
  ...
}:
{
  environment.systemPackages = lib.attrValues {
    inherit (pkgs) cage grub2 neovim;
    inherit (flake-inputs.print-metadata.packages.${pkgs.stdenv.hostPlatform.system}) print-metadata;
    inherit (flake-inputs.modify-update-ssh-keys.packages.${pkgs.stdenv.hostPlatform.system})
      modify-update-ssh-keys
      ;
    atmos-gui = flake-inputs.atmos-gui.defaultPackage.${pkgs.stdenv.hostPlatform.system};
  };
}

If you’re wondering where your module args went; they were all unused.

This would already cut down the number of nixpkgs evals from these two files to 1 (assuming your other flakes aren’t also silly, which given the long evaluation times, they probably are). That’d bring down memory usage to a more “reasonable” few hundred megs (yes, @7c6f434c is right that nixpkgs eval is way too expensive).

Also, yep, drop the --impure flag. If you need it, that means you’re doing something wrong. The above definitions do not need --impure for anything.

After that, I’d also agree with @NobbZ that you should consolidate the various third “package” flakes. Honestly, it looks like they’re all already part of the /etc/nixos flake anyway, there’s no reason to make them subflakes, just vendor the code, those look like simple scripts. There’s probably no reason to break them into a separate flake; though if there is, you should probably do this differently to begin with.

3 Likes