Panel snapshots and backups live on the same platform as the server they protect. restic encrypts your data on the VPS and stores it on a machine somewhere else, so one failure cannot reach both copies.
restic is a single binary and the commands are identical everywhere. Only the install differs: Ubuntu and Debian use apt, while AlmaLinux, Rocky, and other RHEL-based distros use dnf.
1. Install restic
$ sudo apt update $ sudo apt install -y restic
$ sudo dnf install -y epel-release $ sudo dnf install -y restic
On RHEL-based systems restic comes from EPEL. The base repositories do not carry it.
Distribution packages lag behind upstream. The current upstream release is 0.19.0, from June 2026. Debian 13 ships 0.18.0, EPEL 9 ships 0.18.1, EPEL 10 ships 0.18.0, and Ubuntu 24.04 ships 0.16.4. Check what you actually got:
$ restic versionAny version from 0.14.0 onwards creates repositories in format version 2, which supports compression, so a packaged restic is usually fine. If you want the newest release, download the official binary from the restic project's GitHub releases page. Official binaries update themselves in place with restic self-update; packaged ones do not.
2. Create the repository somewhere else
The repository is where restic writes. Off-site means it lives on a different machine from the one being backed up. The common choices:
- Another server over SSH:
sftp:[email protected]:/srv/restic-repo - S3-compatible object storage:
s3:https://storage.example.com/my-bucket, withAWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYin the environment - A local path such as
/srv/restic-repo, which is fine for testing but does not survive losing the server
SFTP needs passwordless key login, because restic cannot answer a password prompt. Set the key up first; our guide on the first ten minutes on a new VPS covers ssh-copy-id.
SFTP servers also close idle connections, which happens when restic spends a long time on unchanged data. Add keepalives on the VPS side:
Host backup-repo
Hostname 203.0.113.20
User backup
ServerAliveInterval 60
ServerAliveCountMax 240The repository string then becomes sftp:backup-repo:/srv/restic-repo. For an IPv6 address or a different port, use URL syntax with a double slash before an absolute path: sftp://backup@[2001:db8::20]:2222//srv/restic-repo. The first slash ends the connection settings and the second starts the path.
Now create the config directory and the two files restic reads:
$ sudo install -d -m 700 /etc/restica long random passphrase that you have also saved somewhere else
RESTIC_REPOSITORY=sftp:backup-repo:/srv/restic-repo RESTIC_PASSWORD_FILE=/root/.restic-password
$ sudo chmod 600 /root/.restic-password /etc/restic/env $ set -a; . /etc/restic/env; set +a $ restic init
set -a exports every variable assigned until set +a. The same file format is read directly by systemd later, so you write it once.
The repository password cannot be recovered
restic encrypts the whole repository with this password, and its documentation states plainly that losing the password means the data is irrecoverably lost. Keep a copy in a password manager or on paper, somewhere other than this server. Whoever holds the storage cannot read the backups without it.
3. Run the first backup
$ restic backup /etc /home /srv /var/www --one-file-system --exclude-caches --tag daily--one-file-system keeps restic on the filesystem each path starts on, so /proc and any mounted volumes are skipped. --exclude-caches skips the contents of directories holding a CACHEDIR.TAG file. --tag labels the snapshot so you can filter and expire on it later.
Put the rest of the junk in an exclude file:
/var/lib/docker /var/www/**/cache node_modules *.log
$ restic backup /etc /home /srv /var/www --one-file-system --exclude-file /etc/restic/excludes.txtPatterns match complete path components, and a leading / anchors the pattern at the root. Use ** to match across any number of subdirectories. An exclude never removes a path you named on the command line: restic backup /var/log/nginx.log saves that file even with *.log in the exclude file. Files found inside a directory you named are still filtered.
To see what a change would do before it touches the repository:
$ restic backup /etc /srv --dry-run --verbose=2Run the backup a second time. It adds almost nothing, because restic splits files into chunks and stores each chunk once.
4. Back up databases through restic
Send the dump straight into the repository with --stdin-from-command, so restic can see the dump command's exit code:
$ restic backup --stdin-from-command --stdin-filename dump.sql --tag db -- mysqldump --all-databasesEverything after -- is your dump command, so use whatever you run today. If it exits non-zero, restic cancels the backup, fails with exit code 1, and creates no snapshot.
Do not pipe a dump into restic backup --stdin. restic's documentation warns about exactly this case: when the dump command fails to connect, the backup still succeeds and stores an empty file.
Restoring goes back the same way:
$ restic dump latest dump.sql | mysqlIf the repository holds several kinds of snapshot, latest may not be the one you want. Select by path instead: restic dump --path /dump.sql latest dump.sql.
5. Keep the repository from growing forever
forget removes snapshots. prune removes the data that only those snapshots referenced. Passing --prune to forget runs both.
$ restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --dry-run $ restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
Run the dry run every time you change the policy.
The calendar options work on natural time boundaries and only count periods that have a snapshot. --keep-daily 7 keeps one snapshot for each of the last seven days that had one, so a week with two missed days reaches further back than you might expect. --keep-within-daily 7d keeps daily snapshots from the last seven days of wall clock time.
Before applying a policy, forget groups snapshots by host and paths, which stops one policy from wiping an unrelated backup set. prune locks the repository while it works and backups cannot run during it, so give it a quiet slot.
6. Run it every night
Put the commands in a script so you can handle restic's exit codes.
#!/bin/sh
set -u
restic backup /etc /home /srv /var/www \
--one-file-system \
--exclude-caches \
--exclude-file /etc/restic/excludes.txt \
--tag daily
rc=$?
# 0 = everything saved. 3 = snapshot created, but some files could not be read.
if [ "$rc" -ne 0 ] && [ "$rc" -ne 3 ]; then
echo "restic backup failed, exit code $rc" >&2
exit "$rc"
fi
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --pruneSince 0.19.0, exit code 3 also means one of the paths you asked for does not exist. A script that only tests for zero records that as a success.
[Unit] Description=restic backup Wants=network-online.target After=network-online.target [Service] Type=oneshot EnvironmentFile=/etc/restic/env ExecStart=/usr/local/bin/restic-backup.sh
[Unit] Description=Run restic backup nightly [Timer] OnCalendar=*-*-* 02:30:00 RandomizedDelaySec=30m Persistent=true [Install] WantedBy=timers.target
$ sudo chmod +x /usr/local/bin/restic-backup.sh $ sudo systemctl daemon-reload $ sudo systemctl enable --now restic-backup.timer $ sudo systemctl start restic-backup.service $ sudo journalctl -u restic-backup.service -n 50 $ sudo systemctl list-timers restic-backup.timer
Persistent=true stores the last run time on disk and fires a missed backup once the server is up again. RandomizedDelaySec offsets the start time by a stable random amount, so several servers do not hit the same storage at 02:30 together.
7. Check the repository and restore something
Two commands tell you whether the repository is still intact.
$ restic snapshots $ restic check $ restic check --read-data-subset=5%
check verifies the structure of the repository. It does not read your backed up data unless you ask it to, and --read-data downloads every pack file in the repository. --read-data-subset=5% reads a random five percent instead, which is cheap enough to run weekly. It also accepts n/t for a fixed slice and a size such as 50M.
Then restore into an empty directory and compare:
$ restic restore latest --target /tmp/restore --include /etc/nginx $ diff -r /etc/nginx /tmp/restore/etc/nginx
To pull out one file without a full restore, browse the repository as a filesystem:
$ mkdir /mnt/restic $ restic mount /mnt/restic
Restoring over live files overwrites them
restic restore overwrites existing files at the target unless you pass --overwrite never. Adding --delete also removes files at the target that the snapshot does not contain. Run it with --dry-run --verbose=2 first.
Quick reference
| Task | Command |
|---|---|
| Create the repository | restic init |
| Back up | restic backup /etc /srv --one-file-system |
| Preview a backup | restic backup /etc --dry-run --verbose=2 |
| List snapshots | restic snapshots |
| Compare two snapshots | restic diff OLD NEW |
| Expire old snapshots | restic forget --keep-daily 7 --prune |
| Verify the structure | restic check |
| Verify five percent of the data | restic check --read-data-subset=5% |
| Restore | restic restore latest --target /tmp/restore |
| Browse as a filesystem | restic mount /mnt/restic |
| Print one file | restic dump latest dump.sql |
Where to go next
Put the repository in a different city from the server it protects. A small VPS in a second D4 Networks location is enough, because the repository only needs disk space and SSH access. For rolling back a bad change on the same server, the panel is faster: snapshots vs backups on your VPS covers when to use each.