Backing up a Linux server with restic and a systemd timer
Contents
This article is for an administrator who wants a Linux server to back itself up every night, encrypted, to separate storage or a second server. The result is a restic repository, a retention policy, a systemd timer that reports failures and a successful trial restore.
What you need#
- A Linux server with root access; run the commands in a root shell (
sudo -i). - A place for the copies off this server: either the backup space on separate storage included with servers of most lines (its size is on the server card, and the Lite line does not have it; the storage is mounted over NFS or CIFS, and support will send the connection details; copying is not automatic — you set it up yourself) or a second server reachable over SSH.
- If the server runs databases, a dump script: the files of a running database copied “live” may not restore. Dumps are covered in the article on database backups.
Step 1 Install restic#
The current stable version as of October 2026 is restic 0.19.1. The simplest route is the distribution package. On Debian and Ubuntu:
apt update
apt install restic
restic versionOn Fedora it is dnf install restic; on AlmaLinux and Rocky Linux enable EPEL first: dnf install epel-release.
The version in the distribution’s repository may lag behind: Debian 13 has 0.18.0, Ubuntu 24.04 has 0.16.4 and Ubuntu 26.04 has 0.18.1. The commands in this article work with these versions too. If you need the newest one, the official way is to download the binary from the project’s releases page (you need curl and bzip2):
cd /tmp
curl -LO https://github.com/restic/restic/releases/download/v0.19.1/restic_0.19.1_linux_amd64.bz2
curl -LO https://github.com/restic/restic/releases/download/v0.19.1/SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS
bunzip2 restic_0.19.1_linux_amd64.bz2
install -m 755 restic_0.19.1_linux_amd64 /usr/local/bin/restic
hash -r
restic versionsha256sum must answer restic_0.19.1_linux_amd64.bz2: OK; if it does not, do not install the file. Remove the distribution package if it was installed: apt remove restic. From then on the binary is updated with restic self-update; distribution packages usually lack this command. The official installation guide also explains how to verify the PGP signature.
Step 2 Password, storage and repository#
A restic repository is always encrypted. Keep the password in a file that only root can read:
install -d -m 700 /etc/restic
openssl rand -base64 32 > /etc/restic/password
chmod 600 /etc/restic/passwordWarning. Copy the contents of
/etc/restic/passwordto a password manager or another place off this server straight away. The password cannot be reset or recovered: without it nobody can read the backups — neither you nor support. Do not run theopensslcommand again once the repository exists: it will overwrite the password file.
restic works with several storage types; the repository address depends on the type:
| Storage type | Repository address (example) | What else is needed |
|---|---|---|
| Local or mounted directory | /mnt/backup/restic | A directory on other storage, mounted during the backup |
| SFTP | sftp:backup-host:/srv/backup/app01 | SSH login with a key, without a password prompt |
| REST server | rest:https://backup.example.com:8000/app01/ | Variables RESTIC_REST_USERNAME and RESTIC_REST_PASSWORD |
| S3-compatible storage | s3:https://s3.example.com/bucket-name | Variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY |
Option 1. The backup space#
The storage does not depend on the server: a failure of the disks or of the server itself will not affect the backups. It works over NFS (version 3 only), CIFS/SMB (version 2.0 only), FTP and FTPS. FTP is not suitable for restic, so mount the storage over NFS or CIFS and keep the repository in the mounted directory. Support will send the host name (HOST), the storage name (NAME) and the password. Access is open only from your servers’ IP addresses, which support adds to the access list at your request; there is no access from the internet or your office. One address may hold at most three connections at a time.
For NFS, install the client and mount the storage:
apt install nfs-common
mkdir -p /mnt/backup
mount -t nfs -o vers=3 HOST:/export/ftpbackup/NAME /mnt/backupTo mount the storage at boot as well, add a line to /etc/fstab:
HOST:/export/ftpbackup/NAME /mnt/backup nfs vers=3,_netdev,nofail 0 0For CIFS, first write the login and password to the file /etc/restic/cifs-credentials:
username=NAME
password=PASSWORDThen mount the storage:
apt install cifs-utils
chmod 600 /etc/restic/cifs-credentials
mkdir -p /mnt/backup
mount -t cifs -o vers=2.0,credentials=/etc/restic/cifs-credentials,uid=root,gid=root,dir_mode=0700,file_mode=0600 //HOST/NAME /mnt/backupThe line for /etc/fstab:
//HOST/NAME /mnt/backup cifs vers=2.0,credentials=/etc/restic/cifs-credentials,uid=root,gid=root,dir_mode=0700,file_mode=0600,_netdev,nofail 0 0The restic documentation advises against keeping a repository on CIFS because of failures with older Linux kernels, so choose NFS where you can.
The nofail option keeps the boot from stopping if the storage is unavailable. Check the fstab entry: findmnt should show the storage with the option vers=3 or vers=2.0.
systemctl daemon-reload
umount /mnt/backup
mount /mnt/backup
findmnt /mnt/backupOnce full, the storage switches to read-only mode until space is freed — keep an eye on usage with df -h /mnt/backup. There is no append-only mode: a process on the server that can write to the storage can also erase the backups, so in case of ransomware keep one more copy off both the server and the storage. To restore data on another server, ask support to open access to the storage from its IP address and mount it there the same way.
Option 2. A second server over SFTP#
On the second server (203.0.113.10 in the example) create a separate user without sudo rights (here backupuser) and a directory for the copies owned by that user:
useradd -m -s /bin/bash backupuser
install -d -o backupuser -m 700 /srv/backupOn the main server create a separate key without a passphrase — the timer will use it:
install -d -m 700 /root/.ssh
ssh-keygen -t ed25519 -N "" -f /root/.ssh/restic_backupDescribe the connection in /root/.ssh/config; the last two lines keep a long session from being dropped:
Host backup-host
HostName 203.0.113.10
User backupuser
IdentityFile /root/.ssh/restic_backup
ServerAliveInterval 60
ServerAliveCountMax 240Add the public key /root/.ssh/restic_backup.pub to ~/.ssh/authorized_keys of backupuser (see the article on SSH keys) and connect by hand once to confirm the server’s key fingerprint: ssh backup-host true.
Environment file and repository#
Put the repository address and the password path in the environment file /etc/restic/restic.env — both you and systemd will use it. The example is for a second server. For the backup space the first line is RESTIC_REPOSITORY=/mnt/backup/restic, and for CIFS the restic documentation recommends adding the line GODEBUG=asyncpreemptoff=1:
RESTIC_REPOSITORY=sftp:backup-host:/srv/backup/app01
RESTIC_PASSWORD_FILE=/etc/restic/password
RESTIC_CACHE_DIR=/var/cache/resticFor a REST server or S3-compatible storage the login or keys go into the same file, so its permissions are 600 too. Load the variables into the shell (needed in every new session) and create the repository:
chmod 600 /etc/restic/restic.env
set -a; . /etc/restic/restic.env; set +a
restic initStep 3 The first backup with excludes#
List what should not be backed up in /etc/restic/excludes.txt, one pattern per line:
/home/*/.cache
/root/.cache
*.tmp
*.swpRun the backup and look through the list of snapshots. Replace the directories with your own; the one with the database dumps must be among them:
restic backup /etc /home /root /srv --exclude-file=/etc/restic/excludes.txt
restic snapshotsStep 4 Retention policy: forget and prune#
forget removes the snapshots that fall outside the policy, and prune deletes from the storage the data nothing refers to any more; without prune no space is freed. First see what would be removed, then run it:
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --dry-run
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --pruneThis policy keeps one snapshot for each of the last seven days, four weeks and six months that have backups. It applies separately to each set of paths: change the list of directories in backup and the old snapshots form a group of their own. How many copies to keep and where is covered in the article on the 3-2-1 rule.
Step 5 Automatic runs: a systemd service and timer#
The script /usr/local/sbin/restic-backup.sh runs the whole cycle. Put the call to your dump script before restic backup, and for the backup space uncomment the mount check:
#!/bin/sh
set -eu
# Backup space: stop if the storage is not mounted
# mountpoint -q /mnt/backup
# Database dumps into a directory that is part of the backup
# /usr/local/sbin/db-dump.sh
restic backup /etc /home /root /srv --exclude-file=/etc/restic/excludes.txt
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check --read-data-subset=5%With set -e the script stops at the first error and returns restic’s exit code: 1 — no backup was created; 3 — a snapshot was created but some files could not be read; 11 — the repository is locked (if no other restic process is running, remove the stale lock with restic unlock); 12 — wrong password. Codes 11 and 12 appeared in restic 0.17.
The service /etc/systemd/system/restic-backup.service:
[Unit]
Description=restic backup
Wants=network-online.target
After=network-online.target
OnFailure=restic-backup-failed.service
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/restic.env
ExecStart=/usr/local/sbin/restic-backup.shThe timer /etc/systemd/system/restic-backup.timer starts it every night, server time; Persistent=true catches up on a run missed while the server was off:
[Unit]
Description=Nightly restic backup
[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=15min
Persistent=true
[Install]
WantedBy=timers.targetOnFailure starts the notification service /etc/systemd/system/restic-backup-failed.service when the script exits with an error. In the example it emails the last lines of the journal, which needs a working mail command; for a messenger or a monitoring system put in your own call:
[Unit]
Description=Notify about a failed restic backup
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'journalctl -u restic-backup.service -n 50 --no-pager | mail -s "restic backup FAILED on %H" admin@example.com'Enable the timer and run the service once by hand:
chmod 700 /usr/local/sbin/restic-backup.sh
systemctl daemon-reload
systemctl enable --now restic-backup.timer
systemctl start restic-backup.serviceHow to check the result#
Timer and journal: the list shows the time of the next run, the journal has no errors, the snapshot list has a fresh entry.
systemctl list-timers restic-backup.timer journalctl -u restic-backup.service -n 30 --no-pager restic snapshotsRepository integrity.
restic checkverifies the structure but does not read the data itself. The--read-data-subset=5%option in the script reads a random 5% of the stored data on every run; the value1/7reads the first of seven parts, so seven runs with different numbers read everything.restic check restic check --read-data-subset=1/7A trial restore into a separate directory, not over live data. The
--verifyoption compares the restored files with the repository;diffshould show at most the files that changed after the backup.restic restore latest --target /var/tmp/restore-test --include /etc --verify diff -r --no-dereference /etc /var/tmp/restore-test/etc rm -rf /var/tmp/restore-test
Restore a database dump into a test database the same way. The most honest check is to restore the data on another machine with only the repository address and the password from the password manager; repeat it every quarter.
BorgBackup as an alternative#
BorgBackup does the same job — deduplication, compression, encryption — but works only with a local directory or a remote server over SSH, and borg must also be installed on the receiving server.
The current stable branch as of October 2026 is 1.4 (release 1.4.5); the 2.0 branch is still in beta and not meant for production backups. Install the borgbackup package on both servers: apt install borgbackup or dnf install borgbackup (on AlmaLinux and Rocky Linux — from EPEL). A minimal example over the same backup-host connection:
export BORG_REPO=ssh://backup-host/srv/backup/app01-borg
borg init --encryption=repokey
borg create --stats ::'{hostname}-{now}' /etc /home /root /srv
borg prune --glob-archives '{hostname}-*' --keep-daily 7 --keep-weekly 4 --keep-monthly 6
borg compact
borg key export :: /root/borg-key.txtborg init asks for a passphrase; for timer runs it is passed through the BORG_PASSCOMMAND variable. Keep the exported key and the passphrase off the server. More in the official Borg documentation.
Common mistakes#
- The repository password is kept only on the server. A lost password means lost backups: the only key to them disappears together with the server.
- The copy sits on the same disk or server. It will not save you from a disk failure, an administrator’s mistake or a break-in. If the repository is in a mounted directory, check in the script that it is mounted (
mountpoint -q /mnt/backup), otherwise the copy lands on the system disk. - A restore has never been tested. A backup nobody has restored data from is only an assumption.
- Nobody sees the failures.
OnFailuredoes not fire when the server is off — check the date of the latest snapshot regularly.
What next#
- The second server for backups can sit in another country and be linked to the main one over a private network; the next step is a standby site.
- Servers with backup space and a private network are in the catalogue; support: +38 044 206 08 08, info@united.net.ua.