VPS Backup Strategies That Actually Work: Automating Off-Site Backups with Restic and S3

VPS Backup Strategies That Actually Work: Automating Off-Site Backups with Restic and S3

The backup that does not exist is not a backup — and neither is the backup you have never tested restoring. VPS backups fail in practice for three predictable reasons: they are not automated, they are stored on the same server as the data they protect, or they are never tested. This guide implements a robust off-site backup strategy using Restic — a fast, encrypted, deduplicated backup tool — with S3-compatible object storage as the backend. The result is automated, encrypted, off-site backups that run without manual intervention and can be verified and restored in minutes.

Why Most VPS Backup Setups Fail

Storing Backups on the Same Server

If your backup is on /home/backups/ on the same VPS that holds your production data, a disk failure, server compromise, or provider-side incident destroys both the data and the backup simultaneously. Off-site backup is not optional for any production system.

Manual Backup Processes

Any backup process that requires human action will eventually fail — either because someone forgets, is on vacation during an incident, or the process is skipped during a busy period. Automated, scheduled backups are the only reliable approach.

Backups Never Tested

A backup you have never restored is a backup of unknown quality. Backup files can be corrupted, incomplete, or in formats that require software that is no longer available. Monthly restoration tests are the only way to verify backup integrity.

Why Restic

Restic is a modern backup tool that addresses the most common failures of traditional backup approaches:

  • Client-side encryption: All data is encrypted before leaving your server — the backup storage provider cannot read your data
  • Deduplication: Restic identifies duplicate data blocks across backups, dramatically reducing storage consumption for incremental backups
  • Integrity verification: Built-in check command verifies backup integrity without requiring a full restore
  • Multiple backends: Supports S3, Backblaze B2, Azure Blob, Google Cloud Storage, SFTP, and local storage
  • Fast incremental backups: Only changed data is transferred after the initial backup
  • Snapshot-based: Each backup is a complete snapshot with deduplication behind the scenes — no complex differential/incremental restore chains

Step 1: Choose an S3-Compatible Storage Backend

Several providers offer S3-compatible object storage at low cost. For backup storage, choose based on price per GB and geographic redundancy:

  • Backblaze B2: $0.006/GB/month — one of the cheapest options with excellent reliability
  • Wasabi: $0.0068/GB/month, no egress fees — good for frequent restores
  • Cloudflare R2: $0.015/GB/month, zero egress fees — excellent for data you may retrieve frequently
  • AWS S3: $0.023/GB/month — more expensive but ubiquitous, with Glacier for even cheaper archival storage
  • DigitalOcean Spaces: $25/month for 250 GB — predictable pricing for larger backup volumes

For most VPS backup use cases, Backblaze B2 or Wasabi offer the best cost-to-reliability ratio. Create a bucket and generate an access key/secret for your backup user.

Step 2: Install Restic

sudo apt install restic -y
restic version

Alternatively, download the latest binary directly for the most recent version:

curl -L https://github.com/restic/restic/releases/latest/download/restic_linux_amd64.bz2 | \
  bunzip2 > /usr/local/bin/restic
chmod +x /usr/local/bin/restic

Step 3: Configure Restic Credentials

Store your backup credentials in a secure file:

sudo mkdir -p /etc/restic
sudo nano /etc/restic/b2-credentials.env
# For Backblaze B2
export RESTIC_REPOSITORY="b2:your-bucket-name:restic"
export RESTIC_PASSWORD="your_strong_encryption_passphrase_here"
export B2_ACCOUNT_ID="your_b2_account_id"
export B2_ACCOUNT_KEY="your_b2_application_key"

For AWS S3 or S3-compatible providers:

# For S3-compatible (Wasabi, DigitalOcean Spaces, etc.)
export RESTIC_REPOSITORY="s3:s3.wasabisys.com/your-bucket/restic"
export RESTIC_PASSWORD="your_strong_encryption_passphrase_here"
export AWS_ACCESS_KEY_ID="your_access_key"
export AWS_SECRET_ACCESS_KEY="your_secret_key"
sudo chmod 600 /etc/restic/b2-credentials.env

Initialize the Restic repository (do this once):

source /etc/restic/b2-credentials.env
restic init

Step 4: Create the Backup Script

sudo nano /usr/local/bin/restic-backup.sh
#!/bin/bash
set -euo pipefail

# Load credentials
source /etc/restic/b2-credentials.env

LOG_FILE="/var/log/restic-backup.log"
DATE=$(date +"%Y-%m-%d %H:%M:%S")

log() {
    echo "[$DATE] $1" | tee -a $LOG_FILE
}

