
Your database dumps and app archives sit in /var/backups on the same VPS they are meant to protect. If that disk fails, the server is deleted or someone with root wipes it, the backups go too. restic copies them to another machine over SSH, encrypted and deduplicated, keeps as much history as you ask for and restores one file or everything. This guide sets it up with an SFTP repository, a stored password, an include list and a systemd timer, on Ubuntu 24.04 and Debian 12.
Quick answer: Install restic on the VPS, give root an SSH key, and on the other machine create a user whose key can only use SFTP. Create the repository with restic -r sftp:backupbox:/srv/restic/vps1 init --repository-version 2 and keep its password in a root-only file and in your password manager, because without it the backups can't be opened. Back up a list of folders (settings, home folders, apps and database dumps) with restic backup --files-from, run it nightly from a systemd timer followed by restic forget --group-by host --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune and restic check, and test restic restore every month.
Why a Copy on the Same Server Isn't Enough
A backup on the VPS's own disk shares every risk with the data: a failed disk, a cancelled or deleted server, an rm -rf in the wrong folder, an attacker who deletes both. A provider snapshot often lives on the same storage system too, as our snapshot guide explains. What survives is a copy on another machine, ideally in another building: a server at home, a second VPS elsewhere or a storage service that offers SFTP.
restic suits this job. The other machine only needs an SSH server with SFTP, because restic starts ssh HOST -s sftp itself (sftp.go). It encrypts everything with AES-256 and authenticates it with Poly1305-AES before upload, so that machine never sees your files (restic design). After the first run, it only stores the parts of files that changed.
Step 1: Install restic on the VPS
sudo apt update
sudo apt install restic
restic version
Ubuntu 24.04 ships restic 0.16.4 and Debian 12 ships 0.14.0, and every command in this guide works on both. The newest release, 0.19.1 (5 July 2026), is available as an official binary through the installation docs. A Space-Node VPS is KVM with full root access, so you can install restic and add systemd units as shown.
Step 2: Create a Key and a Backup User
Create a key for root on the VPS without a passphrase. The restic docs put it plainly: automatic backups are not possible if the server prompts for credentials.
sudo install -d -m 700 /root/.ssh
sudo ssh-keygen -t ed25519 -N '' -C restic@vps1 -f /root/.ssh/restic_ed25519
sudo cat /root/.ssh/restic_ed25519.pub
On the backup machine (Ubuntu or Debian here), add a user for this VPS. Its home folder and .ssh belong to root, so the user can't change its own keys, and it owns only the repository folder. Paste the .pub line from above in place of the example key:
sudo useradd -M -d /srv/restic -s /usr/sbin/nologin vpsbackup
sudo install -d -m 755 /srv/restic /srv/restic/.ssh
sudo install -d -m 700 -o vpsbackup /srv/restic/vps1
echo 'restrict,command="internal-sftp" ssh-ed25519 AAAA...your-key... restic@vps1' | sudo tee /srv/restic/.ssh/authorized_keys
restrict turns off port, agent and X11 forwarding and terminal allocation for this key (sshd(8)). The forced internal-sftp command makes sshd run its built-in SFTP server and answer any other request with This service allows sftp connections only. (session.c). useradd without -p locks the password, so the key is the only way to log in. sshd accepts key folders owned by root or by the user, as long as group and others can't write to them (misc.c).
Step 3: Connect Once and Create the Repository
Back on the VPS, add the backup machine to root's SSH config. Create the file as root, with your backup machine's address:
# /root/.ssh/config
Host backupbox
HostName 203.0.113.20
User vpsbackup
IdentityFile /root/.ssh/restic_ed25519
IdentitiesOnly yes
ServerAliveInterval 60
ServerAliveCountMax 240
The last two lines come from the restic docs: SFTP servers close connections that receive no data, which can happen while restic works through a lot of unchanged files. Now connect once, so root's known_hosts learns the backup machine's host key:
sudo sftp backupbox
ssh asks Are you sure you want to continue connecting (yes/no/[fingerprint])?. Compare the fingerprint with the one ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub prints on the backup machine (for an ED25519 key), type yes, then bye. Don't skip this: the timer runs without a terminal, so ssh can't ask then and stops with Host key verification failed.
Next, the repository password:
sudo install -d -m 700 /etc/restic
sudo sh -c 'head -c 32 /dev/urandom | base64 > /etc/restic/password'
sudo cat /etc/restic/password
Put that password in your password manager now. restic init warns that if you lose it, "your data is irrecoverably lost", and the copy in /etc/restic dies with the VPS. Then create /etc/restic/env:
# /etc/restic/env
RESTIC_REPOSITORY=sftp:backupbox:/srv/restic/vps1
RESTIC_PASSWORD_FILE=/etc/restic/password
RESTIC_CACHE_DIR=/var/cache/restic
restic keeps a local cache so it doesn't have to load metadata from the repository every time (manual). It looks for that cache in $XDG_CACHE_HOME or under $HOME, and systemd sets neither for a service without User= (systemd.exec), so the timer would run without one. RESTIC_CACHE_DIR fixes that. Load the file in a root shell and create the repository:
sudo -i
set -a; . /etc/restic/env; set +a
restic init --repository-version 2
set -a exports every variable the file sets, so each restic command in this shell finds the repository and password; the rest of this guide runs in it. Repository version 2 adds compression. It's the default from restic 0.15.0 on, but Debian 12's 0.14.0 still creates version 1 without it.
Step 4: Choose What to Back Up
Create the two lists:
# /etc/restic/include.txt
/etc
/home
/root
/srv
/opt
/var/www
/usr/local
/var/backups
/var/spool/cron/crontabs
# /etc/restic/exclude.txt
/root/.cache
/home/*/.cache
node_modules
That covers the server's settings, the home folders, your apps and sites (Debian's nginx serves /var/www/html by default), your own scripts in /usr/local, database dumps and crontabs. restic skips a path that doesn't exist with a warning such as pattern "/srv" does not match any files, skipping, and ignores lines that start with #. In exclude patterns, * doesn't match across a /, and a name without a slash, like node_modules, matches that folder anywhere (backup docs).
Leave out the data folders of running databases, such as /var/lib/postgresql and /var/lib/mysql, and Docker's /var/lib/docker, which holds its volumes. PostgreSQL's manual says the server "must be shut down in order to get a usable backup" of its files (File System Level Backup). Back up dumps instead: our PostgreSQL, MariaDB and MySQL and Docker volumes guides write them to /var/backups every night.
Now run the first backup and list the result:
restic backup --files-from /etc/restic/include.txt --exclude-file /etc/restic/exclude.txt --exclude-caches
restic snapshots
--exclude-caches also skips every folder that contains a CACHEDIR.TAG file.
Step 5: Decide How Long to Keep Snapshots
restic forget removes snapshots by a policy, and --prune then deletes the data that only those snapshots used (forget docs). Preview the policy with --dry-run:
restic forget --group-by host --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --dry-run
That keeps the newest snapshot of each of the last 7 days, 4 weeks and 12 months that have snapshots. By default, forget groups snapshots by host and paths. After you edit include.txt, the older snapshots would form a group of their own, and the policy would keep that group's newest daily, weekly and monthly snapshots indefinitely. --group-by host keeps one history per server. Prune locks the repository while it runs, and the docs advise running restic check afterwards, so the timer does both after the backup.
Step 6: Run It Every Night With a systemd Timer
# /etc/systemd/system/restic-backup.service
[Unit]
Description=restic backup to the backup machine
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
Nice=19
ExecStart=restic backup --files-from /etc/restic/include.txt --exclude-file /etc/restic/exclude.txt --exclude-caches
ExecStart=restic forget --group-by host --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
ExecStart=restic check
# /etc/systemd/system/restic-backup.timer
[Unit]
Description=Nightly restic backup
[Timer]
OnCalendar=*-*-* 04:30:00
Persistent=true
[Install]
WantedBy=timers.target
Still in the root shell, test the service once, read its log, then enable the timer:
systemctl daemon-reload
systemctl start restic-backup.service
journalctl -u restic-backup.service -n 30 --no-pager
systemctl enable --now restic-backup.timer
systemctl list-timers restic-backup.timer
With Type=oneshot, systemd runs the ExecStart= lines in order and stops at the first one that fails, so a failed backup never leads to a prune. A oneshot service also has no start timeout, so a big upload isn't cut off (systemd.service). systemd finds a bare restic in /usr/local/bin or /usr/bin, so an official binary works too. Nice=19 gives the backup the lowest CPU priority. Persistent=true catches up on a run missed while the server was off (systemd.timer), and network-online.target makes that catch-up wait for the network (systemd.special). Schedule it after your dump jobs: the ones in our PostgreSQL, MariaDB and Docker guides run at 03:30.
Step 7: Check and Restore
Once a month, read part of the stored data back and restore a folder next to the original:
restic check --read-data-subset=10%
restic ls latest /etc/apt
restic restore latest --target /tmp/restore-test --include /etc/apt
diff -r /etc/apt /tmp/restore-test/etc/apt && echo SAME
rm -rf /tmp/restore-test
The nightly restic check verifies the repository's structure, and --read-data-subset=10% also downloads and verifies a random tenth of the stored data (check docs). Snapshots keep full paths, so --include /etc/apt lands in /tmp/restore-test/etc/apt (restore docs), and diff prints nothing when the copies match. For a single file, restic dump latest /etc/hostname prints it.
After losing the VPS. On the new server, install restic, create a new key as in Step 2 and swap it into the backup machine's authorized_keys, then recreate /root/.ssh/config and /etc/restic with the password from your password manager. Run sudo sftp backupbox once, load the env file in a root shell as in Step 3, and restore into a folder:
restic snapshots
restic restore latest --target /srv/restore
Copy back what you need from /srv/restore rather than restoring over /, so the new server keeps its own /etc/fstab and network settings, then load the dumps as the PostgreSQL, MariaDB and Docker guides show. If several servers share the repository, add --host with the old server's name.
restic, borg or rsync
BorgBackup also deduplicates, compresses and encrypts on the client, but it works best with borg installed on the backup machine too, because the VPS starts borg serve there. In return you can lock the key to one path with command="borg serve --restrict-to-path /path/to/repo",restrict. Borg 1.4.5 (18 July 2026) is the stable release, and 2.0 is still in beta. Plain rsync copies files as they are, so they are readable on the other machine, and it keeps no history unless you build snapshots with --link-dest. It is the simplest way to pull dumps from another machine, as our PostgreSQL guide does, so the VPS holds no login to the copies.
If It Still Fails
subprocess ssh: Host key verification failed.Root has never connected to that host, or the host key changed. Runsudo sftp backupboxand check the fingerprint as in Step 3.Permission deniedfrom ssh. The backup machine rejected the key. Check thatauthorized_keysholds the whole.publine and thatIdentityFilepoints at the private key;sudo ssh -v backupboxshows which key ssh offers. On the backup machine,sudo journalctl -u sshshows lines such asAuthentication refused: bad ownership or modes for directorywhen a folder is writable by others.Fatal: wrong password or no key found. The password file doesn't match the repository. Compare it with the copy in your password manager.Is there a repository at the following location?The path inRESTIC_REPOSITORYdiffers from the folder you created, orrestic initnever ran.repository is already locked. A run that was stopped, for example by a reboot, left its lock behind: systemd stops services with SIGTERM (systemd.kill), and restic before 0.17.0 then exits without cleaning up (changelog). Check thatsystemctl status restic-backup.serviceshows no run in progress, then runrestic unlock. It removes only stale locks: older than 30 minutes, or left by a process that no longer runs on this host.- The service fails with exit status 3. The snapshot was saved, but some files could not be read, and the journal lists them. Fix their permissions or exclude them. Until then, the forget and check lines don't run.
FAQ
Can someone who gets root on my VPS delete these backups?
Yes. The VPS holds a key with write access to the repository, and restic's threat model says it "is not designed to protect against attackers deleting files at the storage location". To close that gap, run restic's rest-server with --append-only on the backup machine, which "allows creation of new backups but prevents deletion and modification of existing backups". Then move forget and prune off the VPS to a separate, trusted machine and use --keep-within, as the forget docs advise for append-only repositories.
Can the backup machine read my files? No. Everything except some informational metadata in the key files is encrypted and authenticated before it leaves the VPS, and only the repository password opens it. That's why the password belongs in your password manager, not only on the server.
Can I back up a running database with restic? Not its data folder: back up a dump, as in Step 4. Run the restic timer after the dump jobs, so each snapshot holds that night's dump.
How much space does the repository need?
The first snapshot stores your data once, compressed in a version 2 repository. Every later snapshot only adds new and changed chunks. restic stats --mode raw-data shows how much the repository holds, so check it after a few weeks and leave room for a year of monthly snapshots.
Are backups included with a Space-Node VPS? Not in the plan price; you can arrange them through a support ticket. A restic repository you run yourself stays useful either way: you choose where it lives, and only your password opens it.
Sources
All opened on 30 October 2026:
- restic docs: Installation, Preparing a new repository (and the 0.14.0 version), Backing up, Working with repositories, Restoring from backup, Removing backup snapshots, Encryption, Manual, References: design and threat model
- restic on GitHub: releases, CHANGELOG, man pages for 0.14.0 (backup, forget, check, restore, ls, stats, unlock, init), source for 0.16.4: sftp.go, lock.go, key.go, global.go, cmd_backup.go, cache/dir.go, rest-server
- Packages: restic in Debian 12, restic in Ubuntu 24.04
- OpenSSH (Debian 12 man pages): sshd(8), sshd_config(5), ssh_config(5), ssh(1), ssh-keygen(1), sftp(1)
- OpenSSH source (9.6p1): session.c, misc.c, platform-misc.c, auth2-pubkeyfile.c, sshconnect.c, sshconnect2.c, readpass.c, Debian 12 sshd_config, Ubuntu 24.04 sshd defaults
- systemd (Debian 12 man pages): systemd.service(5), systemd.exec(5), systemd.timer(5), systemd.special(7), systemd.time(7), systemd.kill(5), systemctl(1), journalctl(1), and systemd.exec(5) in Ubuntu 24.04
- Other man pages (Debian 12): useradd(8), nologin(8), install(1), head(1), base64(1), bash(1), crontab(1), diff(1), rsync(1), pg_createcluster(1)
- BorgBackup 1.4.5 quickstart, borg serve, Borg releases
- PostgreSQL 18: File System Level Backup, Docker daemon data directory, MariaDB server config in Debian 12, nginx default site in Debian 12, Filesystem Hierarchy Standard 3.0, Cache Directory Tagging Specification