NixOS on a Budget VPS: Getting Under the 2GB RAM Floor
6 min readYou found a VPS deal. Three bucks a month, 1GB RAM, somewhere in Eastern Europe. You want NixOS on it. You run nixos-anywhere and it dies halfway through because the kexec installer needs 2GB to do its thing. Your three-dollar dream just hit a wall.
This is the RAM floor problem. The standard nixos-anywhere workflow boots a kexec image that replaces the running kernel with a minimal NixOS installer. That installer needs enough memory to hold itself, the Nix store, and the build artifacts — and that comes out to roughly 2GB. Below that, it either OOM-kills itself or hangs.
Two approaches get you past it:
- nixos-infect converts the running OS in-place without kexec. Works down to 768MB RAM.
- A custom kexec image strips the installer down to fit in ~1GB RAM, keeping the nixos-anywhere workflow intact.
nixos-infect: the in-place conversion
nixos-infect takes whatever Linux distro your VPS booted — usually Ubuntu or Debian — and converts it to NixOS while it’s running. No kexec, no installer image, no second OS in memory. It downloads Nix, builds a NixOS system closure, installs the bootloader, and reboots into your new system.
The tradeoff is that it keeps the existing partition layout. No disko, no declarative disk formatting. Whatever filesystem your provider set up is what you get. For a cheap VPS where the disk layout is “one ext4 partition and a prayer,” that’s usually fine.
Here’s the invocation that actually works on budget providers:
ssh root@your-vps "curl -sL https://github.com/elitak/nixos-infect/raw/master/nixos-infect \
| sed 's|mktemp /tmp/nixos-infect|mktemp /var/tmp/nixos-infect|g' \
| doNetConf=y NIX_CHANNEL=nixos-unstable bash -x"
Three things are happening beyond the obvious curl | bash.
The sed patch: surviving small /tmp
Many budget providers mount /tmp as a small tmpfs — sometimes just tens of megabytes. nixos-infect creates temporary files via mktemp /tmp/nixos-infect.*, and on these providers that write hits the tiny tmpfs and fails silently or with a cryptic “no space left on device” error.
The sed rewrites the path from /tmp/nixos-infect to /var/tmp/nixos-infect, which lives on the real disk. It’s one substitution that turns a mysterious failure into a working install.
sed 's|mktemp /tmp/nixos-infect|mktemp /var/tmp/nixos-infect|g'
If your provider gives /tmp real disk space, the patch is harmless. If it doesn’t, the patch is mandatory.
doNetConf=y: keeping your network alive
Most budget VPS hosts use static IP addressing. Without doNetConf=y, nixos-infect doesn’t capture the current network configuration before rebooting. The machine comes back up with NixOS, but NixOS has no idea what its IP, gateway, or DNS servers are. You’re locked out.
doNetConf=y tells nixos-infect to snapshot the active network config — IPs, gateways, DNS resolvers — and bake it into the generated NixOS configuration. For any host that isn’t running DHCP, this flag is not optional.
What survives the reboot
nixos-infect preserves your root SSH authorized keys, so you keep access after the conversion. The existing partition layout stays as-is. What you get after reboot is a bare NixOS system with your SSH keys and a working network — a blank canvas to deploy your actual config onto.
After reboot, deploy your real NixOS configuration with nixos-rebuild switch --flake or whatever your deployment tool of choice is. nixos-infect just gets you a bootable NixOS base.
Custom kexec: nixos-anywhere on a diet
If you want the full nixos-anywhere experience — declarative partitioning with disko, a clean install, the works — but your VPS only has around 1GB of RAM, you need a slimmer kexec image.
The standard nixos-anywhere kexec bundles a comfortable installer environment. Comfortable costs memory. A custom kexec image strips that down to the bare minimum.
Here’s what that minimal kexec module looks like:
{
self,
modulesPath,
lib,
config,
pkgs,
...
}: {
imports = [
"${modulesPath}/installer/netboot/netboot.nix"
"${modulesPath}/profiles/minimal.nix"
"${modulesPath}/profiles/qemu-guest.nix"
];
system.nixos.variant_id = "kexec";
system.build.kexec_compressed =
pkgs.runCommand "kexec-compressed"
{ buildInputs = [ pkgs.gnutar ]; }
''
mkdir $out
tar -cvzhf $out/kexec.tar.gz -C ${config.system.build.kexecTree} \
bzImage \
initrd.gz \
kexec-boot
'';
boot = {
initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" ];
kernelParams = [
"panic=30"
"boot.panic_on_fail"
"console=ttyS0"
"console=tty1"
];
kernel.sysctl."vm.overcommit_memory" = lib.mkForce "1";
supportedFilesystems = [ "zfs" ];
};
environment.variables.GC_INITIAL_HEAP_SIZE = "1M";
documentation.enable = false;
documentation.nixos.enable = false;
fonts.fontconfig.enable = false;
programs.bash.completion.enable = false;
programs.command-not-found.enable = false;
security.polkit.enable = false;
security.rtkit.enable = pkgs.lib.mkForce false;
services.udisks2.enable = false;
i18n.supportedLocales = [
(config.i18n.defaultLocale + "/UTF-8")
];
networking.hostName = "kexec";
networking.hostId = "88ad2520";
services = {
getty.autologinUser = lib.mkForce "root";
openssh = {
enable = true;
settings.KbdInteractiveAuthentication = false;
settings.PasswordAuthentication = false;
};
};
environment.etc.is_kexec.text = "";
system.stateVersion = "23.11";
users.users.root.openssh.authorizedKeys.keys = [
# your SSH public key here
];
}
The key decisions that shave off memory:
profiles/minimal.nixinstead of the full installer profile — no man pages, no extra toolingvm.overcommit_memory = "1"— tells the kernel to always say yes to memory allocations and sort it out later via OOM, rather than refusing allocations conservatively. Aggressive, but it’s a throwaway installer environmentGC_INITIAL_HEAP_SIZE = "1M"— keeps the Nix garbage collector’s initial heap small- Everything non-essential disabled — documentation, fonts, bash completion, polkit, udisks, command-not-found. Every service you don’t load is memory you keep
/etc/is_kexecmarker file — tells nixos-anywhere it’s already in a kexec environment so it doesn’t try to kexec again
Build the image and point nixos-anywhere at it:
nix build .#kexec-image
nixos-anywhere --flake .#my-vps --kexec ./result/kexec.tar.gz root@your-vps
You get the full nixos-anywhere workflow — disko partitioning, clean install, proper host keys — on a box that would choke on the default kexec image.
When to use which
| RAM | Approach | What you get |
|---|---|---|
| < 768MB | Neither — upgrade or look elsewhere | Pain |
| 768MB – 1GB | nixos-infect | NixOS, existing partitions, no disko |
| ~1GB | Custom kexec + nixos-anywhere | Full nixos-anywhere with disko |
| 2GB+ | Standard nixos-anywhere | Everything works out of the box |
nixos-infect is the safer bet at the low end. It doesn’t need to hold an entire installer OS in memory — it mutates the running system. The downside is you inherit whatever disk layout your provider gave you.
The custom kexec approach is better if you care about declarative partitioning or need a clean-slate install. But it’s cutting it close at 1GB. If the provider is generous with their RAM accounting and doesn’t have other processes eating into it, it works. If they overcommit heavily on the host side, you might still hit the wall.
Provider gotchas
A few things to watch for on the LowEndBox-tier providers where this matters:
/tmp as tmpfs. Already covered above, but worth repeating. If df -h /tmp shows a tmpfs mount of 50–100MB, the sed patch for nixos-infect is mandatory.
Static networking. The vast majority of budget providers don’t use DHCP. doNetConf=y is not optional. Forget it once, learn the lesson permanently.
Swap. Some providers give you swap, some don’t. If your 1GB VPS has no swap and you’re trying the custom kexec route, you’re working without a safety net. Consider adding a swap file to your NixOS config as one of the first things you deploy.
OpenVZ vs KVM. OpenVZ containers can’t kexec at all — the host kernel is shared. nixos-infect is your only option on OpenVZ, and even then it’s hit-or-miss depending on what the container exposes. KVM is the safe choice for NixOS.
After the install
Whichever path you took, you now have a minimal NixOS system with SSH access. From here:
nixos-rebuild switch --flake .#my-vps --target-host root@your-vps
Push your real configuration. Set up your services. The hard part — getting NixOS onto a box that wasn’t designed for it — is done. Everything from here is just regular NixOS.
Three dollars a month, 1GB of RAM, and a fully declarative operating system. Not bad for a provider that only officially supports Ubuntu.