log "Starting backup..."

# Backup critical directories
restic backup \
  /var/www \
  /etc \
  /home \
  --exclude /var/www/*/wp-content/cache \
  --exclude /var/www/*/wp-content/uploads/backups \
  --exclude "*.log" \
  --exclude "*.tmp" \
  --tag "files,$(hostname)" 2>&1 | tee -a $LOG_FILE

log "Files backup complete. Starting database backup..."

# Backup all MySQL/MariaDB databases
DB_BACKUP_DIR=/tmp/restic-db-backup
mkdir -p $DB_BACKUP_DIR

mysqldump --all-databases --single-transaction --quick \
  | gzip > $DB_BACKUP_DIR/all-databases-$(date +%Y%m%d).sql.gz

restic backup $DB_BACKUP_DIR \
  --tag "databases,$(hostname)" 2>&1 | tee -a $LOG_FILE

rm -rf $DB_BACKUP_DIR

log "Database backup complete. Running retention policy..."

# Apply retention policy: keep recent backups, prune old ones
restic forget \
  --keep-hourly 24 \
  --keep-daily 14 \
  --keep-weekly 8 \
  --keep-monthly 12 \
  --prune 2>&1 | tee -a $LOG_FILE

log "Backup process complete."

# Check repository integrity monthly (run when day is 1)
if [ "$(date +%d)" = "01" ]; then
    log "Running monthly integrity check..."
    restic check 2>&1 | tee -a $LOG_FILE
fi
sudo chmod +x /usr/local/bin/restic-backup.sh

Step 5: Schedule Automated Backups with Systemd

Using systemd timers is more reliable than cron for backup scheduling — failed timers are visible and logged:

sudo nano /etc/systemd/system/restic-backup.service
[Unit]
Description=Restic off-site backup
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/restic-backup.sh
StandardOutput=journal
StandardError=journal
sudo nano /etc/systemd/system/restic-backup.timer
[Unit]
Description=Run Restic backup every 6 hours
Requires=restic-backup.service

[Timer]
OnCalendar=*-*-* 00,06,12,18:00:00
RandomizedDelaySec=15min
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable restic-backup.timer
sudo systemctl start restic-backup.timer

# Verify timer is active
systemctl list-timers | grep restic

Step 6: Test Your Backups

List Snapshots

source /etc/restic/b2-credentials.env
restic snapshots

Check Repository Integrity

restic check

Test File Restoration

# Restore a specific file from the most recent snapshot
restic restore latest \
  --target /tmp/restore-test \
  --include /etc/nginx/nginx.conf

# Verify the restored file
ls -la /tmp/restore-test/etc/nginx/nginx.conf
cat /tmp/restore-test/etc/nginx/nginx.conf

Test Database Restoration

# List available database backup snapshots
restic snapshots --tag databases

# Restore database backup to temp directory
restic restore latest \
  --target /tmp/db-restore \
  --tag databases

# Test restoring a database (to a test database, not production)
gunzip -c /tmp/db-restore/tmp/restic-db-backup/*.sql.gz | \
  mysql -u root -p test_restore_db

Step 7: Monitor Backup Success

Set up an alert if backups fail. Using healthchecks.io (free tier available) to track backup execution:

# Add to the backup script after successful completion
curl -fsS --retry 3 https://hc-ping.com/your-check-uuid > /dev/null

Configure healthchecks.io to send an email or Slack alert if the ping is not received within the expected window (e.g., 7 hours for 6-hourly backups). This immediately alerts you to failed backup jobs without requiring you to manually check logs.

Retention Policy Explained

The retention policy in the backup script keeps:

  • Last 24 hourly snapshots (covering the past day with hourly resolution)
  • Last 14 daily snapshots (two weeks of daily backups)
  • Last 8 weekly snapshots (two months of weekly backups)
  • Last 12 monthly snapshots (one year of monthly backups)

Adjust based on your storage budget and recovery requirements. Restic’s deduplication means that storing many snapshots costs less than the raw data size multiplied by snapshot count — only unique data blocks are stored.

Getting Started

This backup strategy works on any USA VPS or Hong Kong VPS with internet access. The off-site storage is completely independent of your VPS provider — even if your VPS provider has an outage, your backup data in B2 or Wasabi remains accessible for restoration to a new server.

Conclusion

Restic with S3-compatible off-site storage provides the foundation of a genuinely reliable backup system: automated scheduling, client-side encryption, deduplication for storage efficiency, and built-in integrity verification. The most important remaining step is regular restoration testing — schedule a monthly restoration drill into your calendar and treat any failure as a production incident requiring immediate resolution.

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!