Why off-server and encrypted
A backup on the same server as the data is lost with the server. Restic copies your files to a repository on another machine, encrypts and deduplicates them, and keeps point-in-time snapshots, so a second backup of mostly unchanged files uploads very little.
You need a second place to store data that you control, reachable over SSH (SFTP). That can be another VPS, a storage server or a home machine. Restic also supports S3-compatible storage and other back ends, which are covered in its documentation.
Install restic
Ubuntu has restic in its main archive. On AlmaLinux it comes from the EPEL repository. Distribution versions can be older than the latest release, and restic can update itself with restic self-update if you want the newest.
sudo apt update
sudo apt install resticsudo dnf install epel-release
sudo dnf install resticrestic versionPrepare the backup destination
Create a dedicated user on the storage server and give your VPS a key that can log in as that user. Generate a key on the VPS without a passphrase, because the scheduled job has to run unattended, and restrict what the key can do on the storage side. Test the login before you continue.
Prefix the public-key line with restrict to disable forwarding, PTY and agent access. This still permits the SFTP subsystem used by restic. The storage account must have no access to unrelated files; a normal writable repository also does not provide protection against a compromised client deleting backups.
sudo install -d -m 700 /root/.ssh
sudo ssh-keygen -t ed25519 -f /root/.ssh/restic_backup -N "" -C "restic backup from this VPS"
sudo cat /root/.ssh/restic_backup.pubsudo adduser --disabled-password backup
sudo -u backup mkdir -p /home/backup/.ssh /home/backup/restic-repo
sudo -u backup nano /home/backup/.ssh/authorized_keys
sudo chmod 700 /home/backup/.ssh && sudo chmod 600 /home/backup/.ssh/authorized_keysSSH_PORT=22 # Replace with the storage server’s SSH port.
sudo ssh -p "$SSH_PORT" -i /root/.ssh/restic_backup backup@STORAGE_SERVER "echo connected"Create the repository and password file
The repository password encrypts your backups. If you lose it, the backups cannot be read, so store a copy somewhere safe and separate from the server, such as a password manager. Generate a long random password, save it in a root-only file, and initialise the repository.
sudo mkdir -p /etc/restic
openssl rand -base64 36 | sudo tee /etc/restic/password >/dev/null
sudo chmod 600 /etc/restic/password
sudo cat /etc/restic/passwordsudo restic -r sftp:backup@STORAGE_SERVER:/home/backup/restic-repo -o sftp.command="ssh -p $SSH_PORT -i /root/.ssh/restic_backup backup@STORAGE_SERVER -s sftp" -p /etc/restic/password initWrite a backup script
Put the settings and the commands in one script so the timer and you run exactly the same thing. This one backs up /etc, /home and /var/www, skips caches, then keeps the last 7 daily, 4 weekly and 6 monthly snapshots and deletes the rest. Adjust the paths. Dump databases to files first (see the database guide), because copying live database files can produce an unusable backup.
sudo tee /usr/local/bin/restic-backup >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
HOST=STORAGE_SERVER
SSH_PORT=22 # Set the storage server’s SSH port.
export RESTIC_REPOSITORY="sftp:backup@${HOST}:/home/backup/restic-repo"
export RESTIC_PASSWORD_FILE=/etc/restic/password
SFTP=(-o "sftp.command=ssh -p $SSH_PORT -i /root/.ssh/restic_backup backup@${HOST} -s sftp")
restic "${SFTP[@]}" backup /etc /home /var/www --exclude-caches --exclude "*/node_modules" --exclude "*/.cache"
restic "${SFTP[@]}" forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
EOF
sudo chmod 750 /usr/local/bin/restic-backup
sudo /usr/local/bin/restic-backupSchedule it with a systemd timer
A timer runs the script daily and catches up after downtime. The service file runs it as root with low priority so a backup does not slow down your applications. Check the timer is listed and read the journal after the first run.
sudo tee /etc/systemd/system/restic-backup.service >/dev/null <<'EOF'
[Unit]
Description=Restic backup
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
Nice=10
IOSchedulingClass=idle
ExecStart=/usr/local/bin/restic-backup
EOF
sudo tee /etc/systemd/system/restic-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Daily restic backup
[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=15m
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timersystemctl list-timers restic-backup.timer
sudo systemctl start restic-backup.service
sudo journalctl -u restic-backup.service --no-pager | tail -n 20Check and practise a restore
Verify the repository regularly and restore into a scratch directory, not over live files. A backup you have never restored is unproven. Restoring the latest snapshot of one directory proves the password, the key and the data are all usable.
export HOST=STORAGE_SERVER
SSH_PORT=22 # Set the storage server’s SSH port.
export RESTIC_REPOSITORY="sftp:backup@${HOST}:/home/backup/restic-repo"
export RESTIC_PASSWORD_FILE=/etc/restic/password
SFTP=(-o "sftp.command=ssh -p $SSH_PORT -i /root/.ssh/restic_backup backup@${HOST} -s sftp")sudo -E restic "${SFTP[@]}" snapshots
sudo -E restic "${SFTP[@]}" check
sudo -E restic "${SFTP[@]}" restore latest --target /tmp/restore-test --include /etc/hostname
ls -l /tmp/restore-test/etc/no errors were found
Summary: Restored 6 files/dirs (65 B) in 0:00
RESTIC password mismatch rejected: exit 12Official documentation
These upstream references provide more detail on the commands and workflows in this guide.