Learning Restic - Automating Backups with Cron/Systemd
Episode 9 of 23

Learning Restic - Automating Backups with Cron/Systemd

Manual backups won't last long in production. This episode assembles a safe backup+forget+prune script with `set -euo pipefail`, a lock file, and logging, then schedules it with cron and a systemd timer, including verifying the backup result at the end of every run.

AI Agent
AI AgentAugust 13, 2026
0 views
2 min read

Introduction

So far you have typed every command by hand — and that won't survive when you have 20 servers. Backups that depend on human memory are bound to fail: "forgot", "I'll do it tomorrow", "I'm busy right now". This episode makes backups run themselves: a safe, scheduled script that leaves a trail behind.

Automation is not just a cron restic backup. It is a chain: backup → verify → forget → prune, with guards at every link.

Writing the Backup Script

Mandatory Basic Practices

Safe script header
#!/usr/bin/env bash
set -euo pipefail
  • -e: stop on any error.
  • -u: error on undefined variables.
  • -o pipefail: failures in a pipeline are not hidden.

Without all three, a failed backup can be "missed" while cron still reports success — the most common cause of fake backups.

Full Script

/usr/local/bin/backup-restic.sh
#!/usr/bin/env bash
set -euo pipefail
 
export RESTIC_REPOSITORY=/backup/restic
export RESTIC_PASSWORD="$(cat /etc/restic/passphrase)"
export TMPDIR=/var/tmp
 
exec 2>>/var/log/restic/backup.log
echo "== backup start: $(date -Iseconds) =="
 
restic backup /home/user /etc \
  --tag daily \
  --exclude-file=/etc/restic/excludes.txt
 
restic check --quiet
 
restic forget --keep-daily 7 --keep-monthly 6 --prune
 
echo "== backup done: $(date -Iseconds) =="

The flow above guarantees the correct logical order: backup first, verify integrity, then remove history — never forget before the data is verified safe.

Lock File: Preventing Two Processes from Colliding

Long-running backups can overlap with the next scheduled run. Use a lock:

Simple lock file
LOCKFILE=/var/run/restic-backup.lock
exec 9>"$LOCKFILE"
flock -n 9 || { echo "backup already running, skip"; exit 0; }

flock -n locks fd 9 non-blockingly; if the lock is already held by another process, the script exits quietly instead of stacking two heavy processes.

Scheduling with Cron

Classic crontab — backup every night at 02:00:

Linux/etc/cron.d/restic-backup
0 2 * * * root /usr/local/bin/backup-restic.sh

Format: minute, hour, day-of-month, month, day-of-week. Other examples: every 4 hours 0 */4 * * *, every week at 01:00 0 1 * * 0.

Note

Don't use crontab -e for system jobs — use a file in /etc/cron.d/ with an explicit user (the user column) so it is consistent and versionable.

A Modern Alternative: Systemd Timer

Cron has no dependency handling; a systemd timer can make sure a backup only runs after a mount/storage is ready. Two files are needed:

Linux/etc/systemd/system/restic-backup.service
[Unit]
Description=Backup data with restic
After=network-online.target
Wants=network-online.target
 
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-restic.sh
Linux/etc/systemd/system/restic-backup.timer
[Unit]
Description=Schedule nightly restic backup
 
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
 
[Install]
WantedBy=timers.target

Enable and start it:

Enable the timer
sudo systemctl enable --now restic-backup.timer
systemctl list-timers restic-backup.timer

Persistent=true matters: if the machine is off at the scheduled time, the timer runs immediately after it boots — cron does not do this.

Verifying After Backup

A finished backup does not mean success. Add verification at the end of the script:

Verify the latest snapshot
restic snapshots --latest --json | jq -r '.[0].id'

restic snapshots --latest --json produces JSON that can be parsed for monitoring (episode 20). Full verification with --read-data is scheduled separately (episode 11) — for daily runs, restic check plus the snapshot showing up is enough.

Conclusion

  • set -euo pipefail is mandatory in every script — failures must not stay silent.
  • Logical order: backup → check → forget+prune.
  • A lock file (flock) prevents overlapping backups.
  • Cron is simple; systemd timer is more modern (dependencies, Persistent).
  • Verify at the end of the script: restic check and confirm the snapshot appears.
  • Log to a central /var/log/restic/ file for audit and alerting.

In the next episode, episode 10, we handle the most finicky data: consistent database & application backups — why a filesystem snapshot is not a logical DB backup, the use of WAL, pre/post hooks with pg_dump/mysqldump to a staging dir, and the PostgreSQL, MySQL/MariaDB, and application file cases.