Docker & DevOps

Schedule restic backups with systemd timers on a Linux VPS

Create a restic backup script, a oneshot systemd service and a timer unit so your Linux VPS backs itself up on a fixed schedule, with logs you can read in journalctl.

IntermediateLinux server3 min read

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 sudo access.
  • restic installed. On Debian and Ubuntu use sudo apt install restic; on AlmaLinux and Rocky enable EPEL and run sudo 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 point RESTIC_REPOSITORY at 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, /home and 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:

Bash
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-repo

You 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:

Bash
#!/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 --prune

Make it executable and test it once:

Bash
sudo chmod 750 /usr/local/bin/restic-backup.sh
sudo /usr/local/bin/restic-backup.sh

The 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:

INI
[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=6h

Nice 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:

INI
[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.target

The three settings that matter:

  • OnCalendar is the schedule. *-*-* 02:30:00 means every day at 02:30 server time. Use Mon *-*-* 03:00:00 for weekly runs.
  • RandomizedDelaySec spreads load if you run several servers against one repository.
  • Persistent=true runs 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:

Bash
systemd-analyze calendar "*-*-* 02:30:00"

Enable the timer

Reload systemd, then enable and start the timer:

Bash
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer

Confirm the

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.