Incremental backups with restic
restic is a command-line backup tool that encrypts your data on the client side, deduplicates it in storage, and after the first full snapshot uploads only what changed. In this article you'll install restic on your mHost VPS, create a repository (on a local disk, on another server over SFTP, or in S3), take and restore a backup, and set up a retention policy so old snapshots don't pile up forever.
What you'll need
- An mHost VPS running Ubuntu 22.04/24.04, Debian 11/12, or AlmaLinux/Rocky Linux 8/9.
- SSH access as
rootor a user withsudo. - Somewhere to put the backups — one of the options from step 3: a separate mounted disk on this same server, a second server (for example, another mHost VPS) reachable over SSH, or a bucket in S3 or S3-compatible storage with access keys.
- The data you want to protect — the examples below use
/etcand/var/www; replace them with your own paths.
How it works: encryption, deduplication, incremental backups
- Encryption. restic encrypts data on the client, before it ever leaves for the repository — AES-256 in counter mode (CTR) plus Poly1305-AES as the MAC for integrity checking. Even if the repository sits on someone else's server or in a public cloud, its contents can't be read without the password.
- Deduplication. Files are split into variable-length chunks (content-defined chunking, based on Rabin fingerprints) — typically 512 KiB to 8 MiB, averaging around 1 MiB; files smaller than 512 KiB aren't split at all. Every chunk is addressed by its SHA-256 hash, and identical chunks — even from different files and different snapshots — are physically stored in the repository only once.
- Incremental backups. On every
restic backuprun, restic reads all the files again (there's no other way to tell what changed), but thanks to deduplication it only uploads the chunks the repository doesn't already have — effectively just the changes. Yet every snapshot in the list still looks like a complete, self-contained backup: to restore, you don't need to hunt down a "full" backup and manually replay a chain of incrementals on top of it, the way classic full+incremental schemes work — you just pick any snapshot and restore straight from it.
Step 1. Install restic
There's no ready-made VMmanager 6 recipe for restic (unlike, say, Zabbix or LEMP — see the full list of one-click installs in "Ready-made VMmanager 6 recipes") — restic installs with a single command from the standard repositories.
Ubuntu / Debian
apt update
apt install -y resticAlmaLinux / Rocky Linux
The restic package isn't in the base repositories — enable EPEL first:
dnf install -y epel-release
dnf install -y resticCheck the version:
restic versionStep 2. Set the repository password
The repository password is what restic derives the encryption key from. restic never stores the password anywhere in the clear, and it can't recover it for you — only you can.
Generate a random password and save it to a file that only root can read:
mkdir -p /etc/restic
openssl rand -base64 48 > /etc/restic/password.txt
chmod 600 /etc/restic/password.txtrestic can take the password three ways:
RESTIC_PASSWORD— the password itself, straight in an environment variable (visible inpsoutput and shell history — not the best choice for a server);RESTIC_PASSWORD_FILE(or the--password-fileflag) — a path to a password file, like the one you just created;RESTIC_PASSWORD_COMMAND(or the--password-commandflag) — a command that prints the password to stdout, for example a call to a password manager.
Export the variable pointing at the file — from here on restic will read the password from it instead of prompting interactively:
export RESTIC_PASSWORD_FILE=/etc/restic/password.txtStep 3. Create the repository (restic init)
The repository is where restic writes the encrypted, deduplicated data. restic also supports other backends (Backblaze B2, Azure Blob Storage, Google Cloud Storage, rclone, a REST server, and more) — here we'll cover the three options a VPS setup needs most often. Pick one.
Local disk
Good for a separate mounted disk on this same server — for quick local restores, for instance. It's not enough on its own: if the server itself is lost or fails, the repository goes with it — for real protection you also need a copy stored off this server (SFTP or S3, see the options below).
mkdir -p /mnt/backup/restic-repo
export RESTIC_REPOSITORY=/mnt/backup/restic-repo
restic initOver SFTP to another server
You'll need a second server (for example, another mHost VPS in a different location) with SSH and passwordless key-based login — without it, a scheduled automatic backup will just stall waiting for a password prompt. The resticbackup user below is an ordinary user on the destination server (root works too, but a dedicated user is safer) — create it in advance if it doesn't exist yet.
Generate a dedicated key and copy it to the destination server:
ssh-keygen -t ed25519 -f ~/.ssh/restic_backup -N ""
ssh-copy-id -i ~/.ssh/restic_backup.pub resticbackup@backup.example.comAdd settings for this host to ~/.ssh/config — restic uses plain SSH, so it picks these up automatically:
cat >> ~/.ssh/config <<'EOF'
Host backup.example.com
IdentityFile ~/.ssh/restic_backup
ServerAliveInterval 60
ServerAliveCountMax 240
EOFCreate the repository:
export RESTIC_REPOSITORY="sftp:resticbackup@backup.example.com:/srv/restic-repo"
restic initIn S3 or S3-compatible storage
Works with Amazon S3 or any S3-compatible object storage — only the URL syntax differs.
Set the access keys:
export AWS_ACCESS_KEY_ID=<MY_ACCESS_KEY>
export AWS_SECRET_ACCESS_KEY=<MY_SECRET_ACCESS_KEY>Amazon S3:
export RESTIC_REPOSITORY="s3:s3.us-east-1.amazonaws.com/mhost-backups"
restic initAnother S3-compatible storage — use the provider's endpoint instead of the AWS address:
export RESTIC_REPOSITORY="s3:https://s3.example-provider.com/mhost-backups"
restic initOnce the repository exists, save both variables to a file — you'll need it in new SSH sessions and again in step 8 for cron:
cat > /etc/restic/env <<'EOF'
export RESTIC_REPOSITORY="sftp:resticbackup@backup.example.com:/srv/restic-repo"
export RESTIC_PASSWORD_FILE=/etc/restic/password.txt
EOF
chmod 600 /etc/restic/envStep 4. Take your first backup
Exclusions are files and folders you don't need backed up (logs, temp files, and the like). Create /etc/restic/excludes.txt:
cat > /etc/restic/excludes.txt <<'EOF'
*.log
*/node_modules
*/.git
EOFRun your first backup — say, the server config and the site's files:
restic backup --tag daily --exclude-file=/etc/restic/excludes.txt /etc /var/wwwrestic walks the given paths, encrypts the data, and uploads it to the repository, then prints a summary at the end: how many files were new/changed/unmodified, how much data was added to the repository (Added to the repository: ...), and the ID of the new snapshot.
Run the same command again without changing any files:
restic backup --tag daily --exclude-file=/etc/restic/excludes.txt /etc /var/wwwThe second time around, restic still reads every file again (there's no other way to know what changed), but thanks to deduplication there's almost nothing to upload: the summary will show Added to the repository: 0 B (0 B stored). That's incremental backup in practice, not just in theory.
Step 5. List your snapshots
restic snapshotsThe command prints a table of every snapshot in the repository: a short snapshot ID, the date and time it was taken, the host, tags (if you set any with --tag), and the paths it covers. You'll need a snapshot ID (the first few characters are enough if they're unique in the repository) in the next step to restore a specific version.
Step 6. Restore your data
Restore the latest snapshot:
restic restore latest --target /tmp/restoreOr a specific one, by ID from restic snapshots:
restic snapshots
restic restore 79766175 --target /tmp/restorerestic doesn't overwrite files in place — it recreates the full original path inside --target. For example, if you backed up /etc, restoring with --target /tmp/restore puts the config files at /tmp/restore/etc/.... Compare the restored files against the current ones and copy over what you need by hand — that way a bad restore can't accidentally clobber a working configuration.
Restore just part of a snapshot — a single directory, say — with the --include flag:
restic restore latest --target /tmp/restore --include /etc/nginxStep 7. Retention policy: forget --prune
Without a retention policy, every backup adds another snapshot and the list grows forever. The forget command removes old snapshots according to rules you set, but the space taken up by data that's no longer needed is only actually freed by prune — either as a separate command, or right away if you add the --prune flag to forget.
First check that the policy will remove exactly what you expect — with --dry-run nothing is actually deleted:
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --dry-runIf the result looks right, run it for real — with --prune right away, to free the space taken by the removed snapshots:
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune--keep-daily 7— keep one snapshot for each of the last 7 days that had a backup;--keep-weekly 4— plus one snapshot for each of the 4 preceding weeks;--keep-monthly 6— plus one snapshot for each of the 6 preceding months.
A snapshot is kept if it matches at least one of the conditions (the conditions are combined with OR, not all at once). There's also a simpler option — --keep-last N, which just keeps the N most recent snapshots regardless of the calendar. By default restic groups snapshots by host and by path set, and applies the policy to each group separately — that matters if several servers share the same repository, or if it holds backups of different sets of paths.
Step 8. Automate it with cron
Wrap the backup and the cleanup of old snapshots into one script:
cat > /usr/local/bin/restic-backup.sh <<'EOF'
#!/bin/bash
set -euo pipefail
source /etc/restic/env
restic backup --tag daily --exclude-file=/etc/restic/excludes.txt /etc /var/www
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
EOF
chmod +x /usr/local/bin/restic-backup.shTest it by hand before you hand it off to cron:
/usr/local/bin/restic-backup.shAdd a cron job — for example, every night at 03:00:
crontab -e0 3 * * * /usr/local/bin/restic-backup.sh >> /var/log/restic-backup.log 2>&1What's next
For the full rundown on automating this with cron, see "Scheduled backups with cron". If a simple file mirror to another server is all you need, without encryption or deduplication, see "Backups with rsync".