Automated VPS Backups with Restic: Encrypted, Deduplicated Backups to S3 and B2

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 check verifies 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.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!