Backup Strategies
A reliable backup strategy isn't just "run rsync every night." It's a layered approach that balances recovery speed, storage costs, and data freshness — ensuring you can restore any server to any point in time.
Backup Types
Full Backup
Copies everything every time:
rsync -av /var/www/html/ /backup/full-$(date +%F)/
Pros: Self-contained, simplest restore Cons: Slow, uses the most storage
Incremental Backup (--link-dest)
Copies only changed files since the last backup. Unchanged files are hard-linked:
rsync -av --link-dest=/backup/latest \
/var/www/html/ /backup/$(date +%F)/
ln -sfn /backup/$(date +%F) /backup/latest
Pros: Fast, space-efficient, each snapshot looks like a full backup Cons: Relies on hard links (same filesystem required for space savings)
Differential Backup (--compare-dest)
Copies only files that differ from a reference point (usually the last full backup):
rsync -av --compare-dest=/backup/full-2024-01-01/ \
/var/www/html/ /backup/diff-$(date +%F)/
Pros: Smaller than full, faster restore than incremental chain Cons: Grows larger over time until next full backup
Comparison
| Strategy | Speed | Storage | Restore Speed | Complexity |
|---|---|---|---|---|
| Full | Slowest | Highest | Fastest | Lowest |
| Incremental | Fastest | Lowest | Fast (each snapshot is complete) | Medium |
| Differential | Medium | Medium | Medium (full + diff) | Medium |
The 3-2-1 Rule
The gold standard for backup architecture:
flowchart LR
SRC["Production Server"] --> L["Local Backup<br/>(Copy 1)"]
SRC --> R["Remote Server<br/>(Copy 2)"]
SRC --> C["Cloud/Offsite<br/>(Copy 3)"]
L -.- M1["Media 1: Local Disk"]
R -.- M2["Media 2: Remote Server"]
C -.- M3["Media 3: Cloud Storage"]
- 3 copies of your data
- 2 different storage media
- 1 offsite copy
# Copy 1: Local incremental
rsync -av --link-dest=/backup/latest \
/var/www/html/ /backup/$(date +%F)/
ln -sfn /backup/$(date +%F) /backup/latest
# Copy 2: Remote server
rsync -avz /backup/$(date +%F)/ user@remote:/backups/$(date +%F)/
# Copy 3: Cloud (using rclone)
rclone sync /backup/$(date +%F)/ s3:my-bucket/backups/$(date +%F)/
Retention Policies
GFS (Grandfather-Father-Son) Rotation
Keep different numbers of daily, weekly, and monthly backups:
| Level | Keep | Frequency |
|---|---|---|
| Daily (Son) | 7 days | Every night |
| Weekly (Father) | 4 weeks | Every Sunday |
| Monthly (Grandfather) | 12 months | First of month |
Retention Script
#!/bin/bash
# retention-cleanup.sh — Apply GFS retention policy
BACKUP_DIR="/backup"
# Keep daily backups for 7 days
find "$BACKUP_DIR/daily" -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;
# Keep weekly backups for 4 weeks
find "$BACKUP_DIR/weekly" -maxdepth 1 -type d -mtime +28 -exec rm -rf {} \;
# Keep monthly backups for 12 months
find "$BACKUP_DIR/monthly" -maxdepth 1 -type d -mtime +365 -exec rm -rf {} \;
Simple Rolling Cleanup
# Keep last 14 daily backups
find /backup/ -maxdepth 1 -type d -name "20*" -mtime +14 -exec rm -rf {} \;
Complete Backup Script
#!/bin/bash
# full-backup-strategy.sh — Files + Database + Retention
set -euo pipefail
SOURCE="/var/www/html/"
BACKUP_BASE="/backup"
TIMESTAMP=$(date +%F)
LOG="/var/log/rsync/backup_$TIMESTAMP.log"
mkdir -p "$BACKUP_BASE/$TIMESTAMP" /var/log/rsync
echo "=== Backup started: $(date) ===" >> "$LOG"
# 1. Database export
if command -v mysqldump &>/dev/null; then
mysqldump --defaults-file=/root/.my.cnf \
--single-transaction --routines --triggers \
--all-databases | gzip > "$BACKUP_BASE/$TIMESTAMP/databases.sql.gz"
echo " Database exported" >> "$LOG"
fi
if command -v pg_dumpall &>/dev/null; then
pg_dumpall | gzip > "$BACKUP_BASE/$TIMESTAMP/postgres.sql.gz"
echo " PostgreSQL exported" >> "$LOG"
fi
# 2. Incremental file backup
rsync -av --stats \
--link-dest="$BACKUP_BASE/latest" \
--exclude='cache/' \
--exclude='*.log' \
--exclude='*.tmp' \
"$SOURCE" "$BACKUP_BASE/$TIMESTAMP/files/" \
>> "$LOG" 2>&1
# 3. Update latest symlink
ln -sfn "$BACKUP_BASE/$TIMESTAMP" "$BACKUP_BASE/latest"
# 4. Offsite sync
rsync -avz "$BACKUP_BASE/$TIMESTAMP/" \
user@offsite:/backups/$TIMESTAMP/ >> "$LOG" 2>&1
# 5. Retention cleanup
find "$BACKUP_BASE" -maxdepth 1 -type d -name "20*" -mtime +14 -exec rm -rf {} \;
find /var/log/rsync/ -name "*.log" -mtime +30 -delete
echo "=== Backup complete: $(date) ===" >> "$LOG"
Verifying Backups
A backup you've never tested is not a backup — it's a hope.
Quick Verification
# Compare file counts
find /var/www/html/ -type f | wc -l
find /backup/latest/files/ -type f | wc -l
# Spot-check critical files
diff /var/www/html/.env /backup/latest/files/.env
diff /var/www/html/wp-config.php /backup/latest/files/wp-config.php
Integrity Check with --checksum
# Verify backup matches source (byte-level)
rsync -avnc /var/www/html/ /backup/latest/files/
# No output = perfect match
Scheduled Restore Test
# Monthly: restore to staging and verify
rsync -av /backup/latest/files/ /var/www/staging/
gunzip < /backup/latest/databases.sql.gz | mysql staging_db
# Visit staging site to verify
Common Pitfalls
| Pitfall | Consequence | Prevention |
|---|---|---|
| Files only, no database | Incomplete restore — site broken | Always export DB alongside files |
| No offsite copy | Server failure = total data loss | Sync to remote server or cloud |
| No retention policy | Disk fills up | Automate cleanup with find -mtime |
| Never testing restores | Discover backup is corrupt during emergency | Monthly restore drills |
| Backing up to same disk | Disk failure loses backup too | Use separate drive or remote server |
Quick Reference
# Full backup
rsync -av /var/www/ /backup/full-$(date +%F)/
# Incremental backup
rsync -av --link-dest=/backup/latest /var/www/ /backup/$(date +%F)/
ln -sfn /backup/$(date +%F) /backup/latest
# Differential backup
rsync -av --compare-dest=/backup/full-baseline/ /var/www/ /backup/diff-$(date +%F)/
# Verify backup integrity
rsync -avnc /var/www/ /backup/latest/
# Cleanup old backups
find /backup/ -maxdepth 1 -type d -name "20*" -mtime +14 -exec rm -rf {} \;
What's Next
- Disaster Recovery — Restore from backups during incidents
- Cloud Storage Integration — Extend backups to S3, GCS, and beyond
- Cron Automation — Schedule these strategies