Logo

Automatic backups with Restic + Backblaze B2

Automatic backups with Restic + Backblaze B2

Set up encrypted, deduplicated, incremental backups to Backblaze B2. Restic is the reference tool: AES-256 encryption, versioned snapshots, granular restoration, $6/TB/month on B2.

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

Step 1: Create the B2 bucket

  1. On Backblaze, B2 Cloud Storage → Buckets → Create a Bucket
  2. Name: verycloud-srv-web-01-backups (must be unique on B2)
  3. Files in Bucket are: Private
  4. 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:

  1. Provision a test VPS
  2. Install Restic + credentials
  3. Restore the latest snapshot
  4. 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:

  1. Free account at https://healthchecks.io
  2. Create a "Daily backup" check
  3. 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

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members