5 minutes reading time
Updated 01 Dec 2025
├──[ 0. main article ]
╰──[ 1. checking partition stats ]
As investigated in my previous article, NixOS consumes an increasing amount of the limited metadata storage area of a filesystem with each new build.
For instance, I use NixOS on my regular desktop computers, so I rarely add or remove more than one or two packages in my configuration.nix file before invoking a rebuild process, which then consumes far more space in the metadata area than in the data area
The reason is simple: NixOS release upgrades or full system updates completed by a rebuild produce the latest generation, which installs the latest version of each package and its individual dependencies without removing their previous versions. Those procedures have the potential to double the amount of storage used.
✅ NixOS is equipped with its own garbage collector, which can be invoked imperatively whenever needed. This frees inodes instantly in critical situations, such as after a system build fails and reports that no space is left.
✅ Besides the imperative clean-up, it is highly recommended to declare an automated garbage-collection routine in the configuration.nix file in order to prevent any unwanted future shortages of filesystem space.
Generations in the context of NixOS are originally called build-generations of a distinct profile. Whenever you run nixos-rebuild "switch", it incrementally builds the next generation of your system.
The "switch"-argument causes the system to transition directly to the newly created generation without requiring a reboot:
Bash --- like in the official NixOS Wiki (seen in November 2025)
A newly created entry at the top of your boot menu refers to the latest generation while preserving the previous ones.
This design guarantees an almost incorruptible operating system. In case of misconfiguration or unwanted results, one can always dive back into a previous generation during the next boot.
In my opinion, this is the most valuable advantage of using NixOS.
To keep things simple, I address the system profile only in this article; it is not user‑specific and therefore serves as the public profile for all users on a system. Multiple profiles are possible, e.g., one for each user on multi‑user systems.
Bash --- lists all existing generations of the system profile
(Simplified) The garbage collector likely traverses the local nix-store recursively and collects all unlinked packages. These are orphaned nix‑store paths because they are no longer linked to any of the existing generation’s system‑profile trees.
As mentioned above, we can free some filesystem space by removing legacy generations of our active profile.
Bash --- this removes all generations including their orphaned packages older than 14d
The nix store optimise operation collects all identical files in your nix-store and replaces each one with a hard link that points to a single original file. This usually frees about 25–30 % of the store. Theoretically, it removes duplicate inodes and the filesystem blocks they were holding pointers to.
⚠️ Attention! This operation is very hungry for resources and may consume a lot of time.
Bash --- replaces identical files in the store by hard links
I use btrfs as my filesystem, so I must run this command to plot a summary of the nix-store filesystem statistics:
Bash --- firstly mentioned in my previous article (you must choose the correct tool depending on your local filesystem)
ℹ️ before the invocation of nix store optimise:
Overall:
Device size: 456.52GiB
Device allocated: 432.52GiB
Device unallocated: 24.00GiB
Device missing: 0.00B
Device slack: 1.50KiB
Used: 303.34GiB
Free (estimated): 145.39GiB (min: 133.39GiB)
Free (statfs, df): 145.39GiB
Data ratio: 1.00
Metadata ratio: 2.00
Global reserve: 512.00MiB (used: 0.00B)
Multiple profiles: no
Data,single: Size:416.50GiB, Used:295.12GiB (70.86%)
/dev/disk/by-uuid/3095dd1c-cf7e-11f0-b26a-1b1bc8337735 416.50GiB
Metadata,DUP: Size:8.00GiB, Used:4.11GiB (51.38%)
/dev/disk/by-uuid/3095dd1c-cf7e-11f0-b26a-1b1bc8337735 16.00GiB
System,DUP: Size:8.00MiB, Used:64.00KiB (0.78%)
/dev/disk/by-uuid/3095dd1c-cf7e-11f0-b26a-1b1bc8337735 16.00MiB
Unallocated:
/dev/disk/by-uuid/3095dd1c-cf7e-11f0-b26a-1b1bc8337735 24.00GiB
ℹ️ right after the invocation
Overall:
Device size: 456.52GiB
Device allocated: 432.52GiB
Device unallocated: 24.00GiB
Device missing: 0.00B
Device slack: 1.50KiB
Used: 293.66GiB
Free (estimated): 153.68GiB (min: 141.68GiB)
Free (statfs, df): 153.68GiB
Data ratio: 1.00
Metadata ratio: 2.00
Global reserve: 512.00MiB (used: 0.00B)
Multiple profiles: no
Data,single: Size:416.50GiB, Used:286.82GiB (68.86%)
/dev/disk/by-uuid/3095dd1c-cf7e-11f0-b26a-1b1bc8337735 416.50GiB
Metadata,DUP: Size:8.00GiB, Used:3.42GiB (42.74%)
/dev/disk/by-uuid/3095dd1c-cf7e-11f0-b26a-1b1bc8337735 16.00GiB
System,DUP: Size:8.00MiB, Used:64.00KiB (0.78%)
/dev/disk/by-uuid/3095dd1c-cf7e-11f0-b26a-1b1bc8337735 16.00MiB
Unallocated:
/dev/disk/by-uuid/3095dd1c-cf7e-11f0-b26a-1b1bc8337735 24.00GiB
The procedure freed
Adding these two lines to the configuration.nix results in automatic garbage collection whenever nixos-rebuild is triggered to build the next generation of your system.
{
nix.gc.automatic = true;
nix.gc.options = "--delete-older-than 30d";
}
configuration.nix --- removes all generations older than 30 days during every build
ℹ️ It is also possible to automate the optimization procedure via declaration. Please read the corresponding official NixOS wiki article for further instructions.