Installing NixOS on a Hetzner VPS with nixos-anywhere
I replaced Debian with NixOS on a Hetzner VPS using
nixos-anywhere, fully
remotely. It sshs into the running box, kexecs into a NixOS installer in RAM,
runs disko to partition, nixos-installs the flake, and reboots.
Files I had to add to the flake:
hetzner/disko.nix- declarative disk layout. Confirm the disk device and boot mode on the box first (lsblk,[ -d /sys/firmware/efi ]). Mine was/dev/sda, legacy BIOS:{ disko.devices.disk.main = { device = "/dev/sda"; type = "disk"; content = { type = "gpt"; partitions = { boot = { size = "1M"; type = "EF02"; }; # BIOS-boot (GRUB core.img) root = { size = "100%"; content = { type = "filesystem"; format = "ext4"; mountpoint = "/"; }; }; }; }; }; }hetzner/hardware-configuration.nix- minimal qemu/virtio guest. disko owns the filesystems, so declare none here:{ modulesPath, lib, ... }: { imports = [ (modulesPath + "/profiles/qemu-guest.nix") ]; boot.initrd.availableKernelModules = [ "virtio_pci" "virtio_scsi" "virtio_blk" "sr_mod" ]; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; }hetzner/configuration.nix- the system. sshd on 80 (proxy) + 22, key-only, and the root key baked in so I am not locked out after the wipe:boot.loader.grub.enable = true; services.openssh = { enable = true; ports = [ 22 80 ]; }; users.users.root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAA... me" ]; system.stateVersion = "26.05";Note: do not set
boot.loader.grub.devicehere. disko already setsboot.loader.grub.devicesfrom the disk holding the EF02 partition; setting it again fails eval with “You cannot have duplicated devices in mirroredBoots”.flake.nix- adiskoinput and the machine, wiring disko in as a module:hetzner = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ disko.nixosModules.disko ./hetzner/configuration.nix ]; };
Then the install itself, after a local nixos-rebuild build --flake .#hetzner
to catch eval errors:
nix run github:nix-community/nixos-anywhere -- \
--flake .#hetzner --ssh-port 80 --target-host root@IP
Note: the host key changes on reinstall, so afterwards
ssh-keygen -R '[IP]:80' before reconnecting.
Troubleshooting: it stalled after kexec
The VPS is an SSH jump host that listens on port 80 precisely because the networks I admin it from block port 22. That block broke the install:
--ssh-port 80sets only the initial connection. After kexec it reconnects on--post-kexec-ssh-port, which defaults to 22 -> blocked -> stalled.- Note:
--post-kexec-ssh-port 80only changes which port it connects to, not what the installer listens on. The stock kexec installer’s sshd is on 22, so pointing it at 80 just hit a closed port.
Since the reconnect never happened, disko never ran and Debian was intact - the
kexec installer lives in RAM, so a reboot would have recovered the old OS. The
fix, with the installer still up (VNC console + internet, disk untouched):
- Start a second sshd on port 80 inside the installer. sshd refuses a relative
invocation, so expand the absolute path:
$(command -v sshd) -p 80 - Drive nixos-anywhere at the running installer, skipping kexec:
nixos-anywhere --flake .#hetzner --ssh-port 80 \ --phases disko,install,reboot --target-host root@IP