Persistence
Persistence Best Practices
Rules for using
environment.persistencewith impermanence without breaking NixOS activation.
The Rule
Never persist a directory that contains files created by NixOS
activation (environment.etc, home.file, home.configFile,
home.dataFile). Persist individual files instead.
Why
Impermanence bind-mounts /persist/<path> over /<path> via systemd
mount units that run at local-fs.target — before NixOS
activation. On first boot /persist/<path> is empty, hiding whatever
activation places there.
Correct (file-level)
environment.persistence."/persist".files = [
"/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key.pub"
];
Wrong (directory-level)
# /etc/ssh/sshd_config is a symlink created by services.openssh
# via environment.etc. The empty bind mount hides it on first boot.
environment.persistence."/persist".directories = ["/etc/ssh"];
When directory persistence is fine
Directory persistence is safe for runtime data — files created by services at runtime, not by NixOS activation:
/var/lib— service databases, state/var/log— log files/var/spool— mail, print queues- User caches:
.cache,.local/share,.local/state
These directories contain data that accumulates after boot and is never managed declaratively by NixOS.
neededForBoot and hideMounts
-
neededForBoot = true(set bypersist.nix): Ensures/persistis mounted in initrd stage 1, before the real root switches. Required for persisted files needed by early boot (SSH host keys, machine-id). -
hideMounts = true(set in each persist block): Tells GVFS to hide the mount point in file managers. Only affects desktop environments, has no effect on system behavior.
Real-world bug: SSH host keys
The modules/services/net/ssh.nix module persisted /etc/ssh as a
directory. On first boot, the bind mount hid the sshd_config symlink
that services.openssh creates via environment.etc. sshd failed with
“sshd_config: No such file or directory”.
Fix: Persist only the host key files individually. sshd_config
is a nix store symlink that doesn’t need persistence.
Module convention: gate on modules.persist.enable
All home.persistence and environment.persistence entries in feature
modules must be conditional on config.modules.persist.enable. This
ensures persistence is truly opt-in — when a host sets
modules.persist.enable = false, no persistence entries are created and
the impermanence module produces no uid/gid reassignment warnings.
# Simple list
home.persistence."/persist".directories =
mkIf config.modules.persist.enable [
".cache/myapp"
];
# Wrapping an existing conditional
home.persistence."/persist".directories =
mkIf config.modules.persist.enable (
mkIf (!cfg.sandbox) [".config/myapp"]
);
# Whole-attrset form
home.persistence."/persist" = mkIf config.modules.persist.enable {
directories = [".config/myapp"];
files = [".myapprc"];
};
The base modules/persist.nix handles two cases:
- Enabled: maps
home.persistence→environment.persistencewith full directory set including/var/lib. - Disabled: force-clears
environment.persistenceviamkForce {}to suppress phantom entries from flake inputs.
Real-world bug: Podman registries.conf
The modules/services/virt/podman.nix module persisted
/etc/containers as a directory, hiding registries.conf created by
environment.etc. Same pattern, same fix: remove the directory persist
since the file is NixOS-managed.