Skip to content

Backups & Recovery

The server uses Restic for encrypted, deduplicated backups to a local SATA drive.

Source: server/modules/restic.nix

Configuration

nix
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

PathServiceContents
/persist/etc/sshSystemSSH host keys (critical for agenix secret decryption)
.../immich/var/lib/immichImmichPhoto/video library
.../immich/var/lib/postgresqlImmichPostgreSQL database (metadata, ML data)
.../opencloud/var/lib/opencloudOpenCloudCloud storage files
.../vaultwarden/var/lib/vaultwardenVaultwardenPassword 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

SettingValueRationale
repository/backupXFS-formatted SATA SSD mounted at /backup
compressionmaxMaximum zstd compression; CPU time is cheap on a homelab
--exclude-cachesFlagSkips directories with a CACHEDIR.TAG file
initializetrueAutomatically initialises the repo if it doesn't exist

Encryption

The backup repository is encrypted with a password managed by agenix:

nix
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

SettingValue
OnCalendardaily
RandomizedDelaySec3600 (1 hour)
Persistenttrue
  • Runs once per day at a random time within a 1-hour window
  • Persistent = true ensures 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
PeriodKeepMeaning
Daily7One snapshot per day for the last 7 days
Weekly2One snapshot per week for the last 2 weeks
Monthly1One snapshot per month for the last month
Yearly0No yearly snapshots

Maximum retention is approximately 1 month of history. Pruning runs automatically after each backup.

Monitoring

bash
# 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 check

Recovery Procedures

Restore a Single File

bash
# 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:

bash
# 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 vaultwarden

Restore 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.

bash
# 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 installation

Keep 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

bash
# 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