Sops Nix
Updated: February 8, 2026
Secrets management for NixOS
Better than agenix || ragenix because it works with Home Manager.
Table of Contents
Each system needs the
keys.txtplaced at~/.config/sops/age/keys.txt
First we need to setup .sops.yaml ++ keys.txt then secrets.yaml will handle the actual secrets.
Installs
Install sops and age
environment.systemPackages = with pkgs; [
age
sops
];
SSH KEYS
ssh keys need to be converted to age keys to add to .sops.yaml
This is the file that allows the use of sops to encrypt and decrypt secrets.yaml
The pub age keys are placed in the.sops.yamlfile. The file must have this exact name.
keys.txtmust also bear the exact name being placed at~/.config/sops/age/keys.txtwith corresponding private keys
# create a folder that sops expects to exist when creating keys
mkdir -p ~/.config/sops/age
# create a new key from scratch
age -c age-keygen -o ~/.config/sops/age/keys.txt
# create an age key from a private ssh key
nix run nixpkgs#ssh-to-age -- -private-key -i ~/.ssh/private > ~/.config/sops/age/keys.txt
nix-shell -p ssh-to-age --run 'cat ~/.ssh/id_ed25519.pub | ssh-to-age'
# print the public key of ~/.config/sops/age/keys.txt
age -c age-keygen -y ~/.config/sops/age/keys.txt
.sops.yaml
# create the config file at root of the flake and secrets folder while we are at it.
vim .sops.yaml
├── flake.nix
├── .sops.yaml
├── secrets/
│ ├── secrets.yaml
# .sops.yaml
# nix-shell -p ssh-to-age --run 'cat ~/.ssh/id_ed25519.pub | ssh-to-age'
# sops updatekeys secrets/secrets.yaml if you add/remove any keys or if no sops installed:
# nix-shell -p sops --run "sops updatekeys secrets/secrets.yaml"
keys:
- &users:
- &megacron agea8a7dsf98a7sydf8as7ydf8as7ydfaos87ydfoa7sdyfoa8s7ydfouahsdfliuhsd # public key user
- &hosts:
- &blackout agea8a7dsf98a7sydf8as7ydf8as7ydfaos87ydfoa7sdyfoa8s7ydfouahsdfliuhsd # age formatted host key
- &energon agea8a7dsf98a7sydf8as7ydf8as7ydfaos87ydfoa7sdyfoa8s7ydfouahsdfliuhsd # age formatted host key
- &galvatron agea8a7dsf98a7sydf8as7ydf8as7ydfaos87ydfoa7sdyfoa8s7ydfouahsdfliuhsd # age formatted host key
- &ratchet agea8a7dsf98a7sydf8as7ydf8as7ydfaos87ydfoa7sdyfoa8s7ydfouahsdfliuhsd # age formatted host key
creation_rules:
- path_regex: secrets/secrets.yaml$
key_groups:
- age:
- *megacron
- *hosts
Create indentation rules for sops. Sops overrides the editor so best to set them here.
stores:
json:
indent: 2
json_binary:
indent: 2
yaml:
indent: 2
YAML stream is basically having more than one document in one file.
---
data: foo
---
data: bar
yaml and json top level arrays are not allowed. This will work as sops key can be added at the same level as a data key.
data:
- some
- array
- elements
Secrets
Prior to using sops command there must exist a secrets directory in the root of the flake.
Files in the secrets folder can be called anything you want and you can have several of them.
# create/edit a secrets file at root of the flake
sops secrets.yaml
# if you only have nix installed use nix shell
nix-shell -p sops --run "sops secrets/secrets.yaml"
# This is a comment
example_key: example_value
example_array:
- example_value1
- example_value2
example_number: 1234.5678
example_boolean:
- true
- false
Commands
sops
-d # decrypt
-e # encrypt
-i # "in place"
-r # rotate data keys
# extract specific values like keys
sops decrypt --extract '["ssh"]["key"]' ~/sops/example.yaml
User Passwords
Sops must run after NixOS creates users so it is not possible to set with
hashedPasswordFile.
# hash a password
echo "password" | mkpasswd -s
# configuration.nix
{ config, ... }:
{
sops.secrets.megacron-password.neededForUsers = true;
users.users.megacron = {
isNormalUser = true;
hashedPasswordFile = config.sops.secrets.megacron-password.path;
};
}
# secrets.yaml
private_keys:
megacron: | # key must be same level of indentation as the pipe
-----BEGIN PRIVATE KEY-----
asfdasdfasdfioasdfoiasjdfoasidjfaosijdfasidjf
-----END PRIVATE KEY-----
megacronPW: passwordwehashed # mkpasswd -s
smtp-password: asfdasdfasdfioasdfoiasjdfoasidjfaosijdfasidjf
megacron-password: asfdasdfasdfioasdfoiasjdfoasidjfaosijdfasidjf
Nix Install
We will go into making a sops.nix module at the end.
This is just to understand the elements involved on how sops works.
# add as an input to flake (follows line optional)
sops-nix.url = "github:Mic92/sops-nix";
sops-nix.inputs.nixpkgs.follows = "nixpkgs";
# then also add to nixosConfigurations
specialArgs = { inherit inputs; }; <-This
modules = [ ./configuration.nix ];
# open configuration.nix and add to imports
imports = [
inputs.sops-nix.nixosModules.sops
];
sops = {
defaultSopsFile = ./secrets/secrets.yaml;
defaultSopsFormat = "yaml";
age.keyFile = "home/user/.config/sops/age/keys.txt";
# how to place secret values
secrets.some-key = { };
secrets."dir/subdir/secret" = { };
# add another owner to your keys
owner = "bob"
owner = config.users.users.bob.name;
};
Now lets say we want to access and reveal a value
# this would cat out the value in plain text
$(cat ${config.sops.secrets."dir/subdir/secret".path})
sops.nix
{ inputs, config, ... }:
{
imports = [
inputs.sops-nix.nixosModules.sops
];
sops = {
defaultSopsFile = "secrets.yaml";
validateSopsFiles = false;
age = {
sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; # auto import host ssh keys as age keys
keyFile = "/var/lib/sops/age/key.txt"; # expected age key to already be on filesystem
generateKey = true; # generate a new age key if one does not exist
};
# secrets will be output to /run/secrets
# e.g. /run/secrets/msmtp-password
# secrets required for user creation are handled in respective ./users/<username>.nix files
# because they will be output to /run/secrets-for-users and only when the user is assigned to a host.
secrets = {
megacronPW = { };
megacronPW.neededForUsers = true;
msmtp-host = { };
msmtp-address = { };
msmtp-password = { };
# extract to default pam-u2f authfile location for passwordless sudo. see ../optional/yubikey
"yubico/u2f_keys" = {
path = "/home/ta/.config/Yubico/u2f_keys";
};
};
};
}
# configuration.nix
users.mutableUsers = false; # allow pw to be set during build process
users.users.username = {
hashedPasswordFile = config.sops.secrets.username.path;
openssh.authorizedKeys = [
(builtins.readFile ./secrets/id_blackout.pub)
];
};
Troubleshooting
sometimes rebuild may fail to start sops service.
Try running command with a--refreshflag
If that fails, then try
# afterward run without --refresh
systemctl --user reset-failed