Installing and managing a nixos VPS
published: Jul 19, 2025
|NOTE: IN DRAFT
In this post we are gonna install nixos on a vps, using deploy-rs for easier configuration management and sops-nix for secret management.
Part 1 - Setting up NixOS
1. Launching the server
Create a VPS instance with the configuration that suits your budget and have heard good reviews about. I’m gonna use digital ocean cause i still have some github credits left.
Before this create a ssh key to access the server.
$ ssh-keygen -t ed25519 -C "[email protected]" # replace the email with yours
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/quantinium/.ssh/id_ed25519): /home/quantinium/.ssh/id_ed25519_nixie
Enter passphrase for "/home/quantinium/.ssh/id_ed25519_nixie" (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/quantinium/.ssh/id_ed25519_nixie
Your public key has been saved in /home/quantinium/.ssh/id_ed25519_nixie.pub
The key fingerprint is:
SHA256:Y2vMC2ibBZZQ9wMD2m0QcraJAMwWCBgT4YDLshjJv9A [email protected]
The key's randomart image is:
+--[ED25519 256]--+
|^=o Bo+ |
|** O * + |
|+++ + o o |
|=o . o . |
|ooo + S |
|o. E o + o |
| . + o = |
| o + o . |
| o . |
+----[SHA256]-----+
that’s it - this keypair is ready to be used. open the file with .pub
extension where you chose to store the ssh key pair and copy it and add it to the panel in vps instance creation panel when asked.
# /home/quantinium/.ssh/id_ed25519_nixtest.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMr8qETvTGSBHbDXiOk+QOhfRvKT0wDvOwtuMEDT+Bcc [email protected]
For reference this is my configuration :-
- Region - San Francisco (Select the one nearest to you)
- Image - Ubuntu 24.10 x86
- Spec - 1 vCPU | 2GB RAM | 60GB NVME Storage (make sure to have >= 2GB RAM as 1GB is not enough for kexec).
- ssh key -
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMr8qETvTGSBHbDXiOk+QOhfRvKT0wDvOwtuMEDT+Bcc [email protected]
- Hostname - nixie
if you are also using digital ocean enable the Add improved metrics monitoring and alerting (free)
.
Wait for your machine to be created and you’ll be assigned the IP address of the machine. Let’s try running a command.
$ ssh -i /home/quantinium/.ssh/id_ed25519_nixie [email protected] uname -a # replace with the path of the public ssh key and server IP
Linux nixos 6.11.0-9-generic #9-Ubuntu SMP PREEMPT_DYNAMIC Mon Oct 14 13:19:59 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
We should have a working ubuntu linux vps running now.
2. Installing NixOS
Now time to cleanse the VPS and replace ubuntu with nixos. We are gonna use nixos-anywhere to install nixos on vps.
nixos-anywhere allows us to install nixos on a target machine by connecting over SSH. if the target machine is running a linux distribution, it can use
kexec
to boot into a temporary RAM-based nixos installer environment and usesdisko
to automatically partition, format and mount disk according the configuration.
before installing nixos on the system, we gotta know two details - ip_address and primary drive
. so ssh into the vps
$ ssh -i /home/quantinium/.ssh/id_ed25519_nixie [email protected]# replace with the path of the public ssh key and server IP
for getting the ip either get it from your vps provider or run ip a
and for getting the primary drive run lsblk
root@nixie:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 32:84:a5:4b:1f:40 brd ff:ff:ff:ff:ff:ff
altname enp0s3
altname ens3
inet 165.22.208.155/20 brd 165.22.223.255 scope global eth0
valid_lft forever preferred_lft forever
inet 10.47.0.6/16 brd 10.47.255.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::3084:a5ff:fe4b:1f40/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether c2:4b:b9:a2:dc:dc brd ff:ff:ff:ff:ff:ff
altname enp0s4
altname ens4
inet 10.122.0.3/20 brd 10.122.15.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::c04b:b9ff:fea2:dcdc/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
root@nixie:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vda 253:0 0 50G 0 disk
├─vda1 253:1 0 48.9G 0 part /
├─vda13 253:13 0 1023M 0 part /boot
├─vda14 253:14 0 4M 0 part
└─vda15 253:15 0 106M 0 part /boot/efi
vdb 253:16 0 488K 1 disk
as we can see the ip address is 165.22.208.155
and primary drive is vda
Now on our local machine we create a configuration file for target host. we can get an example configuration here. Clone the repo to your local machine at the path where you want to store the configuration for your target host.
$ git clone https://github.com/nix-community/nixos-anywhere-examples.git
$ mv nixos-anywhere-examples/ nixie
Let examine what happening here.
we have a flake.nix
that will be evaluated during system build. In it we have different configurations for various systems such as hetzer-cloud, digital ocean and a generic one for general usage. For our use, we’ll just keep the digital ocean and remove everything else. The digital ocean configuration imports several modules we’ll see further.
# flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
inputs.disko.url = "github:nix-community/disko";
inputs.disko.inputs.nixpkgs.follows = "nixpkgs";
outputs =
{
nixpkgs,
disko,
...
}:
{
# tested with 2GB/2CPU droplet, 1GB droplets do not have enough RAM for kexec
# nixos-anywhere --flake .#digitalocean --generate-hardware-config nixos-generate-config ./hardware-configuration.nix <hostname>
nixosConfigurations.digitalocean = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./digitalocean.nix
disko.nixosModules.disko
{ disko.devices.disk.disk1.device = "/dev/vda"; }
./configuration.nix
];
};
};
}
now lets see disk-config.nix
. This is responsible for partitioning the filesystem using the disko module. You can change this file if you want to but we’ll just go with default for now. Here we have to change one tiny thing
# disk-config.nix
{ lib, ... }:
{
disko.devices = {
disk.disk1 = {
device = lib.mkDefault "/dev/vda"; # replace the drive with the primary drive we noted earlier
type = "disk";
};
# ...
};
}
The digitalocean.nix
contains details needed to tailor the configuration for digital ocean purposes. We don’t need to change anything it but you can read it.
We also have the configuration.nix
that contains the configuration to be deployed on the target host and hardware-configuration.nix
contains well nothing cause it’ll be generated during runtime to tailor to our specific host. Make some minimal changes to the configuration.nix
{
#...
users.users.root.initialPassword = "password"; # we will use sops-nix in future to store password as a secret
users.users.root.openssh.authorizedKeys.keys =
[
# change this to your ssh key
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMr8qETvTGSBHbDXiOk+QOhfRvKT0wDvOwtuMEDT+Bcc [email protected]"
];
#...
}
I forgot one thing. create a git config so we dont have mention the ip of our vps. For this cd into ~/.ssh
and create a file named config
and add the following content in it.
Host nixie
HostName 165.22.208.155
User root
IdentityFile ~/.ssh/id_ed25519_nixie
Now the system is ready to be deployed so just run
$ nix run nixpkgs#nixos-anywhere -- --flake .#digitalocean --generate-hardware-config nixos-generate-config ./hardware-configuration.nix nixie
We wait and stare at our text moving on our screen. If everything goes fine running the following command should show NixOS
instead of Ubuntu
ssh nixie uname -a
Linux nixie 6.12.30 #1-NixOS SMP PREEMPT_DYNAMIC Thu May 22 12:29:54 UTC 2025 x86_64 GNU/Linux
So if everything goes. you have installed nixos successfully on your vps. congratulations.
note: if you get an error saying “WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!” go to
~/.ssh/known_hosts
and remove the lines that have your vps ip_address in them.
We can now alter the configuration.nix
and add some stuff and rebuild the target host. Add some packages to the config
environment.systemPackages = map lib.lowPrio [
pkgs.curl
pkgs.git
pkgs.fastfetch
];
and rebuild and test.
$ nixos-rebuild switch --flake .#digitalocean --target-host nixie
$ ssh nixie fastfetch
▗▄▄▄ ▗▄▄▄▄ ▄▄▄▖ root@nixos
▜███▙ ▜███▙ ▟███▛ ----------
▜███▙ ▜███▙▟███▛ OS: NixOS 25.11 (Xantusia) x86_64
▜███▙ ▜██████▛ Host: Droplet (20171212)
▟█████████████████▙ ▜████▛ ▟▙ Kernel: Linux 6.12.30
▟███████████████████▙ ▜███▙ ▟██▙ Uptime: 49 mins
▄▄▄▄▖ ▜███▙ ▟███▛ Packages: 407 (nix-system)
▟███▛ ▜██▛ ▟███▛ Shell: sshd-session: root@notty
▟███▛ ▜▛ ▟███▛ Display (QEMU Monitor): 1024x768 @ 75 Hz in 13"
▟███████████▛ ▟██████████▙ Terminal:
▜██████████▛ ▟███████████▛ CPU: DO-Premium-AMD @ 2.00 GHz
▟███▛ ▟▙ ▟███▛ GPU: RedHat Virtio 1.0 GPU
▟███▛ ▟██▙ ▟███▛ Memory: 268.56 MiB / 1.93 GiB (14%)
▟███▛ ▜███▙ ▝▀▀▀▀ Swap: Disabled
▜██▛ ▜███▙ ▜██████████████████▛ Disk (/): 2.64 GiB / 48.43 GiB (5%) - ext4
▜▛ ▟████▙ ▜████████████████▛ Local IP (ens3):
▟██████▙ ▜███▙ Locale: en_US.UTF-8
▟███▛▜███▙ ▜███▙
▟███▛ ▜███▙ ▜███▙
▝▀▀▀ ▀▀▀▀▘ ▀▀▀▘
Part 2 - Managing your system using deploy-rs
We can use nixos-rebuild to deploy our nixos configuration but there are many tools that expands on it and provide more features. One such tool is deploy-rs. deploy-rs has many features such as
- rootless deployments
- magic rollback - feature to prevent making changing that can make the target machine inaccessible.
- multi-profile deployments - allow deploying multiples profiles independently to different users on same machine or different machines.
To get start import the deploy-rs input in the flake.nix
and change the configuration as shown.
{
# ...
inputs.deploy-rs.url = "github:serokell/deploy-rs";
outputs =
{ self
, nixpkgs
, disko
, deploy-rs
, ...
}:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
deployPkgs = import nixpkgs {
inherit system;
overlays = [
deploy-rs.overlays.default
(self: super: { deploy-rs = { inherit (pkgs) deploy-rs; lib = super.deploy-rs.lib; }; })
];
};
in
{
nixosConfigurations.digitalocean = nixpkgs.lib.nixosSystem {
system = system;
modules = [
./digitalocean.nix
disko.nixosModules.disko
{ disko.devices.disk.disk1.device = "/dev/vda"; }
./configuration.nix
];
};
deploy.nodes = {
digitalocean = {
hostname = "nixie";
fastConnection = true;
profiles = {
system = {
sshUser = "root";
user = "root";
path = deployPkgs.deploy-rs.lib.activate.nixos self.nixosConfigurations.digitalocean;
};
};
};
};
checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib;
};
}
Let see whats happening
- we import the deploy-rs input at the top which outputs deploy-rs which we can use in the config.
- we create nodes which define a single host/server and the profiles we want to run in it.
- each node will have a profile in it which can be used to deploy different configurations to the same node depending on your need.
- the last line defines a set of checks and validations for your deployment configuration.
$ deploy
This should deploy the configuration to the target host using deploy-rs now.
Part 3 - Managing Secrets using sops-nix
sops-nix is a secret management tool for nixos that allows us to encrypt and store secrets in a declarative way. It enable secrets to be decrypted at activation time and managed securely. sops-nix provides us two formats for generating keys: age and pgp. We can also create three types of keys :-
- key that was made for other purposes
- key made for a standalone purpose
- key that was derived from a ssh key. We create a standalone age key for encrypting and decrypting keys locally. This will making editing and updating keys locally much easier. To create a standalone key run the following commands
$ mkdir -p ~/.config/sops/age
$ age-keygen -o ~/.config/sops/age/keys.txt
Public key: age12zlz6lvcdk6eqaewfylg35w0syh58sm7gh53q5vvn7hd7c6nngyseftjxl
# a public key would be printed on your console.
# if for some reason it doesnt get printed run the following command
$ age-keygen -y ~/.config/sops/age/keys.txt
create a .sops.yaml
at the root of your nix configuration and add the following content and replace the user age key with key generate now.
keys:
- &users:
- &quantinium age12zlz6lvcdk6eqaewfylg35w0syh58sm7gh53q5vvn7hd7c6nngyseftjxl
creation_rules:
- path_regex: secrets/[^/]+.(yaml|json|env|ini)$
key_groups:
- age:
- *quantinium
now we create a key for our server which we will deploy the configuration. this key is needed for decryption of secrets on the target machine (our VPS).
to get the public key of our target machine
# replace example.com with the ip_address of machine
$ nix-shell -p ssh-to-age --run 'ssh-keyscan example.com | ssh-to-age'
OR
# ssh into the machine using ssh nixie@ip_address and run
$ nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
copy the public and key and open .sops.yaml
again and add the host public key
# .sops.yaml
keys:
- &users:
- &quantinium age12zlz6lvcdk6eqaewfylg35w0syh58sm7gh53q5vvn7hd7c6nngyseftjxl
- &hosts:
- &nixie age1rgffpespcyjn0d8jglk7km9kfrfhdyev6camd3rck6pn8y47ze4sug23v3
creation_rules:
- path_regex: secrets/[^/]+.(yaml|json|env|ini)$
key_groups:
- age:
- *quantinium
- *nixie
create a directory secrets/secrets.yaml
to store your secrets using the following command. it’ll automatically open the file in the editor configured as $EDITOR
$ mkdir secrets
$ sops secrets/secrets.yaml # this command would be used to decrypt and edit the secrets in future
Now we put our super secret password in secrets.yaml
but before that they need to be hashed using mkpasswd
.
$ mkpasswd -s
Password: password
$y$j9T$ug6flCCQvVzpEwP8NB0g0.$R5AUc9gD4rE4jIpsFwvIPh2Z6rKVC79WmVF0IfXtBr5
reopen the secrets.yaml
using sops secrets/secrets.yaml
and add the following content
# secrets.yaml
nixie_password: $y$j9T$ug6flCCQvVzpEwP8NB0g0.$R5AUc9gD4rE4jIpsFwvIPh2Z6rKVC79WmVF0IfXtBr5
services:
secret:
message: HI
Exit the file and if you try to access the secrets.yaml
, the content should be encrypted and should seem smth like this
# secrets.yaml
nixie_password: ENC[AES256_GCM,data:I5s1QFHKYiNmbF3kXIHdiyCwKlvsutBnH6pQ1W/mf+rMgBsHtOHwLRoFJ34ZCtas0L2rHX9LlB6XDBScMBsxYMyuYejtyoQJ/A==,iv:m3KjMnKH2ChpMbKuuF5qGaXskkEYc8d3R5TjIBwRC38=,tag:VZuYJh0rgx557NB7ucbebw==,type:str]
services:
secret:
message: ENC[AES256_GCM,data:u5U=,iv:QamOdhj7V0iqQlNv+mdBMLGsnemoFi80ppe+AFG3GaY=,tag:GbtNAsZ6wy6NE7A5AeCh/Q==,type:str]
# ...
Now lets put the secrets in our configuration. as usual add the sops-nix input in our flake.nix
and add the sops modules.
# flake.nix
{
inputs.sops-nix.url = "github:Mic92/sops-nix";
inputs.sops-nix.inputs.nixpkgs.follows = "nixpkgs";
outputs = { self , nixpkgs, disko, sops-nix, deploy-rs, ...}: {
# ....
modules = [
#....
./configuration.nix
sops-nix.nixosModules.sops
];
};
};
}
in configuration.nix
add the following
# configuration.nix
sops = {
defaultSopsFile = ../../secrets/secrets.yaml;
defaultSopsFormat = "yaml";
age = {
# import host keys as ssh keys
sshKeyPaths = ["/etc/ssh/ssh_host_ed25519_key"];
# this will use an age key that is already expected to be in the filesystem
keyFile = "/var/lib/sops-nix/keys.txt";
# if the above key is not there we create the it.
generateKey = true;
};
};
Now lets use these secrets in our config. I’m gonna make the systemd service that uses the message and we can change our password to sps secret.
sops.secrets = {
"nixie_password" = { };
"services/secret/message" = {
owner = "nixie";
};
};
systemd.services."secretservice" = {
description = "Write secret message to file";
after = [ "sops-nix.service" ];
wantedBy = [ "multi-user.target" ];
script = ''
echo "Secret path: ${config.sops.secrets."services/secret/message".path}" >> /home/nixie/secret
echo "heres the super secret message: $(cat ${config.sops.secrets."services/secret/message".path})" >> /home/nixie/secret
'';
serviceConfig = {
User = "nixie";
WorkingDirectory = "/home/nixie";
PermissionsStartOnly = true;
};
};
Also add the password for the user
sops.secrets.nixie_password.neededForUsers = true;
users.mutableUsers = false;
users.users.nixie = {
isNormalUser = true;
extraGroups = [ "wheel" ];
hashed
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMr8qETvTGSBHbDXiOk+QOhfRvKT0wDvOwtuMEDT+Bcc [email protected]"
];
};
now deploy
$ deploy
This should create a user named nixie whose password is being provided using sops and a systemd service that outputs the line at /home/nixie/secret
We can check this by
$ ssh nixie cat /home/nixie/secret
Secret path: /run/secrets/services/secret/message
heres the super secret message: HI
This concludes our endeavour to install nixos on a vps, deploy it using deploy-rs and use sops-nix for secrets management.
If you have reached till the end. Thanks for reading it and if you have any queries related to it you can message me on any of the socials.
References:-
- vimjoyer - youtube.com/@vimjoyer
- EmergentMind - youtube.com/@Emergent_Mind
- nixos-anywhere - github.com/nix-community/nixos-anywhere
- nixos-anywhere template - github.com/nix-community/nixos-anywhere
- sops-nix - github.com/Mic92/sops-nix
- deploy-rs - github.com/serokell/deploy-rs
- repo - github.com/quantinium3/installing-nixos-on-vps