Independent hosting operator since 2014 · New York · Dallas · Amsterdam Status Looking Glass Contact
D4 NetworksD4 Networks
Client area Deploy a server
Home / Resources / Back up your VPS off server with restic

Tutorials

Back up your VPS off server with restic

Jul 27, 2026 · 17 min read

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

Ubuntu and Debian
$ sudo apt update
$ sudo apt install -y restic
RHEL-based
$ 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:

ssh session
$ restic version

Any 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, with AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in 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:

/root/.ssh/config
Host backup-repo
    Hostname 203.0.113.20
    User backup
    ServerAliveInterval 60
    ServerAliveCountMax 240

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

ssh session
$ sudo install -d -m 700 /etc/restic
/root/.restic-password
a long random passphrase that you have also saved somewhere else
/etc/restic/env
RESTIC_REPOSITORY=sftp:backup-repo:/srv/restic-repo
RESTIC_PASSWORD_FILE=/root/.restic-password
ssh session
$ 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

ssh session
$ 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:

/etc/restic/excludes.txt
/var/lib/docker
/var/www/**/cache
node_modules
*.log
ssh session
$ restic backup /etc /home /srv /var/www --one-file-system --exclude-file /etc/restic/excludes.txt

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

ssh session
$ restic backup /etc /srv --dry-run --verbose=2

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

ssh session
$ restic backup --stdin-from-command --stdin-filename dump.sql --tag db -- mysqldump --all-databases

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

ssh session
$ restic dump latest dump.sql | mysql

If 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.

ssh session
$ 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.

/usr/local/bin/restic-backup.sh
#!/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 --prune

Since 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.

/etc/systemd/system/restic-backup.service
[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
/etc/systemd/system/restic-backup.timer
[Unit]
Description=Run restic backup nightly

[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=30m
Persistent=true

[Install]
WantedBy=timers.target
ssh session
$ 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.

ssh session
$ 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:

ssh session
$ 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:

ssh session
$ 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

TaskCommand
Create the repositoryrestic init
Back uprestic backup /etc /srv --one-file-system
Preview a backuprestic backup /etc --dry-run --verbose=2
List snapshotsrestic snapshots
Compare two snapshotsrestic diff OLD NEW
Expire old snapshotsrestic forget --keep-daily 7 --prune
Verify the structurerestic check
Verify five percent of the datarestic check --read-data-subset=5%
Restorerestic restore latest --target /tmp/restore
Browse as a filesystemrestic mount /mnt/restic
Print one filerestic 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.

restic backups sftp s3 systemd ubuntu debian almalinux

Related articles