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
checkcommand 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.