Skip to content
Search RunEntLinux

Searches general documentation on RunEntLinux. Each distribution has its own documentation search.

    Browse this documentation

    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.

    Before defining a strategy, understand the three basic backup types:

    TypeDescriptionProsCons
    Full BackupBacks up all data every timeSimple and fast recoveryTime-consuming, requires more storage
    Incremental BackupOnly backs up data changed since the last backupFast, saves spaceRecovery requires the full chain
    Differential BackupBacks up data changed since the last full backupRelatively simple recoveryData volume grows over time

    Recommended backup strategy (Grandfather-Father-Son scheme):

    • Daily: Incremental backup
    • Weekly: Full backup
    • Monthly: Full backup archived offline

    rsync is the most commonly used file synchronization tool, supporting incremental transfers that only copy changed portions.

    Terminal window
    # Local directory sync
    rsync -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 progress
    Terminal window
    # Push to a remote server
    rsync -avhz --progress /var/www/ user@backup-server:/backup/www/
    # Pull from a remote server
    rsync -avhz --progress user@backup-server:/data/ /local/backup/data/
    # -z enables compressed transfer, useful when bandwidth is limited
    Terminal window
    # Exclude directories/files that don't need to be backed up
    rsync -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 preview
    rsync -avhn --delete /var/www/ /backup/www/ # -n is equivalent to --dry-run
    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:

    /usr/local/bin/rsync_backup.sh
    #!/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 links
    if [ -d "$LATEST" ]; then
    LINK_DEST="--link-dest=${LATEST}"
    else
    LINK_DEST=""
    fi
    # Perform the backup
    rsync -avh --delete $LINK_DEST "${SOURCE}/" "${TARGET}/"
    # Update the latest symlink
    rm -f "$LATEST"
    ln -s "$TARGET" "$LATEST"
    # Delete backups older than 30 days
    find "$BACKUP_BASE" -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +
    echo "[$(date)] Backup complete: ${TARGET}"

    tar is suitable for creating complete packaged archives of specific directories.

    Terminal window
    # Create a compressed archive
    tar 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 backup
    tar czf /backup/www-$(date +%Y%m%d).tar.gz \
    --exclude='*.log' \
    --exclude='cache' \
    -C / var/www

    tar supports incremental backups based on snapshot files:

    Terminal window
    # 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/www

    When restoring incremental backups, you must first restore the full backup, then apply each incremental in order:

    Terminal window
    # Restore the full backup
    tar xzf /backup/www-full-20260301.tar.gz -C /
    # Restore incremental backups in order
    tar xzf /backup/www-incr-20260302_020000.tar.gz -C /
    tar xzf /backup/www-incr-20260303_020000.tar.gz -C /

    BorgBackup (Borg for short) is a modern deduplicating and compressing backup tool, well-suited for server backup scenarios.

    Terminal window
    sudo dnf install epel-release -y
    sudo dnf install borgbackup -y
    Terminal window
    # Local repository
    borg 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!
    Terminal window
    # Create a backup archive
    borg 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'
    Terminal window
    # List all archives
    borg list /backup/borg-repo
    # View the contents of a specific archive
    borg list /backup/borg-repo::myserver-2026-03-20_020000
    # Restore to a specific directory
    cd /tmp/restore
    borg extract /backup/borg-repo::myserver-2026-03-20_020000
    # Restore only a specific path
    borg extract /backup/borg-repo::myserver-2026-03-20_020000 var/www
    Terminal window
    # Retention policy: 7 daily, 4 weekly, 6 monthly
    borg prune \
    --verbose \
    --list \
    --keep-daily=7 \
    --keep-weekly=4 \
    --keep-monthly=6 \
    /backup/borg-repo
    # Free disk space from deleted archives
    borg compact /backup/borg-repo
    /usr/local/bin/borg_backup.sh
    #!/bin/bash
    # BorgBackup automated backup script
    set -euo pipefail
    # Configuration
    export BORG_REPO="/backup/borg-repo"
    export BORG_PASSPHRASE="your_secure_passphrase" # In production, consider using a key file
    BACKUP_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 backup
    borg 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 backups
    borg 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 variable
    unset BORG_PASSPHRASE
    Terminal window
    chmod 700 /usr/local/bin/borg_backup.sh
    Terminal window
    sudo crontab -e
    # Run Borg backup daily at 2 AM
    0 2 * * * /usr/local/bin/borg_backup.sh >> /var/log/borg_backup.log 2>&1
    # Run rsync backup daily at 3 AM
    0 3 * * * /usr/local/bin/rsync_backup.sh >> /var/log/rsync_backup.log 2>&1

    systemd timers are more flexible than cron, with support for log integration and dependency management.

    Terminal window
    # Create the service unit
    sudo tee /etc/systemd/system/borg-backup.service > /dev/null <<'EOF'
    [Unit]
    Description=BorgBackup Daily Backup
    After=network-online.target
    Wants=network-online.target
    [Service]
    Type=oneshot
    ExecStart=/usr/local/bin/borg_backup.sh
    Nice=19
    IOSchedulingClass=idle
    EOF
    # Create the timer unit
    sudo tee /etc/systemd/system/borg-backup.timer > /dev/null <<'EOF'
    [Unit]
    Description=Run BorgBackup daily at 2 AM
    [Timer]
    OnCalendar=*-*-* 02:00:00
    RandomizedDelaySec=600
    Persistent=true
    [Install]
    WantedBy=timers.target
    EOF
    # Enable the timer
    sudo systemctl daemon-reload
    sudo systemctl enable --now borg-backup.timer
    # Check timer status
    systemctl list-timers borg-backup.timer
    # Manually trigger a test run
    sudo systemctl start borg-backup.service
    journalctl -u borg-backup.service -f

    Local backups cannot protect against datacenter-level disasters (fire, power outages, widespread hardware failure). You should maintain at least one offsite backup.

    /usr/local/bin/offsite_backup.sh
    #!/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}/"
    Terminal window
    # Install rclone
    sudo 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 storage
    rclone sync /backup/borg-repo remote:my-backup-bucket/borg-repo \
    --transfers=4 \
    --progress
    Terminal window
    # Create a temporary recovery directory
    mkdir -p /tmp/restore-test
    cd /tmp/restore-test
    # Restore from the latest archive
    export 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 integrity
    echo "File count: $(find . -type f | wc -l)"
    echo "Total size: $(du -sh .)"
    # Compare key files
    diff /etc/nginx/nginx.conf /tmp/restore-test/etc/nginx/nginx.conf
    diff -r /var/www/html/ /tmp/restore-test/var/www/html/ | head -20
    # Clean up
    rm -rf /tmp/restore-test
    unset BORG_PASSPHRASE
    /usr/local/bin/test_restore.sh
    #!/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 archive
    LATEST=$(borg list --last 1 --short "$BORG_REPO")
    # Attempt recovery
    if 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"
    fi
    else
    RESULT="Failed - borg extract returned an error"
    fi
    # Send report
    REPORT="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 up
    rm -rf "$RESTORE_DIR"
    unset BORG_PASSPHRASE

    Add the recovery test to a scheduled task; running it once a month is recommended:

    Terminal window
    # Run recovery test at 5 AM on the 1st of every month
    0 5 1 * * /usr/local/bin/test_restore.sh >> /var/log/restore_test.log 2>&1

    In addition to file backups, databases require their own backup strategies.

    Terminal window
    # Full backup
    mysqldump --all-databases --single-transaction --quick \
    --routines --triggers \
    -u root -p | gzip > /backup/mysql-$(date +%Y%m%d).sql.gz
    # Specific database
    mysqldump --single-transaction -u root -p mydb | gzip > /backup/mydb-$(date +%Y%m%d).sql.gz
    # Restore
    gunzip < /backup/mysql-20260320.sql.gz | mysql -u root -p
    Terminal window
    # Full backup
    sudo -u postgres pg_dumpall | gzip > /backup/postgres-$(date +%Y%m%d).sql.gz
    # Specific database
    sudo -u postgres pg_dump mydb | gzip > /backup/mydb-$(date +%Y%m%d).sql.gz
    # Restore
    gunzip < /backup/postgres-20260320.sql.gz | sudo -u postgres psql

    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