Automated VPS Backup Strategies
Learn how to set up automated backups for your VPS. Covers rsync, restic, snapshot-based backups, and offsite storage strategies to keep your data safe.
Automated VPS Backup Strategies
Backups are the thing you wish you had set up yesterday. This guide covers everything from quick scripts to bulletproof automated backup systems.
Why This Matters
Your VPS will fail. Hardware dies, data centers have outages, you'll run a bad command, or ransomware will find you. The question isn't if you'll need backups—it's when.
What can go wrong without backups:
- Accidental
rm -rf /in the wrong directory - Database corruption during an update
- Provider has an outage and loses your data
- Malware encrypts everything
- Years of work, gone in seconds
The 3-2-1 Rule:
- 3 copies of your data
- 2 different storage types
- 1 offsite location
Prerequisites
- A VPS (we recommend Hostinger VPS which includes automatic backups in most plans)
- External storage (S3, Backblaze B2, another server, etc.)
- Basic shell scripting knowledge
Step 1: Identify What to Back Up
Not everything needs backing up. Focus on:
Must backup:
- Databases (PostgreSQL, MySQL, MongoDB)
- User uploads and media
- Configuration files
- SSL certificates
- Environment files (.env)
Optional (can be recreated):
- Application code (if in git)
- Docker images (if in registry)
- System packages (reinstallable)
Don't backup:
- Logs (usually)
- Cache directories
- Temporary files
- node_modules, vendor, etc.
Step 2: Set Up Local Backups
Create a backup directory structure:
sudo mkdir -p /backups/{daily,weekly,monthly}
sudo chown $USER:$USER /backups
Basic backup script:
#!/bin/bash
# /usr/local/bin/backup-local.sh
set -e
DATE=$(date +%Y-%m-%d_%H-%M)
BACKUP_DIR="/backups/daily"
RETENTION_DAYS=7
# Create backup directory
mkdir -p "$BACKUP_DIR/$DATE"
# Backup PostgreSQL
echo "Backing up PostgreSQL..."
docker exec postgres pg_dumpall -U postgres | gzip > "$BACKUP_DIR/$DATE/postgres.sql.gz"
# Backup MySQL
echo "Backing up MySQL..."
docker exec mysql mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases | gzip > "$BACKUP_DIR/$DATE/mysql.sql.gz"
# Backup application data
echo "Backing up app data..."
tar -czf "$BACKUP_DIR/$DATE/app-data.tar.gz" /home/deploy/apps/*/data/ 2>/dev/null || true
# Backup configurations
echo "Backing up configs..."
tar -czf "$BACKUP_DIR/$DATE/configs.tar.gz" \
/etc/nginx/sites-available \
/etc/letsencrypt \
/home/deploy/apps/*/.env \
/home/deploy/apps/*/docker-compose.yml \
2>/dev/null || true
# Remove old backups
echo "Cleaning old backups..."
find "$BACKUP_DIR" -type d -mtime +$RETENTION_DAYS -exec rm -rf {} + 2>/dev/null || true
echo "Local backup completed: $BACKUP_DIR/$DATE"
Make it executable:
chmod +x /usr/local/bin/backup-local.sh
Step 3: Set Up Remote Backups with Restic
Restic is a modern backup tool with encryption and deduplication.
Install Restic:
sudo apt install restic -y
Option A: Backblaze B2 (Cheapest)
Create a B2 account and bucket, then:
# Set environment variables
export B2_ACCOUNT_ID="your-account-id"
export B2_ACCOUNT_KEY="your-account-key"
export RESTIC_REPOSITORY="b2:your-bucket-name:backups"
export RESTIC_PASSWORD="your-encryption-password"
# Initialize repository (only once)
restic init
# Run backup
restic backup /home/deploy/apps /etc/nginx /etc/letsencrypt
# Check snapshots
restic snapshots
Option B: AWS S3
export AWS_ACCESS_KEY_ID="your-key"
export AWS_SECRET_ACCESS_KEY="your-secret"
export RESTIC_REPOSITORY="s3:s3.amazonaws.com/your-bucket/backups"
export RESTIC_PASSWORD="your-encryption-password"
restic init
restic backup /home/deploy/apps
Option C: Another Server via SFTP
export RESTIC_REPOSITORY="sftp:user@backup-server:/backups"
export RESTIC_PASSWORD="your-encryption-password"
restic init
restic backup /home/deploy/apps
Step 4: Complete Backup Script
#!/bin/bash
# /usr/local/bin/backup-full.sh
set -e
# Configuration
export RESTIC_REPOSITORY="b2:mybucket:vps-backups"
export RESTIC_PASSWORD_FILE="/root/.restic-password"
export B2_ACCOUNT_ID="your-id"
export B2_ACCOUNT_KEY="your-key"
BACKUP_DIR="/tmp/backup-$(date +%Y%m%d)"
LOG_FILE="/var/log/backup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Create temp directory
mkdir -p "$BACKUP_DIR"
log "Starting backup..."
# Dump databases
log "Dumping PostgreSQL..."
docker exec -t postgres pg_dumpall -U postgres > "$BACKUP_DIR/postgres.sql" 2>/dev/null || log "PostgreSQL dump failed or not running"
log "Dumping MySQL..."
docker exec -t mysql mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" --all-databases > "$BACKUP_DIR/mysql.sql" 2>/dev/null || log "MySQL dump failed or not running"
# Backup Redis
log "Backing up Redis..."
docker exec -t redis redis-cli BGSAVE 2>/dev/null || true
sleep 2
docker cp $(docker ps -qf name=redis):/data/dump.rdb "$BACKUP_DIR/redis.rdb" 2>/dev/null || log "Redis backup failed or not running"
# Backup files with restic
log "Running restic backup..."
restic backup \
--verbose \
--exclude='*.log' \
--exclude='node_modules' \
--exclude='.git' \
--exclude='__pycache__' \
--exclude='.cache' \
"$BACKUP_DIR" \
/home/deploy/apps \
/etc/nginx \
/etc/letsencrypt
# Cleanup old snapshots (keep 7 daily, 4 weekly, 6 monthly)
log "Pruning old backups..."
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune
# Cleanup temp directory
rm -rf "$BACKUP_DIR"
# Check repository health
log "Checking repository..."
restic check
log "Backup completed successfully!"
# Optional: Send notification
# curl -X POST "https://api.pushover.net/1/messages.json" \
# -d "token=xxx&user=xxx&message=Backup completed"
Create password file:
echo "your-secure-password" | sudo tee /root/.restic-password
sudo chmod 600 /root/.restic-password
Step 5: Schedule with Cron
# Edit crontab
sudo crontab -e
Add:
# Daily backup at 3 AM
0 3 * * * /usr/local/bin/backup-full.sh >> /var/log/backup.log 2>&1
# Weekly integrity check on Sundays at 4 AM
0 4 * * 0 restic check --read-data-subset=10% >> /var/log/backup-check.log 2>&1
Step 6: Restore Procedures
Know how to restore BEFORE you need it.
Restore from Restic
# List snapshots
restic snapshots
# Restore specific snapshot
restic restore latest --target /restore-point
# Restore specific files
restic restore latest --target /restore-point --include "/home/deploy/apps/myapp"
# Mount backups (browse like filesystem)
mkdir /mnt/restic
restic mount /mnt/restic
# Browse at /mnt/restic/snapshots/
Restore PostgreSQL
# Drop and recreate database
docker exec -it postgres psql -U postgres -c "DROP DATABASE myapp;"
docker exec -it postgres psql -U postgres -c "CREATE DATABASE myapp;"
# Restore
cat backup/postgres.sql | docker exec -i postgres psql -U postgres
Restore MySQL
cat backup/mysql.sql | docker exec -i mysql mysql -u root -p"$MYSQL_ROOT_PASSWORD"
Step 7: Test Your Backups
Untested backups are not backups. Schedule monthly restore tests:
#!/bin/bash
# /usr/local/bin/test-restore.sh
TEST_DIR="/tmp/restore-test-$(date +%Y%m%d)"
mkdir -p "$TEST_DIR"
echo "Restoring latest snapshot to $TEST_DIR..."
restic restore latest --target "$TEST_DIR"
# Verify database dump is valid
echo "Verifying PostgreSQL dump..."
head -20 "$TEST_DIR/tmp/backup-*/postgres.sql"
# Check file integrity
echo "Checking restored files..."
find "$TEST_DIR" -type f | wc -l
# Cleanup
rm -rf "$TEST_DIR"
echo "Restore test completed. Review output above."
Step 8: Backup Monitoring
Get notified when backups fail:
#!/bin/bash
# Add to end of backup script
HEALTHCHECK_URL="https://hc-ping.com/your-uuid"
# On success
curl -fsS --retry 3 "$HEALTHCHECK_URL"
# On failure (add to trap)
trap 'curl -fsS --retry 3 "$HEALTHCHECK_URL/fail"' ERR
Use Healthchecks.io (free tier available) to monitor:
- Get alerted if backup doesn't run
- Track backup duration over time
- View backup history
Step 9: Database-Specific Strategies
PostgreSQL Continuous Archiving (WAL)
For zero data loss:
# postgresql.conf
archive_mode = on
archive_command = 'restic backup --tag wal %p'
MySQL Binary Logs
# my.cnf
log-bin = /var/log/mysql/mysql-bin.log
expire_logs_days = 7
MongoDB
# Use mongodump for consistent backups
mongodump --archive --gzip | restic backup --stdin --stdin-filename mongo.archive.gz
Backup Strategies Compared
| Strategy | RPO | Cost | Complexity |
|---|---|---|---|
| Daily dumps | 24h | Low | Low |
| Hourly dumps | 1h | Medium | Low |
| WAL archiving | Minutes | Medium | Medium |
| Replication | Seconds | High | High |
RPO = Recovery Point Objective (max data you can lose)
Best Practices
- Encrypt everything - Backups contain sensitive data
- Test restores monthly - A backup you can't restore is worthless
- Multiple destinations - Don't put all backups in one place
- Monitor backup jobs - Know immediately when they fail
- Document procedures - Write runbooks for restores
- Automate everything - Manual backups get forgotten
- Version your backup scripts - Keep them in git
- Separate backup credentials - Limit blast radius if compromised
Common Mistakes to Avoid
❌ Only local backups - Server dies, backups die with it
❌ Never testing restores - You'll discover corruption when you need data most
❌ No encryption - Backup storage gets hacked, all data exposed
❌ Backing up running databases - Corrupted dumps that won't restore
❌ No retention policy - Storage fills up, backups stop
❌ Same credentials everywhere - Attacker deletes backups too
❌ No monitoring - Backups silently fail for weeks
❌ Storing backup password with backups - Defeats encryption entirely
Emergency Recovery Checklist
When disaster strikes:
- Don't panic - Rushed recovery causes more damage
- Assess the damage - What exactly is lost?
- Spin up new server - Hostinger VPS can have a new server ready in minutes
- Restore latest backup - Use your documented procedure
- Restore databases - Check data integrity
- Update DNS if needed - Point to new server
- Document what happened - Post-mortem prevents repeats
FAQ
How often should I backup?
Depends on how much data you can afford to lose. Most sites: daily. E-commerce/financial: hourly. Critical systems: continuous replication.
Where should I store backups?
Different provider than your VPS. If Hostinger hosts your server, use Backblaze B2 or AWS S3 for backups. Never same provider for production and backups.
How long should I keep backups?
7 daily + 4 weekly + 12 monthly is a good starting point. Adjust based on compliance requirements and storage costs.
Are provider snapshots enough?
No. Snapshots are convenient but they're in the same data center. Use them for quick rollbacks, not disaster recovery.
How do I backup Docker volumes?
Stop the container briefly, or use volume backup tools:
docker run --rm -v myvolume:/data -v /backups:/backup alpine tar czf /backup/myvolume.tar.gz /data
Is Restic better than duplicity/borg?
They're all good. Restic has the best cloud storage support. Borg is fastest for local/SFTP. Pick one and stick with it.
Next steps: Set up monitoring to catch problems before you need those backups!
Ready to get started?
Get the best VPS hosting deal today. Hostinger offers 4GB RAM VPS starting at just $4.99/mo.
Get Hostinger VPS — $4.99/mo// up to 75% off + free domain included
// related topics
fordnox
Expert VPS reviews and hosting guides. We test every provider we recommend.
// last updated: February 6, 2026. Disclosure: This article may contain affiliate links.