Quick take: rsync is the most reliable Linux backup tool I know — battle-tested, built into almost every distribution, and produces backups that are just plain files in directories. No proprietary format, no special restore tool needed. You can test a restore at any time with a simple copy command.
Introduction
I have been doing infrastructure work for 15 years and I have seen the same backup failure pattern repeatedly: the backup exists, the restore does not work. Either the backup was never tested, the storage filled up silently, the cron job stopped running, or the retention policy was never configured. A backup system that has never had a successful restore tested is not a backup system — it is a hope.
rsync eliminates most of these failure modes. It is simple enough that you understand exactly what it is doing at every step. The backups are plain files in directories — you can open them in a file manager, test a restore with a simple copy command, and verify completeness with a file count. This guide builds a real backup system from scratch.
rsync Basics and Essential Flags
The basic syntax is: rsync [options] source destination
A trailing slash on the source path changes what gets copied. rsync -a /data/ /backup/ copies the contents of /data directly into /backup. Without the trailing slash, rsync -a /data /backup/ creates /backup/data/ as a subdirectory. Be consistent — always use trailing slashes on both source and destination to avoid confusion.
The flags I use on every backup job:
-a # Archive: preserves permissions, timestamps, symlinks, owner, group
-v # Verbose: shows what is transferred (remove for silent cron jobs)
-z # Compress during transfer (useful over slow WAN, skip on fast LAN)
--delete # Delete from destination files that no longer exist in source
--progress # Show transfer progress for large files
-n / --dry-run # Show what would happen without making any changes — always test first
--exclude # Skip files or directories matching a pattern
--link-dest # Hard-link unchanged files from a reference backup (key to incrementals)
Your First Local Backup
Start simple. Backup your web root to a separate drive or partition:
# Always dry-run first to see what will be copied
rsync -avn /var/www/ /mnt/backup/www/
# Run for real when satisfied with the dry-run output
rsync -av /var/www/ /mnt/backup/www/
Run it a second time immediately. rsync will report "sent X bytes" but transfer almost nothing, because the destination is already in sync. This is rsync's efficiency: it compares source and destination by file size and modification time, and only transfers what has actually changed.
Use --delete to keep the backup as an exact mirror. Without it, files you delete from the source accumulate in the backup indefinitely:
rsync -av --delete /var/www/ /mnt/backup/www/
Remote Backup over SSH
rsync uses SSH for remote transfers. The syntax uses a colon to separate the host from the path:
# Push to a remote backup server
rsync -avz --delete /var/www/ backupuser@192.168.1.200:/backups/webserver/
# Pull from a remote server to local storage
rsync -avz --delete remoteuser@203.0.113.10:/var/www/ /mnt/backup/remote-web/
For automated backups, generate a dedicated SSH key without a passphrase so the script can run unattended. Store this key separately from your personal key:
ssh-keygen -t ed25519 -f ~/.ssh/backup_key -C "automated-backup-$(date +%Y)" -N ""
Copy it to the backup destination server:
ssh-copy-id -i ~/.ssh/backup_key.pub backupuser@192.168.1.200
Specify the key in rsync commands:
rsync -avz -e "ssh -i ~/.ssh/backup_key" /var/www/ backupuser@192.168.1.200:/backups/www/
Incremental Snapshots with Hard Links
A simple nightly mirror gives you one recovery point — if a file is deleted before the next backup runs, it is gone from both places. Incremental backups with hard links solve this by maintaining multiple dated snapshots without using proportionally more disk space.
rsync's --link-dest flag is the key. It tells rsync to hard-link files in the new backup to files in a previous backup if they are identical. A hard link is not a copy — it is another name for the same data on disk. So a month of daily backups where 2% of files change daily uses only about 60% more space than a single full backup, not 30 times more.
The pattern for date-stamped snapshots:
TODAY=$(date +%Y-%m-%d)
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
BACKUP_ROOT=/mnt/backup/snapshots
rsync -av --delete --link-dest=$BACKUP_ROOT/$YESTERDAY /var/www/ $BACKUP_ROOT/$TODAY/
Each day creates a new dated directory. Files that did not change are hard-linked to yesterday's snapshot — they appear as full files in the new directory but share disk blocks with the previous version. Files that changed are copied fresh.
Writing the Backup Script
A production backup script needs logging, error handling, and retention management. Save this as /usr/local/bin/backup.sh:
#!/bin/bash
set -euo pipefail
SOURCE="/var/www/"
BACKUP_ROOT="/mnt/backup/snapshots"
REMOTE_HOST="backupuser@192.168.1.200"
REMOTE_PATH="/backups/webserver"
SSH_KEY="/root/.ssh/backup_key"
LOGFILE="/var/log/backup.log"
RETENTION_DAYS=30
TODAY=$(date +%Y-%m-%d)
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"; }
log "Backup started"
# Local incremental snapshot
rsync -a --delete --link-dest="$BACKUP_ROOT/$YESTERDAY" "$SOURCE" "$BACKUP_ROOT/$TODAY/" >> "$LOGFILE" 2>&1 || { log "ERROR: local backup failed"; exit 1; }
log "Local snapshot complete: $BACKUP_ROOT/$TODAY"
# Mirror latest snapshot to remote server
rsync -az --delete -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" "$BACKUP_ROOT/$TODAY/" "$REMOTE_HOST:$REMOTE_PATH/$TODAY/" >> "$LOGFILE" 2>&1 || { log "ERROR: remote backup failed"; exit 1; }
log "Remote backup complete"
# Remove snapshots older than retention period
find "$BACKUP_ROOT" -maxdepth 1 -type d -name "????-??-??" -mtime +$RETENTION_DAYS -exec rm -rf {} \; >> "$LOGFILE" 2>&1
log "Cleanup complete. Retention: $RETENTION_DAYS days"
log "Backup finished successfully"
Make it executable and test manually before scheduling:
sudo chmod +x /usr/local/bin/backup.sh
sudo /usr/local/bin/backup.sh
Check the log file confirms success:
tail -20 /var/log/backup.log
Automating with Cron
Schedule the backup to run nightly at 2am:
sudo crontab -e
# Nightly backup at 2:00am
0 2 * * * /usr/local/bin/backup.sh
# Send email on failure (requires a mail transfer agent installed)
MAILTO="your@email.com"
For more critical data, run more frequently. Every 6 hours covers most disaster recovery needs without excessive storage growth:
0 */6 * * * /usr/local/bin/backup.sh
Verify cron ran successfully by checking the log the morning after enabling it:
cat /var/log/backup.log
Verifying Your Backups
A backup that has never been tested is not a backup. Test it before you ever need it. Compare a snapshot against the current source to confirm they match:
rsync -avn --delete /var/www/ /mnt/backup/snapshots/$(date +%Y-%m-%d)/
If today's snapshot is correct, this dry-run should show zero files to transfer or delete. Verify file counts match:
find /var/www/ -type f | wc -l
find /mnt/backup/snapshots/$(date +%Y-%m-%d)/ -type f | wc -l
Do a real restore test monthly — copy a file from a week-old snapshot to a temporary location and confirm it is intact:
cp /mnt/backup/snapshots/2026-06-15/index.html /tmp/restore-test.html
diff /var/www/index.html /tmp/restore-test.html
Document your restore procedure so that during an actual emergency — when you are under pressure and short on time — you are executing a tested procedure, not improvising.
Restoring Files and Directories
Because rsync backups are plain files, restoration is a simple copy operation:
# Restore a single file from a specific snapshot date
cp /mnt/backup/snapshots/2026-06-15/uploads/logo.png /var/www/uploads/logo.png
# Restore a whole directory from a specific date
rsync -av /mnt/backup/snapshots/2026-06-15/uploads/ /var/www/uploads/
# Full restore (caution — overwrites current state)
rsync -av --delete /mnt/backup/snapshots/2026-06-15/ /var/www/
For remote backups, pull from the backup server:
rsync -avz -e "ssh -i ~/.ssh/backup_key" backupuser@192.168.1.200:/backups/webserver/2026-06-15/uploads/ /var/www/uploads/
The 3-2-1 Backup Rule
The 3-2-1 rule is the gold standard in backup strategy:
- 3 copies of your data — the live data plus two backups
- 2 different storage media — not both on the same server or same disk
- 1 offsite copy — geographically separated from the primary site
The setup in this guide achieves all three: live data on the web server + local incremental snapshots on an attached drive + remote copy on a separate server. For the offsite copy you can add cloud object storage using rclone, which speaks rsync-like syntax but writes to AWS S3, Backblaze B2, Cloudflare R2, or any other compatible backend.
Final Thoughts
The most important property of the backup system in this guide is its transparency. You can see every snapshot, open every file, count every directory, and run a restore test in two minutes. There is no proprietary format, no subscription service, and no special tool required to access your backup data.
The second most important property is the automated retention cleanup. Storage that fills up silently is one of the most common backup failures I have seen. With the 30-day retention and automated cleanup in the script, you will always have a month of daily snapshots and no surprise storage exhaustion at 3am.
Set it up, test a restore, schedule the cron job, and check the log once a week for a month. After that you can trust it and largely forget about it — which is exactly what a good backup system should allow you to do.
FAQ: How to Automate Linux Server Backups with rsync
How is rsync different from a simple file copy?+
rsync compares source and destination by file size and modification time before transferring anything. On the second run, rsync only copies files that have changed — not the entire dataset. This makes ongoing backups fast even for large directories, and also means you can run the backup frequently without it being expensive in time or bandwidth.
How much extra disk space do incremental rsync backups use?+
With --link-dest, unchanged files are hard-linked rather than copied, so they consume no additional disk space — a hard link is just another name pointing at the same data on disk. Only changed or new files use additional space. A month of daily snapshots for a dataset where 2% of files change daily uses roughly 60% more space than a single backup, not 30 times more.
Can I use rsync to back up a running database?+
Not directly — backing up a live database with rsync can produce a corrupted backup if the database files change during the copy. Always dump the database first using mysqldump or pg_dump, then back up the dump file with rsync. The dump is a consistent point-in-time snapshot that rsync can safely copy.
How do I restore a specific file from an rsync backup?+
Simply copy the file back from the backup snapshot directory: cp /mnt/backup/snapshots/2026-06-15/path/to/file.txt /original/path/. Because rsync backups are plain files in plain directories, restoration requires no special tools — any file manager or copy command works.
What does --delete do and when should I use it?+
Without --delete, files removed from the source accumulate in the backup destination indefinitely — the backup grows larger than the source over time. With --delete, the destination is kept as an exact mirror: files deleted from source are also deleted from the backup on the next run. Use --delete for mirror backups. Omit it if you want to archive and preserve deleted files.
Need help with your Linux server or infrastructure?
Work directly with Muhammad Irfan Aslam for Linux, Docker, Nginx, DevOps, cloud, and server support.
Hire Me for Support