Introduction
Backing up a VPS without a strategy is the same as not backing up at all. Real requirements:
- Client-side encryption (provider can't read your data)
- Deduplication (1 TB of data → 50 GB transferred)
- Versioning (recover a file from 3 days ago)
- Off-site (data center disaster = data safe)
- Cheap (Backblaze B2: $6/TB/month, free egress within limits)
Restic checks all boxes. Combined with Backblaze B2 (S3-compatible but 4x cheaper than AWS S3), it's the winning combo.
Prerequisites
- Linux VPS to back up
- Backblaze B2 account (https://www.backblaze.com/b2) with a created bucket
- A B2 Application Key with bucket access
Step 1: Create the B2 bucket
- On Backblaze, B2 Cloud Storage → Buckets → Create a Bucket
- Name:
verycloud-srv-web-01-backups(must be unique on B2) - Files in Bucket are: Private
- Object Lock: Disable (or enable for compliance)
Then App Keys → Add a New Application Key:
- Name:
restic-srv-web-01 - Allow access to: your specific bucket
- Type of Access: Read and Write
You get keyID and applicationKey. Save them: the key is shown only once.
Step 2: Install Restic
sudo apt update
sudo apt install -y restic
restic version
Restic from repos is often outdated. For the latest version:
sudo restic self-update
Step 3: Configure env variables
Create a credentials file:
sudo nano /root/.restic-env
# B2 Repository
export RESTIC_REPOSITORY="b2:verycloud-srv-web-01-backups:/"
# B2 Credentials
export B2_ACCOUNT_ID="YOUR_KEY_ID"
export B2_ACCOUNT_KEY="YOUR_APPLICATION_KEY"
# Repo password (encryption)
# ⚠️ Generate it and SAVE IT OUTSIDE THE VPS. Without this password, backups are unrecoverable.
export RESTIC_PASSWORD="UltraLongRandomPassword_With$pecialChars!_2024"
sudo chmod 600 /root/.restic-env
Step 4: Initialize the repository
source /root/.restic-env
sudo restic init
Typical response:
created restic repository abc123def at b2:verycloud-srv-web-01-backups:/
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is irrecoverably lost.
⚠️ Save the password in Bitwarden, KeePass or an external vault. No recovery possible without it.
Step 5: First backup
source /root/.restic-env
sudo restic backup /etc /home /var/www /opt --tag manual
Typical output:
repository abc123 opened
created new cache in /root/.cache/restic
no parent snapshot found, will read all files
Files: 12450 new, 0 changed, 0 unmodified
Dirs: 1245 new, 0 changed, 0 unmodified
Added to the repo: 1.234 GiB
processed 12450 files, 1.456 GiB in 0:42
snapshot abc12345 saved
First backup transfers everything. Following ones will be incremental and deduplicated: only new blocks uploaded.
Step 6: List snapshots
source /root/.restic-env
sudo restic snapshots
ID Time Host Tags Paths
----------------------------------------------------------------------
abc12345 2026-05-16 03:00:00 srv-web-01 manual /etc /home /var/www /opt
def67890 2026-05-17 03:00:00 srv-web-01 daily /etc /home /var/www /opt
Step 7: Automated backup script
sudo nano /usr/local/bin/restic-backup.sh
#!/bin/bash
set -e
# Load credentials
source /root/.restic-env
# Logging
LOG_FILE=/var/log/restic-backup.log
exec >> $LOG_FILE 2>&1
echo "========================================"
echo "Backup started: $(date)"
echo "========================================"
# MySQL dump if present
if systemctl is-active --quiet mariadb || systemctl is-active --quiet mysql; then
echo "MySQL dump..."
mkdir -p /var/backups/mysql
mysqldump -u root --all-databases --single-transaction \
| gzip > /var/backups/mysql/all-$(date +%F).sql.gz
# Keep only latest local dump
find /var/backups/mysql -name "*.sql.gz" -mtime +1 -delete
fi
# PostgreSQL dump if present
if systemctl is-active --quiet postgresql; then
echo "PostgreSQL dump..."
mkdir -p /var/backups/postgres
sudo -u postgres pg_dumpall \
| gzip > /var/backups/postgres/all-$(date +%F).sql.gz
find /var/backups/postgres -name "*.sql.gz" -mtime +1 -delete
fi
# Restic backup
echo "Restic backup in progress..."
restic backup \
/etc \
/home \
/root \
/var/www \
/var/backups \
/opt \
--tag daily \
--exclude '/home/*/.cache' \
--exclude '/var/www/*/cache' \
--exclude '*.log' \
--exclude 'node_modules' \
--exclude '__pycache__'
# Rotation (forget)
echo "Old snapshot rotation..."
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--keep-yearly 2 \
--prune
# Integrity check (fast)
echo "Quick check..."
restic check --read-data-subset 5%
echo "Backup completed: $(date)"
echo ""
sudo chmod +x /usr/local/bin/restic-backup.sh
Step 8: Schedule via cron
sudo crontab -e
# Daily backup at 3 AM
0 3 * * * /usr/local/bin/restic-backup.sh
# Full check once a week
0 5 * * 0 source /root/.restic-env && restic check --read-data
To receive the report by email:
0 3 * * * /usr/local/bin/restic-backup.sh && mail -s "Backup OK $(hostname)" [email protected] < /var/log/restic-backup.log
Step 9: Restore a specific file
source /root/.restic-env
# List snapshots
sudo restic snapshots
# View a snapshot's contents
sudo restic ls abc12345
# Find a file across all snapshots
sudo restic find "wp-config.php"
# Restore a file to /tmp/restore
sudo restic restore abc12345 --target /tmp/restore --include /var/www/site.fr/wp-config.php
# Fully restore a snapshot
sudo restic restore latest --target /tmp/restore-full
Step 10: Mount a snapshot as a filesystem
Very handy to browse a backup without restoring everything:
mkdir /mnt/restic-mount
sudo restic mount /mnt/restic-mount &
You can now navigate /mnt/restic-mount/snapshots/ with ls, cp, etc.
To unmount:
sudo umount /mnt/restic-mount
Step 11: Typical retention policies
For a standard web VPS
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--keep-yearly 2 \
--prune
→ Last 7 days, last 4 weeks, last 6 months, last 2 years.
For a critical database
restic forget \
--keep-hourly 24 \
--keep-daily 14 \
--keep-weekly 8 \
--keep-monthly 12 \
--keep-yearly 5 \
--prune
→ More frequent backups (hourly) with longer retention.
Step 12: Restic across multiple servers (one repo per server)
Best practice: one B2 bucket per server. Advantages:
- If one server is compromised, other backups are safe
- Faster restoration (fewer snapshots to parse)
- Trackable quotas/costs per server
To manage multiple servers, use resticprofile which simplifies YAML configs.
Step 13: Test a restore (semi-annual drill)
⚠️ A backup not tested doesn't exist. Every 6 months:
- Provision a test VPS
- Install Restic + credentials
- Restore the latest snapshot
- Check that the site/app starts
If restoration fails, the problem is identified before a real disaster.
Step 14: Monitoring with healthchecks.io
To be alerted if backup cron doesn't run:
- Free account at https://healthchecks.io
- Create a "Daily backup" check
- Modify the cron:
0 3 * * * /usr/local/bin/restic-backup.sh && curl -fsS -m 10 --retry 5 https://hc-ping.com/YOUR-UUID
If backup doesn't ping in the expected window, you get an email/SMS alert.
Troubleshooting
"repository password is wrong"
The password in /root/.restic-env doesn't match. If you lost the password → data unrecoverable.
"context deadline exceeded"
Network or B2 problem. Add retries:
restic backup ... --option b2.connections=10
Very slow backup
Reduce concurrency:
restic backup ... --option b2.connections=4
Or check bandwidth:
iftop -i eth0
"lock already held"
Another backup is running, or a previous one stopped abruptly:
sudo restic unlock
B2 space explodes
Check retention. Without forget --prune, old snapshots accumulate. Force:
sudo restic forget --keep-daily 7 --keep-weekly 4 --prune
Useful commands
# Repo stats
sudo restic stats
# Detailed stats (deduplicated size)
sudo restic stats --mode raw-data
# Diff between 2 snapshots
sudo restic diff abc12345 def67890
# Delete a specific snapshot
sudo restic forget abc12345 --prune
# Show what would be deleted without doing it
sudo restic forget --keep-daily 7 --dry-run
# Migrate a repo (change password)
sudo restic key passwd
# Backup with selective exclusion
sudo restic backup /home --exclude-file /etc/restic-excludes.txt
Conclusion
With Restic + B2:
- Cost: ~$1-5/month for 100-500 GB (vs AWS S3 $5-25)
- Security: AES-256 client-side encryption, B2 has zero access
- Restoration: granular (1 file) or complete (entire snapshot)
- Reliability: smart deduplication, integrity verification
Going further:
- Combine with rclone to back up to multiple destinations (3-2-1 backup rule)
- Use rest-server to host your own Restic repo
- Migrate to Borg or Kopia if you have specific needs (also worth it)
Resources
- Official site: https://restic.net
- Documentation: https://restic.readthedocs.io
- Backblaze B2: https://www.backblaze.com/b2/cloud-storage.html
- healthchecks.io: https://healthchecks.io
- VeryCloud guide — Docker Compose prod:
/docs/article/docker-compose-prod


















