Get started
Install a router
Install nixos-router over SSH with local/deploy.sh or from an installer USB stick, set up the host flake, and finish the first-boot checklist.
On this page
There are two ways to put nixos-router on a machine: a network install over SSH with local/deploy.sh, or an installer image that you boot from a USB stick. Both erase the target disk, install the router and leave a host flake in /etc/nixos that the router rebuilds itself from.
Before you start
A workstation running Linux on x86_64, with Nix and flakes enabled. The router's system is built there.
A checkout of the repository:
git clone https://github.com/Avunu/nixos-router.git cd nixos-routerThe router's interface names, such as
enp1s0. Boot the target from any Linux live image and runip -br linkto list them, and note which port is the WAN.An SSH key for the admin account. The router only accepts key logins over SSH.
Choose a path
| Path | Use it when | What you run |
|---|---|---|
| Network install | The target can boot a Linux live image and you can reach it over SSH as root. | local/deploy.sh |
| Installer USB stick | You want to install offline, or hand a prepared stick to someone on site. | nix run, or nix build .#installerIso |
Prepare the settings
Both paths read a settings file: local/router-settings.json for the network install and local/install-settings.json for the installer image. The copies in the repository are examples from a real site, down to the SSH keys. Replace the SSH keys with your own, or those keys get admin access to your router. The examples also turn on Suricata and a guest network on VLAN 20 across the LAN ports; change suricata and guest if you don't want those.
At a minimum, set these keys:
{
"hostName": "router",
"timeZone": "America/Chicago",
"stateVersion": "26.11",
"diskDevice": "/dev/sda",
"bootMode": "uefi",
"adminUser": {
"name": "admin",
"sshKeys": ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyReplaceMe admin@laptop"],
"initialPassword": "correct-horse-battery-staple"
},
"wan": { "interface": "enp1s0", "vlan": null },
"lan": {
"interfaces": ["enp2s0", "enp3s0"],
"address": "192.168.1.1",
"networkAddress": "192.168.1.0",
"prefixLength": 24,
"domain": "lan",
"dhcp": { "poolOffset": 100, "poolSize": 150, "leaseTime": "24h" }
}
}diskDeviceis the disk that gets erased. Double-check it.bootModeisuefi(systemd-boot) orlegacy(GRUB), to match the machine's firmware.stateVersionis the NixOS release you install with. Set it once and never change it.adminUser.initialPasswordis what Cockpit andsudotake until you change it. Without one, the admin account has no password: SSH keys still sign you in, but you can neither sign in to Cockpit nor usesudo.
Everything else, such as the guest network, VLANs and filtering, can wait until the router runs. The settings file describes every top-level key, and Networks and VLANs covers the network keys.
Network install
local/deploy.sh installs the host flake in local/ onto the target with nixos-anywhere.
Boot the target from a Linux live image that runs an SSH server, such as the NixOS minimal installer, and make sure you can sign in as root, for example
ssh root@192.168.1.50.Edit
local/router-settings.json. If you want to lock any setting in Nix, editlocal/flake.nixtoo; see The host flake.Run the script with the router's future name and the target's current address:
local/deploy.sh router.lan 192.168.1.50The first time, Nix asks whether to allow the flake's
extra-substituterssetting, thenixos-router.cachix.orgbinary cache. Answer yes. If your user isn't a trusted user of the Nix daemon, the daemon ignores the setting with a warning, and the workstation compiles the router's packages itself, which works but takes much longer.
The script then:
Checks the files. It stops if
local/flake.nixorlocal/router-settings.jsonis missing.Checks the name. The configuration it installs is named after
hostNamein the settings file. It warns if the first label of the name you passed differs; the name argument is otherwise only used in its messages.Refuses a placeholder password. An empty
adminUser.initialPassword, or one ofadmin,admin123,passwordorchangeme, stops the install with:error: set adminUser.initialPassword in <dir>/router-settings.json to a password of its own (change it after the first login, then clear it from the settings)Stages
/etc/nixoswithflake.nix,flake.lockandrouter-settings.json. The settings file is installed readable by root only, because it holds the initial password.Runs nixos-anywhere 1.13.0 against
root@<ip>. It partitionsdiskDevice, installs the system and reboots the target.
When it finishes, the script prints the SSH command for the admin account and the Cockpit address.
The router starts on exactly the inputs in local/flake.lock. To start on the newest nixos-router instead, run nix flake update --flake ./local before you deploy. The router updates itself every night anyway.
Installer USB stick
The installer comes from nixos-install-helper. Run it from the root of your checkout:
nix runThe wizard first collects settings, then asks how to deploy:
Settings. The first time, it walks through a questionnaire generated from the router's options and writes the answers to
installer/router-settings.json. Later runs ask whether to reconfigure. You can also run this step alone withnix run .#configure.Unattended ISO. It builds
installerIsowith the settings frominstaller/, then offers to write it to a USB stick.nix run .#installoffers the same choice without the settings step; with no settings ininstaller/, it useslocal/install-settings.json.Network install. It runs nixos-anywhere against a target you name. The same step runs alone as
nix run .#deploy -- root@<ip>.
The guided ISO, a generic image that asks for its settings on the box, is turned off: a router cannot be installed without knowing its WAN and LAN ports.
Use local/deploy.sh for network installs, not the wizard's Network install or the deploy app. Those install the system but leave /etc/nixos empty, so the router has no host flake to rebuild from: Cockpit cannot apply changes and the nightly upgrade fails.
You can also build the image without the wizard. It then uses local/install-settings.json:
nix build .#installerIsoThe finished image is under result/iso/. When the target boots from it:
The installer shows the target disk, which is
diskDevicefrom the settings, and starts after a 10-second countdown. Press Ctrl+C to abort.If the disk already holds an installed system, the installer waits 10 seconds for you to press Enter to wipe it. If you don't, it leaves the disk alone and stops; remove the stick and reboot to start the existing system.
It installs offline, since everything it needs is on the image, and reboots.
The installed router's /etc/nixos holds a generated flake.nix, a local.nix for your own additions, and router-settings.json. The generated flake imports local.nix, so that file is where this router's Nix settings go.
The host flake
local/flake.nix is the template for the /etc/nixos/flake.nix of a router installed over the network. It is short:
Inputs:
nixos-router(github:Avunu/nixos-router/main), andnixpkgs, which follows nixos-router's own nixpkgs.Modules: the router module; the settings file, loaded through
nixos-router.lib.settingsModule; and a module of locked settings, which turns Cockpit on and sets its port and allowed origins. Your own Nix settings go in that module too. This flake doesn't import alocal.nix.Outputs: one NixOS configuration, named after the
hostNamein the settings file, plus adefaultalias. The nightly upgrade,system-upgradeand Cockpit's apply all build/etc/nixos#<hostName>.
The settings part looks like this:
modules = [
nixos-router.nixosModules.router
(nixos-router.lib.settingsModule ./router-settings.json)
{
router.cockpit = {
enable = true;
port = 9090;
};
# router.wan.interface = "enp0s20f0"; # example: lock the WAN NIC
}
];Always load the settings through nixos-router.lib.settingsModule, not builtins.fromJSON. The loader upgrades settings written for an older version of nixos-router, so an upgrade never fails on an old option shape. See The settings file.
Why nixpkgs follows nixos-router
nixos-router's CI builds the router-specific packages and pushes them to the binary cache at nixos-router.cachix.org: the Technitium DNS apps, router-dns-tools, the Cockpit plugin bundle and the NixOS system derivations. A router downloads them only when its nixpkgs is the exact revision CI built. So the host flake takes nixpkgs from nixos-router's lock instead of tracking nixos-unstable itself:
inputs = {
nixos-router.url = "github:Avunu/nixos-router/main";
nixpkgs.follows = "nixos-router/nixpkgs";
};This also keeps the Technitium DNS apps in step with the DNS server that loads them. The router module adds the cache on the router itself, and local/flake.nix declares it in nixConfig for the build on your workstation.
Routers installed before this change have nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable" and nixos-router.inputs.nixpkgs.follows = "nixpkgs" instead. Change /etc/nixos/flake.nix to the block above once, then run system-upgrade.
The /main in the block matters on its own. It keeps the router on the release branch. A nixos-router input without a ref follows the default branch, develop, where unreleased changes land, so a router that has no /main needs the same one-line change.
First-boot checklist
Connect to the LAN. Plug a computer into a LAN port. It gets an address from the DHCP pool, such as
192.168.1.100.Open Cockpit at
https://192.168.1.1:9090, yourlan.address, or athttps://router.lan:9090(<hostName>.<lan.domain>). The certificate is self-signed, so your browser warns once; accept it. To get a trusted certificate later, see A trusted certificate.Sign in as
adminwith the initial password. See The web UI.Change the password on Cockpit's Accounts page, or with
passwdover SSH. It is also yoursudopassword.Clear the initial password. On System → Settings, empty Initial password, then choose Save and apply from the Save menu. The initial password only matters when the account is created, and until you clear it, it sits readable in the settings file and the Nix store.
Check the WAN. On Network → Diagnostics, pick ping as the Tool, enter
1.1.1.1as the Target and press Run. Then try dig withexample.com.Check for unapplied changes. A warning icon on the System entry in Cockpit's menu means the settings file differs from the running system; System → Operations lists the differences. After a fresh install there should be none.
Register your devices on the Hosts page, and give servers a static IP. Most features build on that; see Hosts and host groups.
Troubleshooting
The script stops with
error: set adminUser.initialPassword .... The settings file has no initial password, or a well-known one. Set a password of your own and run the script again.The script stops with
error: <dir>/flake.nix is missing. Run it from a full checkout; it looks forflake.nixandrouter-settings.jsonbeside itself.You can sign in over SSH but
sudoasks for a password you never set. The settings had noadminUser.initialPassword, so the account has no password. Reinstall with one.The build fails with
router: at least one physical interface must be assigned (no port exists to carry traffic).The settings assign no port to any network. Setwan.interfaceandlan.interfaces.