Featured image of post Automated Restic Backup to S3-Compatible Storage: Complete VPS Data Vault Guide

Automated Restic Backup to S3-Compatible Storage: Complete VPS Data Vault Guide

Build an automated VPS backup system with Restic + MinIO/S3, featuring encrypted storage, deduplication, scheduled jobs, and one-click recovery.

Introduction

In self-hosting and VPS operations, backups are your last line of defense. While rclone is great for file syncing, if you need versioned backups, encrypted storage, and incremental deduplication, Restic is one of the most elegant open-source solutions available today.

This guide walks you through building a complete Restic backup system from scratch, encrypting and backing up data to S3-compatible storage (MinIO, AWS S3, Cloudflare R2 all work), with crontab for fully automated, unattended operation.


Why Restic?

FeatureResticrclone copyrsync
Incremental backup✅ Block-level dedup❌ Full copy❌ File-level
End-to-end encryption✅ Built-in AES-256❌ Needs extra config
Snapshot management✅ Automatic versioning
Native S3/R2 support
Cross-platformLinux/macOS/WindowsAll platformsLinux/macOS
Restore granularityFile/dir/volumeFile/dirFile/dir

Key advantage: Restic performs block-level deduplication at the repository level. A 1TB disk image might only upload a few MB on subsequent backups. Combined with encryption, your backups remain absolutely secure even on public cloud storage.


Architecture Overview

┌──────────────────────────────────────────────────┐
│                  VPS (Source Server)               │
│                                                  │
│  ┌────────┐   ┌──────────┐   ┌───────────────┐   │
│  │ /etc    │   │ /home    │   │  DB dumps     │   │
│  │ Config  │   │ User data │   │  (mysqldump)  │   │
│  └────┬────┘   └────┬─────┘   └───────┬───────┘   │
│       │             │                 │            │
│       └──────────┬──┴─────────────────┘            │
│                  ▼                                  │
│         ┌───────────────┐                          │
│         │   Restic CLI   │  Encrypt + Dedup + Snap  │
│         └───────┬───────┘                          │
│                 │ HTTPS                            │
└─────────────────┼──────────────────────────────────┘
                  │
          ┌───────▼────────┐
          │  S3 Storage     │
          │  (MinIO / R2)  │
          │  Encrypted repo │
          └────────────────┘

Environment Setup

Install Restic

# Ubuntu/Debian
wget https://github.com/restic/restic/releases/latest/download/restic_0.17.0_$(dpkg --print-architecture).deb
sudo dpkg -i restic_0.17.0_$(dpkg --print-architecture).deb

# Or build from source
git clone https://github.com/restic/restic.git
cd restic && go build && sudo cp restic /usr/local/bin/

# Verify installation
restic version
# restic 0.17.0 compiled at 2024-12-18 19:29:04 UTC using go1.23.4

Deploy S3-Compatible Storage (MinIO)

If your VPS has enough resources, deploy MinIO locally:

# Single-binary MinIO deployment
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
sudo mv minio /usr/local/bin/

# Create data directory
sudo mkdir -p /data/minio
sudo useradd -r -s /bin/false minio
sudo chown minio:minio /data/minio

# Create systemd service
sudo tee /etc/systemd/system/minio.service > /dev/null <<'EOF'
[Unit]
Description=MinIO Object Storage
After=network-online.target

[Service]
User=minio
Group=minio
ExecStart=/usr/local/bin/minio server /data/minio \
  --console-address ":9001" \
  --address ":9000"
Restart=always
Environment="MINIO_ROOT_USER=admin"
Environment="MINIO_ROOT_PASSWORD=your...sudo systemctl daemon-reload
sudo systemctl enable --now minio

Visit http://<your-vps-ip>:9001 to create a bucket like restic-backups.

Alternative: If you don’t want to run MinIO, use Cloudflare R2 (zero egress fees) or AWS S3 directly.

Configure S3 Environment Variables

# Add to ~/.bashrc or /etc/environment
export S3_ENDPOINT="http://127.0.0.1:9000"    # MinIO address
export S3_ACCESS_KEY="admin"
export S3_SECRET_KEY="your...port RESTIC_REPOSITORY="s3:s3.amazonaws.com/restic-backups"
export RESTIC_PASSWORD="your...n
# Apply immediately
source ~/.bashrc

For Cloudflare R2:

export S3_ENDPOINT="https://s3.us-east-1.r2.cloudflarestorage.com"
export S3_ACCESS_KEY="<r2-access-key>"
export S3_SECRET_KEY="<r2-...port RESTIC_REPOSITORY="s3:s3.us-east-1.r2.cloudflarestorage.com/restic-backups"

Initializing Repository & First Backup

Initialize Repository

restic init
# created restic repository <repo-id> at ...
# password protection enabled

Backup Critical Directories

# Backup multiple paths
restic backup /etc /home /var/www --tag vps-config --tag production

# View snapshots
restic snapshots
# ID        Date                  Host    Tags       Paths
# a1b2c3d4  2026-07-25 03:00:12   vps01   vps-config  /etc /home /var/www
# 4 objects stored, has been excluded from listing
# 5 objects stored, new size: 2.345 GiB

