-
Notifications
You must be signed in to change notification settings - Fork 0
Managing Secrets
How to securely manage sensitive data in your NullOS configuration.
NullOS configuration is stored in Git, but some information should remain private:
- Passwords
- API keys
- SSH keys
- VPN credentials
- Backup encryption keys
This guide covers strategies for keeping secrets out of version control.
The simplest approach - variables.nix is already gitignored:
# variables.nix (gitignored)
{
username = "yourname";
gitEmail = "private@email.com";
resticRepository = "sftp:user@private-server.com:/backups";
# Other private settings
}Create separate files for secrets:
# secrets.nix (add to .gitignore)
{
apiKeys = {
github = "ghp_xxxxxxxxxxxx";
openai = "sk-xxxxxxxxxxxxxxx";
};
vpnCredentials = {
mullvad = "1234567890123456";
};
}Import in configuration:
let
secrets = import ./secrets.nix;
in
{
# Use secrets here
environment.variables.GITHUB_TOKEN = secrets.apiKeys.github;
}.gitignore:
variables.nix
secrets.nix
*.secret.nixStore secrets in files outside the Nix store:
# modules/services/backup.nix
services.restic.backups.nsdata = {
repository = vars.resticRepository;
passwordFile = "/etc/nixos/secrets/restic-password";
# File is read at runtime, not stored in Nix store
};# Create secrets directory
sudo mkdir -p /etc/nixos/secrets
sudo chmod 700 /etc/nixos/secrets
# Add password
echo "my-secret-password" | sudo tee /etc/nixos/secrets/restic-password
sudo chmod 600 /etc/nixos/secrets/restic-passwordFor user-level secrets:
mkdir -p ~/.config/secrets
chmod 700 ~/.config/secrets
echo "token" > ~/.config/secrets/api-token
chmod 600 ~/.config/secrets/api-tokenUse in home configuration:
programs.git.extraConfig = {
github.user = builtins.readFile "/home/${username}/.config/secrets/github-user";
};For advanced secret management, use agenix.
Add to flake.nix inputs:
inputs = {
# Existing inputs...
agenix = {
url = "github:ryantm/agenix";
inputs.nixpkgs.follows = "nixpkgs";
};
};Import module:
# In nixosConfiguration modules
imports = [
inputs.agenix.nixosModules.default
];ssh-keygen -t ed25519 -f ~/.ssh/agenix -C "agenix"# secrets/secrets.nix
let
user1 = "ssh-ed25519 AAAAC3... user@machine1";
user2 = "ssh-ed25519 AAAAC3... user@machine2";
allUsers = [ user1 user2 ];
in
{
"restic-password.age".publicKeys = allUsers;
"vpn-config.age".publicKeys = allUsers;
"api-keys.age".publicKeys = allUsers;
}# Install agenix CLI
nix profile install github:ryantm/agenix
# Encrypt a secret
agenix -e restic-password.age# In configuration
age.secrets.restic-password = {
file = ./secrets/restic-password.age;
owner = "root";
mode = "600";
};
# Reference in services
services.restic.backups.nsdata = {
passwordFile = config.age.secrets.restic-password.path;
};Alternative to agenix using SOPS.
# flake.nix inputs
inputs.sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
# Import module
imports = [
inputs.sops-nix.nixosModules.sops
];keys:
- &admin age1xxxxxxxxxxxxxxxxxx
creation_rules:
- path_regex: secrets/.*\.yaml$
key_groups:
- age:
- *admin# Install sops
nix-shell -p sops
# Create secret file
sops secrets/passwords.yamlYAML content:
restic_password: my-secret-password
github_token: ghp_xxxxxxsops.secrets = {
"restic_password" = {
sopsFile = ./secrets/passwords.yaml;
};
};
# Reference
services.restic.backups.nsdata = {
passwordFile = config.sops.secrets.restic_password.path;
};ssh-keygen -t ed25519 -C "your@email.com"Don't put in Nix config! Instead:
# Copy to new machine
ssh-copy-id user@newmachineOr manually:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
cp id_ed25519* ~/.ssh/
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pubPublic SSH config is safe:
# home/ssh.nix
programs.ssh = {
enable = true;
matchBlocks = {
"github.com" = {
user = "git";
identityFile = "~/.ssh/id_ed25519";
};
"myserver" = {
hostname = "192.168.1.100";
user = "admin";
identityFile = "~/.ssh/id_ed25519";
};
};
};Tailscale auth is handled via web login:
tailscale up
# Follow authentication promptNo secrets in configuration needed!
Store account number in secret file:
echo "1234567890123456" | sudo tee /etc/nixos/secrets/mullvad-accountUse in script:
mullvad account login "$(cat /etc/nixos/secrets/mullvad-account)"Store private key separately:
# modules/services/vpn.nix
networking.wireguard.interfaces.wg0 = {
privateKeyFile = "/etc/nixos/secrets/wireguard-private.key";
# Public config is safe
peers = [
{
publicKey = "xxxx="; # Safe to commit
endpoint = "vpn.example.com:51820";
}
];
};Authenticate interactively:
gh auth loginStores token in ~/.config/gh/ (not in Nix config).
Use system credential manager:
programs.git = {
enable = true;
extraConfig = {
credential.helper = "store"; # or "cache"
};
};Credentials stored in ~/.git-credentials (gitignored).
Store in shell profile (outside Nix):
# ~/.zshenv (not managed by Nix)
export OPENAI_API_KEY="sk-xxxxx"
export GITHUB_TOKEN="ghp_xxxxx"Load from files at runtime:
# home/default.nix
home.sessionVariables = {
GITHUB_TOKEN = builtins.readFile /home/${username}/.config/secrets/github-token;
};Many apps have their own credential storage:
- VSCode: Stores tokens in encrypted keyring
-
Docker:
~/.docker/config.json -
AWS:
~/.aws/credentials
These don't need to be in Nix config.
Never commit the password!
services.restic.backups.nsdata = {
repository = "sftp:user@server:/backup";
# Read from file
passwordFile = "/etc/nixos/secrets/restic-password";
# Or use environment variable
# environmentFile = "/etc/nixos/secrets/restic-env";
};sudo mkdir -p /etc/nixos/secrets
echo "my-strong-password" | sudo tee /etc/nixos/secrets/restic-password
sudo chmod 600 /etc/nixos/secrets/restic-password✅ Safe to commit:
- Usernames (if not sensitive)
- Hostnames
- Public keys
- Public configuration
- Example/template files
❌ Never commit:
- Passwords
- Private keys
- API tokens
- Auth credentials
# Secrets
variables.nix
secrets.nix
*.secret.nix
*.age
*.key
*-password
*-token
# Directories
secrets/
.secrets/# Check what will be committed
git status
git diff --cached
# Search for potential secrets
git diff --cached | grep -i "password\|secret\|key"- Public: Example configs, documentation
-
Private: Your actual
variables.nixand secrets
Install pre-commit hooks:
# Install gitleaks
nix-shell -p gitleaks
# Scan for secrets
gitleaks detectImmediately:
- Rotate the secret (change password, revoke token)
- Remove from Git history:
# Use BFG Repo-Cleaner
nix-shell -p bfg-repo-cleaner
bfg --replace-text passwords.txt .git
git reflog expire --expire=now --all
git gc --prune=now --aggressive- Force push (if remote)
git push --force- Notify anyone who cloned the repo
NullOS/
├── .gitignore # Ignore secrets
├── variables.nix # Gitignored
├── secrets/ # Gitignored
│ ├── restic-password
│ ├── vpn-config
│ └── api-keys.env
└── secrets.age/ # agenix encrypted
├── secrets.nix
└── *.age
variables.nix
secrets/
secrets.nix
*.age
!secrets.age/secrets.nix
*.secret.*# modules/services/backup.nix
{
services.restic.backups.nsdata = {
repository = vars.resticRepository; # From variables.nix
passwordFile = "/etc/nixos/secrets/restic-password";
paths = [ "/home/${vars.username}" ];
exclude = [
"/home/${vars.username}/.cache"
"/home/${vars.username}/.secrets"
];
};
}- Installation - Initial setup
- Backup - Backup configuration
- Variables Reference - Configuration variables
- Troubleshooting - Common issues