Data and storageWritten by GLWS18 min readUbuntu 24.04 LTS / AlmaLinux 9
Commands run on your server unless a section says otherwise. Replace example usernames, addresses and paths with your own values. Keep a working session open when changing remote access.

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.

Ubuntu only
sudo apt update
sudo apt install restic
AlmaLinux only
sudo dnf install epel-release
sudo dnf install restic
Both systems
restic version

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

On the VPS (as root, because the job will run as root)
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.pub
On the storage server: add that public key to the backup user
sudo 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_keys
On the VPS: test the login and accept the host key
SSH_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"
On AlmaLinux the adduser command differs. Use sudo useradd -m backup instead.

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.

On the VPS
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/password
Initialise (replace STORAGE_SERVER)
sudo 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 init

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

Create /usr/local/bin/restic-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-backup

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

Create the service and timer
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.timer
Check
systemctl list-timers restic-backup.timer
sudo systemctl start restic-backup.service
sudo journalctl -u restic-backup.service --no-pager | tail -n 20

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

Use the same settings as the script
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")
As root, with those variables set (use sudo -E or a root shell)
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/
RECORDED LAB OUTPUT · Debian 13
no errors were found
Summary: Restored 6 files/dirs (65 B) in 0:00
RESTIC password mismatch rejected: exit 12
Recorded encrypted backup on a second VM and byte-identical restore. The final line is a deliberate wrong-password test, not a normal restore result.

Official documentation

These upstream references provide more detail on the commands and workflows in this guide.