# Check repository stats
restic stats
# total files: 12847
# total bytes: 2.345 GiB
# unique data: 892 MiB  ← actual storage after dedup
# compressed:  1.123 GiB

Database-Specific Backups

Restic backs up at the file level; databases should be dumped first:

#!/bin/bash
# /usr/local/bin/db-backup.sh
set -euo pipefail

BACKUP_DIR="/tmp/restic-db-dump"
mkdir -p "$BACKUP_DIR"

# MySQL/MariaDB
for db in $(mysql -e 'SHOW DATABASES;' -N | grep -v '^Information\|^performance'); do
    mysqldump --single-transaction --routines --triggers "$db" \
        > "$BACKUP_DIR/${db}_$(date +%Y%m%d_%H%M%S).sql"
done

# PostgreSQL
pg_dumpall > "$BACKUP_DIR/all_pg_$(date +%Y%m%d_%H%M%S).sql"

# Backup dump files with Restic
restic backup "$BACKUP_DIR" --tag database-dumps
rm -rf "$BACKUP_DIR"
echo "$(date): Database backups completed successfully" >> /var/log/db-backup.log
sudo chmod +x /usr/local/bin/db-backup.sh

Automated Scheduled Backups

Crontab Configuration

# Create dedicated backup script
sudo tee /usr/local/bin/restic-auto-backup.sh > /dev/null <<'SCRIPT'
#!/bin/bash
set -euo pipefail

LOG="/var/log/restic-backup.log"
LOCKFILE="/tmp/restic-backup.lock"

# Prevent concurrent execution
exec 200>"$LOCKFILE"
flock -n 200 || { echo "$(date): Another backup is running, exiting." >> "$LOG"; exit 0; }

echo "=== Backup started at $(date) ===" >> "$LOG"

# Pre-step: database dump
/usr/local/bin/db-backup.sh >> "$LOG" 2>&1

# Execute Restic backup
restic backup \
    /etc \
    /home \
    /var/www \
    /tmp/restic-db-dump \
    --tag auto-backup \
    --tag "$(date +%Y-%m)" \
    >> "$LOG" 2>&1

# Clean old snapshots: keep 7 daily, 4 weekly, 12 monthly
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune >> "$LOG" 2>&1

echo "=== Backup completed at $(date) ===" >> "$LOG"
rm -rf /tmp/restic-db-dump

# Release lock
flock -u 200
SCRIPT

sudo chmod +x /usr/local/bin/restic-auto-backup.sh
# Edit crontab
crontab -e

# Daily backup at 3 AM with email notification
0 3 * * * /usr/local/bin/restic-auto-backup.sh

Monitoring & Alerts

# Check backup health
restic check --read-data-store >> /var/log/restic-health.log 2>&1

# Set up health check cron
0 6 * * * restic snapshots --last 1 | tail -1 >> /var/log/restic-status.log 2>&1

Recovery Operations

List All Snapshots

restic snapshots
restic snapshots --tag auto-backup

Restore Individual Files

# Restore to specified directory
restic restore latest --target /tmp/recovered/

# Restore specific path
restic restore a1b2c3d4 --include "/etc/nginx/nginx.conf" --target /tmp/

# Restore snapshot with specific tag
restic restore --tag production --target /tmp/prod-recovery/

Full System Recovery

# Mount as FUSE (browse in real-time)
sudo apt install restic-fuse  # available on some distros
restic mount /mnt/restic &
ls /mnt/restic/

# Boot from ISO for full disk recovery
# Mount the backup target drive, then:
restic restore latest --target /mnt/recovery/

Best Practices & Optimization

Exclude Unnecessary Content

restic backup /home --exclude '*.cache' --exclude '.local/share/Trash' --exclude 'node_modules'

Repository Health Checks

# Run weekly
restic check --quiet
# No output = healthy

# Force rebuild index
restic rebuild-index

Multi-Repository Strategy

Production → R2 (cold backup, monthly)
Development → MinIO (hot backup, daily)
Personal data → Local NAS (on-premise)

Password Management

# Never put passwords in scripts! Use a key manager
export RESTIC_PASSWORD_FILE=/run/s...d
# Or
export RESTIC_PASSWORD_KEYRING=***  # use libsecret/keyring

Cost Estimation

OptionStorage CostMonthly Estimate
MinIO (local)Hardware cost$0 (existing disk)
Cloudflare R2Free 10GB, then $0.015/GB~$1.50/mo (1TB)
AWS S3 Standard$0.023/GB~$2.50/mo (1TB)
Backblaze B2$0.005/GB~$0.70/mo (1TB)

Restic’s deduplication means: For 100GB of daily backup with only 2% daily change, you only need about 6GB of additional storage per month.


Summary

Restic + S3 provides VPS operators with:

  • Encrypted security: AES-256 end-to-end encryption; cloud providers can’t read your data
  • Extreme deduplication: Block-level dedup saves 70-90% storage space
  • Version history: Unlimited snapshots, rollback to any point in time
  • Automation-friendly: One command trigger, perfect for crontab integration

📦 Next step: Configure Restic automated backups for your VPS, then perform a recovery test—backups that haven’t been verified by restoration are no backups at all.

📺 看视频版教程 → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials