Backups & Recovery
The server uses Restic for encrypted, deduplicated backups to a local SATA drive.
Source: server/modules/restic.nix
Configuration
services.restic.backups.backup = {
initialize = true;
passwordFile = config.age.secrets.restic_password.path;
repository = "/backup";
extraBackupArgs = [ "--exclude-caches" "--compression=max" ];
paths = [
"/persist/etc/ssh"
"/persist/var/lib/nixos-containers/immich/var/lib/immich"
"/persist/var/lib/nixos-containers/immich/var/lib/postgresql"
"/persist/var/lib/nixos-containers/opencloud/var/lib/opencloud"
"/persist/var/lib/nixos-containers/vaultwarden/var/lib/vaultwarden"
];
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 2"
"--keep-monthly 1"
"--keep-yearly 0"
];
timerConfig = {
Persistent = true;
OnCalendar = "daily";
RandomizedDelaySec = 3600;
};
};Backup Paths
| Path | Service | Contents |
|---|---|---|
/persist/etc/ssh | System | SSH host keys (critical for agenix secret decryption) |
.../immich/var/lib/immich | Immich | Photo/video library |
.../immich/var/lib/postgresql | Immich | PostgreSQL database (metadata, ML data) |
.../opencloud/var/lib/opencloud | OpenCloud | Cloud storage files |
.../vaultwarden/var/lib/vaultwarden | Vaultwarden | Password vault database |
What's NOT Backed Up
- AdGuard Home config: Ephemeral by design, declared entirely in Nix
- Authelia state: Session data is ephemeral; user database is an agenix secret in the repo
- Traefik state: ACME certificates are re-obtained automatically
- Nix store (
/nix): Rebuilt from the flake on deployment - Root filesystem (
/): Wiped on every boot (impermanence)
Backup Settings
| Setting | Value | Rationale |
|---|---|---|
repository | /backup | XFS-formatted SATA SSD mounted at /backup |
compression | max | Maximum zstd compression; CPU time is cheap on a homelab |
--exclude-caches | Flag | Skips directories with a CACHEDIR.TAG file |
initialize | true | Automatically initialises the repo if it doesn't exist |
Encryption
The backup repository is encrypted with a password managed by agenix:
age.secrets.restic_password = {
mode = "440";
owner = "root";
group = "root";
file = ../secrets/restic_password.age;
};The password is decrypted at activation and passed to Restic via passwordFile. The encrypted .age file is in the Git repository; the plaintext never touches disk (it lives in /run/agenix/ on tmpfs).
Schedule
| Setting | Value |
|---|---|
OnCalendar | daily |
RandomizedDelaySec | 3600 (1 hour) |
Persistent | true |
- Runs once per day at a random time within a 1-hour window
Persistent = trueensures a missed backup (e.g., during downtime) runs immediately after the next boot
Retention Policy
--keep-daily 7
--keep-weekly 2
--keep-monthly 1
--keep-yearly 0| Period | Keep | Meaning |
|---|---|---|
| Daily | 7 | One snapshot per day for the last 7 days |
| Weekly | 2 | One snapshot per week for the last 2 weeks |
| Monthly | 1 | One snapshot per month for the last month |
| Yearly | 0 | No yearly snapshots |
Maximum retention is approximately 1 month of history. Pruning runs automatically after each backup.
Monitoring
# Check timer status
systemctl list-timers restic-backups-backup.timer
# View last backup log
journalctl -u restic-backups-backup.service -b
# List snapshots
restic -r /backup --password-file /run/agenix/restic_password snapshots
# Check repository health
restic -r /backup --password-file /run/agenix/restic_password checkRecovery Procedures
Restore a Single File
# List files in the latest snapshot
restic -r /backup --password-file /run/agenix/restic_password ls latest
# Restore a specific file to a target directory
restic -r /backup --password-file /run/agenix/restic_password restore latest \
--target /tmp/restore \
--include "/persist/var/lib/nixos-containers/vaultwarden/var/lib/vaultwarden/db.sqlite3"Restore a Full Service
Example: restoring Vaultwarden data:
# 1. Stop the container
sudo machinectl stop vaultwarden
# 2. Restore data
sudo restic -r /backup --password-file /run/agenix/restic_password restore latest \
--target / \
--include "/persist/var/lib/nixos-containers/vaultwarden/"
# 3. Restart the container
sudo machinectl start vaultwardenRestore SSH Keys (Disaster Recovery)
If the server's SSH host keys are lost, agenix cannot decrypt any secrets and the system is unrecoverable from the flake alone.
# 1. Boot from live USB
# 2. Mount the backup drive
mount /dev/sda1 /mnt/backup
# 3. Restore SSH keys
restic -r /mnt/backup --password-file <(echo "your-password") restore latest \
--target /mnt \
--include "/persist/etc/ssh/"
# 4. Proceed with normal installationKeep an Offline Copy
The Restic password and SSH host keys form a circular dependency: the password is encrypted with the SSH key, and the SSH key is backed up with Restic. Keep an offline copy of either:
- The Restic password in plaintext (e.g., in a password manager or physical safe)
- The SSH host private key
Without one of these, a total disk failure means all secrets are lost.
Restore from a Specific Snapshot
# List available snapshots
restic -r /backup --password-file /run/agenix/restic_password snapshots
# Restore from a specific snapshot ID
restic -r /backup --password-file /run/agenix/restic_password restore abc123 \
--target /tmp/restore