To schedule restic backups with systemd timers you need three files: a shell script that runs restic, a oneshot service that calls the script, and a timer that starts the service on a calendar. This guide builds all three on a Linux VPS, then shows how to confirm the schedule and read the output of each run.
Systemd timers are a good fit for backups because they log to the journal, survive reboots, can catch up on missed runs, and never start a second copy of a job that is still running.
Before you start
You need:
- A Linux VPS running Ubuntu 22.04/24.04, Debian 12, or AlmaLinux/Rocky 8–9, with root or
sudoaccess. - restic installed. On Debian and Ubuntu use
sudo apt install restic; on AlmaLinux and Rocky enable EPEL and runsudo dnf install restic. Check the restic documentation for the current version and release notes. - A backup target. This guide uses a repository on a mounted disk at
/srv/backup/restic-repo. You can pointRESTIC_REPOSITORYat an SFTP host, S3-compatible bucket or any other backend restic supports — the timer setup is identical. - A rough idea of what to back up.
/etc,/homeand your web roots are the usual starting points.
Warning: The restic repository password is the only key to your backups. If you lose it, the data is unreadable. Store a copy somewhere outside the VPS before you go further.
Install restic and create the repository:
sudo install -d -m 700 /etc/restic
sudo sh -c 'openssl rand -base64 32 > /etc/restic/password'
sudo chmod 600 /etc/restic/password
sudo mkdir -p /srv/backup/restic-repo
sudo RESTIC_PASSWORD_FILE=/etc/restic/password restic init --repo /srv/backup/restic-repoYou should see created restic repository and a repository ID. Copy that ID into your notes.
Write the restic backup script
Keep the logic in a script so the service unit stays short and you can run the same job by hand when debugging.
Create /usr/local/bin/restic-backup.sh:
#!/usr/bin/env bash
set -euo pipefail
export RESTIC_REPOSITORY="/srv/backup/restic-repo"
export RESTIC_PASSWORD_FILE="/etc/restic/password"
export RESTIC_CACHE_DIR="/var/cache/restic"
restic backup /etc /home /var/www \
--exclude=/home/*/.cache \
--exclude=/var/www/*/node_modules \
--exclude-caches \
--tag scheduled
restic forget \
--keep-daily 7 --keep-weekly 4 --keep-monthly 6 \
--tag scheduled --pruneMake it executable and test it once:
sudo chmod 750 /usr/local/bin/restic-backup.sh
sudo /usr/local/bin/restic-backup.shThe first run uploads everything and can take a while. Later runs only send changed data, so they are much faster.
Tip: --exclude-caches skips directories that contain a CACHEDIR.TAG file, which covers most build and package caches without you listing them by hand.
Create the oneshot service
The service describes what to run. It is Type=oneshot because the script does its work and exits.
Create /etc/systemd/system/restic-backup.service:
[Unit]
Description=Restic backup to /srv/backup/restic-repo
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/restic-backup.sh
Nice=10
IOSchedulingClass=idle
TimeoutStartSec=6hNice and IOSchedulingClass keep the backup from competing with your live services. TimeoutStartSec gives a large first backup room to finish.
Write the systemd timer
The timer describes when to run the service. Create /etc/systemd/system/restic-backup.timer:
[Unit]
Description=Run the restic backup daily at 02:30
[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=15m
Persistent=true
Unit=restic-backup.service
[Install]
WantedBy=timers.targetThe three settings that matter:
OnCalendaris the schedule.*-*-* 02:30:00means every day at 02:30 server time. UseMon *-*-* 03:00:00for weekly runs.RandomizedDelaySecspreads load if you run several servers against one repository.Persistent=trueruns the job at the next boot if the VPS was down when the timer was due.
Check that systemd parses your calendar expression the way you expect:
systemd-analyze calendar "*-*-* 02:30:00"Enable the timer
Reload systemd, then enable and start the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timerConfirm the
