Automated VPS Backups with Restic: Encrypted, Deduplicated Backups to S3 and B2
Restic is a modern backup tool designed for correctness and efficiency — every backup is encrypted with AES-256 before leaving your server, deduplicated so only changed data is uploaded, and verifiable so you know your backups are restorable before you need them. This guide automates VPS backups to S3-compatible object storage with retention policies, database dumps, and integrity verification.
Why Restic Over rsync or tar
- Encryption: AES-256 client-side encryption — even if your S3 bucket is compromised, files are unreadable without your password
- Deduplication: Restic splits files into variable-length chunks and only uploads new chunks — a 20 GB backup after the first takes only the MB of changed data
- Integrity verification:
restic checkverifies every backup is intact and restorable - Snapshots: Each backup is a snapshot — restore any file from any point in time
- Retention policies: Keep daily/weekly/monthly snapshots automatically
Step 1: Install Restic
<code">sudo apt install -y restic restic version # Or install latest binary directly: # wget https://github.com/restic/restic/releases/download/v0.17.3/restic_0.17.3_linux_amd64.bz2 # bunzip2 restic_0.17.3_linux_amd64.bz2 && chmod +x restic_0.17.3_linux_amd64 # sudo mv restic_0.17.3_linux_amd64 /usr/local/bin/restic
Step 2: Configure Object Storage
<code">sudo nano /etc/restic/env.sh
For Backblaze B2 ($6/TB/month — most affordable):
<code">#!/bin/bash # Backblaze B2 configuration export RESTIC_REPOSITORY="b2:your-bucket-name:/backups/vps-hostname" export RESTIC_PASSWORD="YourStrongEncryptionPassphrase!" export B2_ACCOUNT_ID="your_b2_account_id" export B2_ACCOUNT_KEY="your_b2_application_key"
For AWS S3 or S3-compatible (Cloudflare R2, etc.):
<code">#!/bin/bash # AWS S3 configuration export RESTIC_REPOSITORY="s3:s3.amazonaws.com/your-bucket-name/backups" export RESTIC_PASSWORD="YourStrongEncryptionPassphrase!" export AWS_ACCESS_KEY_ID="your_access_key" export AWS_SECRET_ACCESS_KEY="your_secret_key" export AWS_DEFAULT_REGION="us-east-1" # For Cloudflare R2: # export RESTIC_REPOSITORY="s3:https://ACCOUNT_ID.r2.cloudflarestorage.com/bucket-name" # export AWS_ACCESS_KEY_ID="r2_access_key" # export AWS_SECRET_ACCESS_KEY="r2_secret_key"
<code">sudo chmod 600 /etc/restic/env.sh
Step 3: Initialize the Repository
<code">source /etc/restic/env.sh restic init # Output: created restic repository abc123 at b2:your-bucket-name:/backups/vps-hostname # IMPORTANT: Save the repository ID and your password — without them, you cannot decrypt backups
Step 4: Create Backup Script
<code">sudo nano /usr/local/bin/restic-backup.sh
<code">#!/bin/bash
set -euo pipefail
source /etc/restic/env.sh
LOG="/var/log/restic-backup.log"
HOSTNAME=$(hostname)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
log() { echo "[$TIMESTAMP] $*" | tee -a "$LOG"; }
log "=== Backup started ==="
# Step 1: Dump databases BEFORE backup
log "Dumping PostgreSQL databases..."
sudo -u postgres pg_dumpall | gzip > /tmp/postgres_all.sql.gz
log "Dumping MariaDB databases..."
sudo mysqldump --all-databases --single-transaction | \
gzip > /tmp/mysql_all.sql.gz
# Step 2: Run Restic backup
log "Running Restic backup..."
restic backup \
--verbose \
--tag "$HOSTNAME" \
--tag "$(date +%Y-%m-%d)" \
--exclude="/proc/*" \
--exclude="/sys/*" \
--exclude="/dev/*" \
--exclude="/run/*" \
--exclude="/tmp/*" \
--exclude="/var/cache/*" \
--exclude="/var/log/*.gz" \
--exclude="/home/*/.cache" \
--exclude="/root/.cache" \
/etc \
/var/www \
/opt \
/home \
/tmp/postgres_all.sql.gz \
/tmp/mysql_all.sql.gz \
2>&1 | tee -a "$LOG"
# Step 3: Apply retention policy
log "Applying retention policy..."
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 3 \
--prune \
2>&1 | tee -a "$LOG"
# Step 4: Clean up temp files
rm -f /tmp/postgres_all.sql.gz /tmp/mysql_all.sql.gz
log "=== Backup completed ==="
# Step 5: Ping Healthchecks.io (set HEALTHCHECK_URL in env.sh)
if [ -n "${HEALTHCHECK_URL:-}" ]; then
curl -fsS "$HEALTHCHECK_URL" > /dev/null
fi
<code">sudo chmod +x /usr/local/bin/restic-backup.sh # Test the backup manually first sudo /usr/local/bin/restic-backup.sh
Step 5: Verify the Backup
<code">source /etc/restic/env.sh # List all snapshots restic snapshots # Verify backup integrity (reads data and checks cryptographic hashes) restic check # "no errors were found" = your backup is verifiable and restorable # Check specific snapshot contents restic ls latest /var/www --long | head -30 # Verify a full snapshot (slower but thorough) restic check --read-data
Step 6: Schedule with Cron and Monitor
<code">sudo crontab -e
<code"># Daily backup at 2:30 AM 30 2 * * * /usr/local/bin/restic-backup.sh >> /var/log/restic-backup.log 2>&1 # Weekly verification (Sunday at 4 AM) 0 4 * * 0 source /etc/restic/env.sh && restic check >> /var/log/restic-check.log 2>&1 # Add to env.sh for monitoring: # HEALTHCHECK_URL=https://hc-ping.com/YOUR-UUID
Step 7: Restore from Backup
<code">source /etc/restic/env.sh
# List snapshots to find the one to restore
restic snapshots
# Restore a specific file to a destination directory
restic restore latest \
--target /tmp/restored \
--include /var/www/mysite/wp-config.php
# Restore an entire directory
restic restore latest \
--target /tmp/restored \
--include /var/www/mysite
# Mount backup as a filesystem (browse and copy files manually)
sudo apt install -y fuse
restic mount /mnt/restic-mount &
ls /mnt/restic-mount/snapshots/latest/var/www/
# Browse and copy individual files
sudo umount /mnt/restic-mount
Retention Policy Explained
<code"># What --keep-daily 7 --keep-weekly 4 --keep-monthly 3 means: # Keep the most recent snapshot for each of the last 7 days # Keep the most recent snapshot for each of the last 4 weeks # Keep the most recent snapshot for each of the last 3 months # This creates a backup history like: # Day 1-7: daily snapshots # Week 1-4: weekly snapshots (older than 7 days) # Month 1-3: monthly snapshots (oldest retained) # Total: ~14 snapshots covering 3 months of history
Getting Started
Restic backups work on any Ubuntu VPS at VPS.DO. Backblaze B2 is the most cost-effective destination at $6/TB/month, with a $0.01/GB download fee (rarely needed — backups are write-heavy). A typical 5 GB VPS with WordPress and a database first backs up in 2–5 minutes; subsequent daily backups take 30–60 seconds due to deduplication. The 3-month retention history in the script provides excellent recovery flexibility.
Conclusion
Restic + Backblaze B2 (or Cloudflare R2) is the most cost-effective, reliable VPS backup solution available: client-side encryption means your backup provider can’t read your data, deduplication keeps storage costs low, and restic check verifies backups are restorable before you need them. Schedule daily backups with weekly integrity checks and monitor with Healthchecks.io — your backup system is only as good as your ability to detect when it stops running.