Backup Strategy
Data is the most valuable asset on a server. Hardware can be replaced, software can be reinstalled, but lost data is often unrecoverable. This article covers backup solutions from basic to advanced, helping you build a reliable data protection system.
Backup Types
Section titled “Backup Types”Before defining a strategy, understand the three basic backup types:
| Type | Description | Pros | Cons |
|---|---|---|---|
| Full Backup | Backs up all data every time | Simple and fast recovery | Time-consuming, requires more storage |
| Incremental Backup | Only backs up data changed since the last backup | Fast, saves space | Recovery requires the full chain |
| Differential Backup | Backs up data changed since the last full backup | Relatively simple recovery | Data volume grows over time |
Recommended backup strategy (Grandfather-Father-Son scheme):
- Daily: Incremental backup
- Weekly: Full backup
- Monthly: Full backup archived offline
Backing Up with rsync
Section titled “Backing Up with rsync”rsync is the most commonly used file synchronization tool, supporting incremental transfers that only copy changed portions.
Basic Usage
Section titled “Basic Usage”# Local directory syncrsync -avh --progress /var/www/ /backup/www/
# Parameter explanation:# -a Archive mode (preserves permissions, timestamps, symlinks, etc.)# -v Verbose output# -h Human-readable file sizes# --progress Show transfer progressRemote Backup
Section titled “Remote Backup”# Push to a remote serverrsync -avhz --progress /var/www/ user@backup-server:/backup/www/
# Pull from a remote serverrsync -avhz --progress user@backup-server:/data/ /local/backup/data/
# -z enables compressed transfer, useful when bandwidth is limitedSync with Exclusion Rules and Deletion
Section titled “Sync with Exclusion Rules and Deletion”# Exclude directories/files that don't need to be backed uprsync -avh \ --exclude='*.log' \ --exclude='cache/' \ --exclude='tmp/' \ --delete \ /var/www/ /backup/www/
# --delete removes extra files on the destination to keep it fully in sync# Note: --delete is risky; for the first run, add --dry-run to previewrsync -avhn --delete /var/www/ /backup/www/ # -n is equivalent to --dry-runrsync Incremental Backup Script (with Hard Links)
Section titled “rsync Incremental Backup Script (with Hard Links)”Use hard links for efficient incremental backups where each backup appears to be a full copy but only occupies space for changed files:
#!/bin/bash# Incremental backup using hard links
SOURCE="/var/www"BACKUP_BASE="/backup/www"DATE=$(date +%Y-%m-%d_%H%M%S)LATEST="${BACKUP_BASE}/latest"TARGET="${BACKUP_BASE}/${DATE}"
# If a previous backup exists, use hard linksif [ -d "$LATEST" ]; then LINK_DEST="--link-dest=${LATEST}"else LINK_DEST=""fi
# Perform the backuprsync -avh --delete $LINK_DEST "${SOURCE}/" "${TARGET}/"
# Update the latest symlinkrm -f "$LATEST"ln -s "$TARGET" "$LATEST"
# Delete backups older than 30 daysfind "$BACKUP_BASE" -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +
echo "[$(date)] Backup complete: ${TARGET}"Backing Up with tar
Section titled “Backing Up with tar”tar is suitable for creating complete packaged archives of specific directories.
Full Backup
Section titled “Full Backup”# Create a compressed archivetar czf /backup/www-$(date +%Y%m%d).tar.gz -C / var/www
# Use xz compression (higher compression ratio, but slower)tar cJf /backup/www-$(date +%Y%m%d).tar.xz -C / var/www
# Exclude specific content during backuptar czf /backup/www-$(date +%Y%m%d).tar.gz \ --exclude='*.log' \ --exclude='cache' \ -C / var/wwwIncremental Backup
Section titled “Incremental Backup”tar supports incremental backups based on snapshot files:
# First run — full backup (creates the snapshot file)tar czf /backup/www-full-$(date +%Y%m%d).tar.gz \ --listed-incremental=/backup/www.snar \ -C / var/www
# Subsequent runs — incremental backup (based on the snapshot file)tar czf /backup/www-incr-$(date +%Y%m%d_%H%M%S).tar.gz \ --listed-incremental=/backup/www.snar \ -C / var/wwwWhen restoring incremental backups, you must first restore the full backup, then apply each incremental in order:
# Restore the full backuptar xzf /backup/www-full-20260301.tar.gz -C /
# Restore incremental backups in ordertar xzf /backup/www-incr-20260302_020000.tar.gz -C /tar xzf /backup/www-incr-20260303_020000.tar.gz -C /Using BorgBackup
Section titled “Using BorgBackup”BorgBackup (Borg for short) is a modern deduplicating and compressing backup tool, well-suited for server backup scenarios.
Installation
Section titled “Installation”sudo dnf install epel-release -ysudo dnf install borgbackup -yInitializing a Repository
Section titled “Initializing a Repository”# Local repositoryborg init --encryption=repokey /backup/borg-repo
# Remote repository (via SSH)borg init --encryption=repokey ssh://user@backup-server/backup/borg-repo
# You will be prompted to enter a passphrase for the encryption key — keep it safe!Creating a Backup
Section titled “Creating a Backup”# Create a backup archiveborg create \ --verbose \ --stats \ --progress \ --compression zstd,3 \ /backup/borg-repo::'{hostname}-{now:%Y-%m-%d_%H%M%S}' \ /var/www \ /etc \ /home \ --exclude '/home/*/.cache' \ --exclude '*.tmp'Viewing and Restoring
Section titled “Viewing and Restoring”# List all archivesborg list /backup/borg-repo
# View the contents of a specific archiveborg list /backup/borg-repo::myserver-2026-03-20_020000
# Restore to a specific directorycd /tmp/restoreborg extract /backup/borg-repo::myserver-2026-03-20_020000
# Restore only a specific pathborg extract /backup/borg-repo::myserver-2026-03-20_020000 var/wwwAutomatic Cleanup of Old Backups
Section titled “Automatic Cleanup of Old Backups”# Retention policy: 7 daily, 4 weekly, 6 monthlyborg prune \ --verbose \ --list \ --keep-daily=7 \ --keep-weekly=4 \ --keep-monthly=6 \ /backup/borg-repo
# Free disk space from deleted archivesborg compact /backup/borg-repoComplete Automated Borg Backup Script
Section titled “Complete Automated Borg Backup Script”#!/bin/bash# BorgBackup automated backup script
set -euo pipefail
# Configurationexport BORG_REPO="/backup/borg-repo"export BORG_PASSPHRASE="your_secure_passphrase" # In production, consider using a key fileBACKUP_NAME="{hostname}-{now:%Y-%m-%d_%H%M%S}"LOG_FILE="/var/log/borg_backup.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"}
log "Starting backup..."
# Create the backupborg create \ --verbose \ --stats \ --compression zstd,3 \ --exclude '/home/*/.cache' \ --exclude '/var/tmp/*' \ --exclude '/var/log/*.gz' \ "${BORG_REPO}::${BACKUP_NAME}" \ /etc \ /home \ /var/www \ /var/lib/mysql \ 2>&1 | tee -a "$LOG_FILE"
log "Backup complete, cleaning up old archives..."
# Clean up old backupsborg prune \ --verbose \ --list \ --keep-daily=7 \ --keep-weekly=4 \ --keep-monthly=6 \ "$BORG_REPO" \ 2>&1 | tee -a "$LOG_FILE"
borg compact "$BORG_REPO" 2>&1 | tee -a "$LOG_FILE"
log "All operations complete."
# Unset the passphrase variableunset BORG_PASSPHRASEchmod 700 /usr/local/bin/borg_backup.shAutomated Scheduling
Section titled “Automated Scheduling”Using cron
Section titled “Using cron”sudo crontab -e
# Run Borg backup daily at 2 AM0 2 * * * /usr/local/bin/borg_backup.sh >> /var/log/borg_backup.log 2>&1
# Run rsync backup daily at 3 AM0 3 * * * /usr/local/bin/rsync_backup.sh >> /var/log/rsync_backup.log 2>&1Using systemd timer (Recommended)
Section titled “Using systemd timer (Recommended)”systemd timers are more flexible than cron, with support for log integration and dependency management.
# Create the service unitsudo tee /etc/systemd/system/borg-backup.service > /dev/null <<'EOF'[Unit]Description=BorgBackup Daily BackupAfter=network-online.targetWants=network-online.target
[Service]Type=oneshotExecStart=/usr/local/bin/borg_backup.shNice=19IOSchedulingClass=idleEOF
# Create the timer unitsudo tee /etc/systemd/system/borg-backup.timer > /dev/null <<'EOF'[Unit]Description=Run BorgBackup daily at 2 AM
[Timer]OnCalendar=*-*-* 02:00:00RandomizedDelaySec=600Persistent=true
[Install]WantedBy=timers.targetEOF
# Enable the timersudo systemctl daemon-reloadsudo systemctl enable --now borg-backup.timer
# Check timer statussystemctl list-timers borg-backup.timer
# Manually trigger a test runsudo systemctl start borg-backup.servicejournalctl -u borg-backup.service -fOffsite Backup
Section titled “Offsite Backup”Local backups cannot protect against datacenter-level disasters (fire, power outages, widespread hardware failure). You should maintain at least one offsite backup.
rsync to a Remote Server
Section titled “rsync to a Remote Server”#!/bin/bash# Sync local backups to a remote server
REMOTE_USER="backup"REMOTE_HOST="offsite-backup.example.com"REMOTE_PATH="/backup/$(hostname)"LOCAL_BACKUP="/backup/borg-repo"
# Use SSH key authentication (avoid password prompts)rsync -avhz --progress \ -e "ssh -i /root/.ssh/backup_key -o StrictHostKeyChecking=yes" \ "${LOCAL_BACKUP}/" \ "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/"Sync to Object Storage (S3-Compatible)
Section titled “Sync to Object Storage (S3-Compatible)”# Install rclonesudo dnf install rclone -y
# Configure (interactive)rclone config# Follow the prompts to configure an S3-compatible storage (e.g., AWS S3, MinIO, Alibaba Cloud OSS, etc.)
# Sync backups to object storagerclone sync /backup/borg-repo remote:my-backup-bucket/borg-repo \ --transfers=4 \ --progressTesting Recovery
Section titled “Testing Recovery”Borg Recovery Test
Section titled “Borg Recovery Test”# Create a temporary recovery directorymkdir -p /tmp/restore-testcd /tmp/restore-test
# Restore from the latest archiveexport BORG_REPO="/backup/borg-repo"export BORG_PASSPHRASE="your_secure_passphrase"
LATEST=$(borg list --last 1 --short "$BORG_REPO")echo "Restoring archive: ${LATEST}"borg extract "${BORG_REPO}::${LATEST}"
# Verify file integrityecho "File count: $(find . -type f | wc -l)"echo "Total size: $(du -sh .)"
# Compare key filesdiff /etc/nginx/nginx.conf /tmp/restore-test/etc/nginx/nginx.confdiff -r /var/www/html/ /tmp/restore-test/var/www/html/ | head -20
# Clean uprm -rf /tmp/restore-testunset BORG_PASSPHRASEAutomated Recovery Test Script
Section titled “Automated Recovery Test Script”#!/bin/bash# Automated backup recovery test
set -euo pipefail
RESTORE_DIR="/tmp/restore-test-$(date +%s)"BORG_REPO="/backup/borg-repo"export BORG_PASSPHRASE="your_secure_passphrase"RESULT="Success"
mkdir -p "$RESTORE_DIR"cd "$RESTORE_DIR"
# Get the latest archiveLATEST=$(borg list --last 1 --short "$BORG_REPO")
# Attempt recoveryif borg extract "${BORG_REPO}::${LATEST}"; then FILE_COUNT=$(find . -type f | wc -l) TOTAL_SIZE=$(du -sh . | awk '{print $1}')
# Basic validation if [ "$FILE_COUNT" -eq 0 ]; then RESULT="Failed - restored file count is 0" fielse RESULT="Failed - borg extract returned an error"fi
# Send reportREPORT="Backup Recovery Test Report\n\n"REPORT+="Date: $(date)\n"REPORT+="Archive: ${LATEST}\n"REPORT+="Result: ${RESULT}\n"REPORT+="File Count: ${FILE_COUNT:-N/A}\n"REPORT+="Data Size: ${TOTAL_SIZE:-N/A}\n"
echo -e "$REPORT" | mail -s "[Backup Test] Recovery Test ${RESULT}" "$MAILTO"
# Clean uprm -rf "$RESTORE_DIR"unset BORG_PASSPHRASEAdd the recovery test to a scheduled task; running it once a month is recommended:
# Run recovery test at 5 AM on the 1st of every month0 5 1 * * /usr/local/bin/test_restore.sh >> /var/log/restore_test.log 2>&1Database Backup
Section titled “Database Backup”In addition to file backups, databases require their own backup strategies.
MySQL/MariaDB
Section titled “MySQL/MariaDB”# Full backupmysqldump --all-databases --single-transaction --quick \ --routines --triggers \ -u root -p | gzip > /backup/mysql-$(date +%Y%m%d).sql.gz
# Specific databasemysqldump --single-transaction -u root -p mydb | gzip > /backup/mydb-$(date +%Y%m%d).sql.gz
# Restoregunzip < /backup/mysql-20260320.sql.gz | mysql -u root -pPostgreSQL
Section titled “PostgreSQL”# Full backupsudo -u postgres pg_dumpall | gzip > /backup/postgres-$(date +%Y%m%d).sql.gz
# Specific databasesudo -u postgres pg_dump mydb | gzip > /backup/mydb-$(date +%Y%m%d).sql.gz
# Restoregunzip < /backup/postgres-20260320.sql.gz | sudo -u postgres psqlBackup Strategy Checklist
Section titled “Backup Strategy Checklist”When planning a backup solution, verify each of the following:
- Clearly define the scope of data to back up (configuration files, website data, databases, user data)
- Choose appropriate backup tools and types
- Set backup frequency (daily/weekly/monthly)
- Configure automated scheduling
- Set a backup retention policy (how many copies, how long to keep)
- Maintain at least one offsite backup
- Encrypt backup data (especially for offsite/cloud backups)
- Perform a recovery test at least once a month
- Monitor whether backup tasks complete successfully on schedule
- Document the complete recovery procedure