deniz.in

Markets

Weather

Loading weather

· via Hacker News – Front Page (native)

NixOS tuned to idle at 96MB and rebuild inside 512MB in detailed LowEndSpirit guide

A LowEndSpirit write-up details cutting NixOS 26.05 from a 288MB idle and 896MB rebuilds to a 96MB idle and a 371MB rebuild peak, using kernel tuning, module pruning and zram.

NixOS tuned to idle at 96MB and rebuild inside 512MB in detailed LowEndSpirit guide

A write-up on the LowEndSpirit forum, which reached the Hacker News front page, walks through shrinking NixOS 26.05 until it idles at 96MB of RAM and can rebuild itself on a 512MB VPS. The author, who set aside half a day for the job, says it took closer to three days, and notes the guide came from working notes rather than a planned article.

Where the system started

According to the post, a stock cloud-init deployment ran at 1024MB of RAM only just, sitting at 288MB used with 89 kernel modules loaded. A plain same-release rebuild needed 896MB; at 768MB, nix-build was killed by the OOM manager with 505MB of anonymous memory allocated.

After the tuning pass, the same system idles at 96MB used with 54 modules on a 512MB VPS, and a full channel update plus rebuild, including a kernel switch to 7.3, completes with a 371MB peak and no OOM kills. The author measured these numbers a few minutes after the second boot with nobody logged in, using their own build-environment testing tools, and cautions that some figures were recorded only to the megabyte.

Kernel command-line savings

Several of the early wins come from boot parameters. The NixOS kernel ships with allocation profiling enabled by default, and the author measured the tracking tables at roughly 6MB, reclaimed with sysctl.vm.mem_profiling=never. A bare mem_profiling=never without the sysctl prefix does nothing, they note, because the kernel simply hands the invalid parameter to userspace.

Further parameters disable the 2MB KFENCE pool, remove the kdamond and kauditd threads, and blacklist initialisation for KSM and TPM support that a guest never uses. On a 2GB test VM the whole command-line set took non-cache memory from 128MB to 121MB and cut kernel threads from 64 to 59.

The single biggest change, according to the post, was transparent_hugepage=never. With huge pages on madvise, the kernel raises vm.min_free_kbytes to 41108, versus 3710 without, reserving memory the workload never gets. Turning THP off freed 52MB at 2GB and made 103MB more available at 896MB, though the author flags that production systems may have reasons to keep it.

Pruning modules and the initrd

The guide blacklists hardware a KVM guest never sees: floppy, PS/2 mouse, AGP, wireless configuration pulled in by networkd, and similar drivers, one of which was holding an 8MB kernel slab. For modules requested during switch-root, a plain blacklist did not work, so they are replaced with /bin/false installs instead.

The initrd is cut to virtio_pci, virtio_blk and ext4, and a small derivation replaces system.modulesTree to drop sound, GPU, wireless, USB, physical NIC and storage HBA drivers while keeping dependencies of everything retained. That trimmed the module tree from 162MB to 45MB on disk, and because it runs inside nixos-rebuild, kernel updates receive the same treatment automatically.

Userspace trims

On the service side, the post disables systemd-resolved, worth 8.3MB of PSS and previously listening for LLMNR and mDNS on all interfaces, with cloud-init writing a plain resolv.conf instead. systemd-timesyncd, at 4.6MB, is replaced by a one-shot busybox ntpd on a boot and hourly timer, which the author expects to work on most KVM hosts. sshd becomes socket-activated so its 3.9MB listener exists only during sessions, though the author suggests skipping that on hosts with SSH exposed on a public IP. systemd-oomd is dropped at 1.8MB, and a one-shot service writes LLDP=no into cloud-init's network files to stop networkd's LLDP socket at idle. What remains resident is systemd, udevd, nsncd, networkd, journald, logind, dbus-broker and a single getty, around 40MB of userland PSS.

Fitting a rebuild into 512MB

Two changes make in-place rebuilds viable. First, zram with zstd compression at 100 percent of memory, capped at 512MB with no disk swap, paired with a high swappiness so the kernel compresses cold anonymous pages rather than discarding page cache. During a 512MB rebuild, zram peaked at 116MB of real RAM. Second, Boehm garbage-collector environment variables tune Nix evaluation: on a 2GB VM, evaluation peaked at 599MB stock, and the GC settings brought it to 548MB. The author found nothing else that meaningfully reduces evaluation memory.

A closure trim, mostly for disk, took the closure from 963MB to 910MB and the compressed image from 607MB to 477MB. The author deliberately left disk-minimising options out of their TierHive template, pointing readers to a separate Nixcademy guide, because such defaults could surprise end users.

The remaining weak spot is release jumps: an attempt to move from 26.05 to unstable on 512MB ran about 30 minutes before nix-build was OOM-killed, though the old generation kept running and a second attempt finished because downloads were already cached.

Why it matters

NixOS is often assumed to need generous resources, and this guide shows the floor is far lower than expected, which matters for cheap VPS tiers, embedded targets and anything billed by memory. Because NixOS configuration is declarative, these tweaks persist across rebuilds, and the module-tree trimming even reapplies itself on kernel updates. The post also documents hidden costs, such as default allocation profiling and THP-driven memory reservations, that apply to Linux systems generally. Readers should note the numbers come from one person's runs on KVM guests, and several choices carry stated production trade-offs.

  • #nixos
  • #linux
  • #memory-optimization
  • #virtualization
  • #kernel