Secrets in Nix, Publicly
Note
May 6, 2026
· ~3 min read
I keep my entire Nix config in a public repo. Dotfiles, packages, shell setup, everything. But I also need my SSH and GPG keys on every machine I use. Storing private keys in a public repo is obviously a terrible idea. So how do you manage secrets in Nix without going private?
The answer is agenix.
How it works
agenix uses age encryption. You encrypt secrets with your SSH public key,
and only someone with the matching private key can decrypt them. The
encrypted .age files live in your repo. On rebuild, agenix decrypts them
automatically.
nix
├── secrets/ ~/.local/share/ssh/
│ ├── ssh-key.age ───────────► id_rsa (decrypted on rebuild)
│ └── gpg-key.age id_ed25519 (decryption key)
├── secrets.nix
└── modules/agenix.nix
Anyone can clone the repo. Only I can use it.
Setting it up
I generated a dedicated ed25519 key for agenix. My existing RSA key stays
untouched for GitHub, servers, and everything else.
ssh-keygen -t ed25519 -C "agenix@pwnwriter" -f ~/.local/share/ssh/id_ed25519 -N "" # FIXME: change comment and path
Add agenix to the flake
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
home-manager = { ... };
darwin = { ... };
catppuccin.url = "github:catppuccin/nix";
+
+ agenix = {
+ url = "github:ryantm/agenix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
};
Define who can decrypt what
A secrets.nix file at the repo root maps public keys to secret files.
let
pwnwriter = "ssh-ed25519 AAAA... agenix@pwnwriter"; # FIXME: your ed25519 public key
in
{
"secrets/ssh-key.age".publicKeys = [ pwnwriter ]; # FIXME: your secret files
"secrets/gpg-key.age".publicKeys = [ pwnwriter ];
}
This is just a list of public keys. Safe to commit. It tells agenix which keys are allowed to encrypt and decrypt each secret.
Encrypt the secrets
nix run github:ryantm/agenix -- -e secrets/ssh-key.age
nix run github:ryantm/agenix -- -e secrets/gpg-key.age
This opens your editor. Paste the secret, save, done. The output is an
encrypted .age file that only your ed25519 key can unlock.
Wire it into home-manager
I created a module that tells agenix where the decryption key lives and where to place the decrypted secrets.
# modules/agenix.nix
{ config, ... }:
{
age = {
identityPaths = [
"${config.xdg.dataHome}/ssh/id_ed25519" # FIXME: path to your ed25519 key
];
secrets = {
ssh-key = {
file = ../secrets/ssh-key.age;
path = "${config.xdg.dataHome}/ssh/id_rsa"; # FIXME: where to place decrypted key
};
gpg-key = {
file = ../secrets/gpg-key.age;
};
};
};
}
Then each machine needs the agenix home-manager module. Here’s the diff for one of my darwin machines:
# machines/earlymoon.nix
let
inherit (inputs)
darwin
home-manager
catppuccin
+ agenix
;
in
darwin.lib.darwinSystem {
modules = [
home-manager.darwinModules.home-manager
{
home-manager.users.pwnwriter = { # FIXME: your username
imports = [
./../modules
catppuccin.homeModules.catppuccin
+ agenix.homeManagerModules.default
];
};
}
];
}
Same pattern for every machine. My standalone home-manager config on Linux gets the same one-liner.
Rebuild
darwin-rebuild switch --flake .#earlymoon # FIXME: your machine name
That’s it. agenix reads the ed25519 private key, decrypts the .age files, and
drops them where the module says. My SSH key appears at
~/.local/share/ssh/id_rsa, ready to use.
Five touchpoints total. No external services, no key servers, no runtime daemons. Just age encryption and a rebuild.
NOW,
Adding a new secret later
# encrypt
nix run github:ryantm/agenix -- -e secrets/new-secret.age
# add to secrets.nix
"secrets/new-secret.age".publicKeys = [ pwnwriter ];
# add to modules/agenix.nix
age.secrets.new-secret.file = ../secrets/new-secret.age;
To edit an existing secret, same encrypt command. It decrypts to your editor, you change it, and it re-encrypts on save.