Posted in linux, nixos on January 11, 2026 by Adrian Wyssmann ‐ 5 min read
I my last post I showed you how to configure a remote host. In this post I want to reduce the manual interaction with the remote host - remember I had to do the manual setup on the target system.
The 3 first steps are basically the same, I have to get the target system ready
Now the next steps is crucial, running the target system with an usb stick comes with ssh running. Unfortunately neither root nor default user nixos have a password set. If you use the image downloaded from the website you still have to interact with the remote system briefly by running sudo passwd nixos or sudo passwd root.
Instead of using the standard NixOS ISO, you can build a custom ISO that already contains your SSH public key. When the notebook boots from this USB, it will automatically start the SSH daemon and trust your key.
You can do this using nixos-generators. Add this to your local machine (where you run your Nix commands):
nix run github:nix-community/nixos-generators -- \
--format iso \
--configuration '{ ... }: {
users.users.nixos.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAA..." ];
}'I did not test that yet.
So assuming we have now a running remote system and we have an admin password. Before we can start, we need some configuration. Besides the configuration of the system (bootloader, …) as shown in
my last post we also need the disc configuration. For that we will use
disko - a tool that allows us to describe our disk layout (partitions, LVM, LUKS, file systems) in a .nix file.
disko.devices = {
disk = {
main = {
device = "/dev/sdb";
type = "disk";
content = {
type = "gpt";
partitions = {
ESP = {
size = "500M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
root = {
end = "-1G";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
plainSwap = {
size = "100%";
content = {
type = "swap";
discardPolicy = "both";
resumeDevice = true; # resume from hiberation from this device
};
};
};
};
};
};
};
}On the github repository of disko you can find a lot of different examples, if you have a more complicated setup. For my homelab it’s simple, I only need a boot partition, a swap partition and the root partition.
The second tool we need is nixos-anywhere - a tool which allows to install NixOS on any machine over SSH. It only requires the target machine to be booted into a Linux installer (like a NixOS ISO) with SSH access.
I update my flake.nix accordingly to install the new host. For simplification I will just add a simplified version
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
disko.url = "github:nix-community/disko";
disko.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, disko, ... }: {
nixosConfigurations.lenovo = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
disko.nixosModules.disko
./hosts/lenovo
./profiles/server
];
};
};
}hosts/lenovo directory contains host specific configuration including the disko config, So the default.nix
{ pkgs, ... }: {
imports = [
./hardware.nix
./disko.nix
];
}Whereas the hardware.nix looks like that
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
};
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usb_storage" "sd_mod" "rtsx_pci_sdmmc" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
}Under profiles/server I have common network config etc. which are common for all “servers”.
With all the configuration in place we can trigger the installation. As the host received a ip with DHCP you need to check the ip. Then you can run
nix run github:nix-community/nixos-anywhere -- \
--flake .#lenovo \
root@<target-ip-address>So now this happens:
Now we have a fully working remote system.
There are some things you might want to consider:
If you are using
nix-sops, it’s also important to add the host key to your .sops.yaml as described in my last post
Ensure your ssh does not allow password authentication
# Enable the OpenSSH server.
services.sshd.enable = true;
services.openssh.settings = {
PasswordAuthentication = false;
PermitRootLogin = "no";
};Ensure you lock down the default user accounts root and nixos
For nixos I set a password and do add my ssh-keys
users.users.nixos = {
isNormalUser = true;
extraGroups = [
"wheel"
"networkmanager"
"video"
];
hashedPasswordFile = config.sops.secrets.default_password.path;
# Kill the empty string fallback
initialHashedPassword = lib.mkForce null;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOrOn3Kj/+ztMtQAaq4pVvXgTsIs1ZOqQDbsA+nJMuRM nixos@homelab from clawfinger"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFTCwPNpVjW6R9vqpKgNSWgGS5hZMZcHwexAMl7E/OI2 nixos@homelab from clawfinger"
];
};root is disabled for login entierly and also password is set
users.users.root = {
openssh.authorizedKeys.keys = lib.mkForce [ ];
hashedPasswordFile = config.sops.secrets.default_root_password.path;
};This workflow is a game-changer for my homelab. I can now reinstall my servers from scratch without ever touching a keyboard attached to the device.
As a next step, I will use a Raspberry PI as a remote host.