rsync Backup Patterns¶
rsync is the tool for moving files on Linux. Fast, resumable, bandwidth-efficient — it only transfers what changed. For local and remote backups, it's what I reach for first.
The Short Answer¶
# Sync source to destination (local)
rsync -av /source/ /destination/
# Sync to remote server
rsync -avz /source/ user@server:/destination/
# Dry run first — see what would change
rsync -avnP /source/ /destination/
Core Flags¶
| Flag | What it does |
|---|---|
-a |
Archive mode: preserves permissions, timestamps, symlinks, owner/group. Use this almost always. |
-v |
Verbose output — shows files being transferred. |
-z |
Compress during transfer. Useful over slow connections, overhead on fast LAN. |
-P |
Progress + partial transfers (resumes interrupted transfers). |
-n |
Dry run — shows what would happen without doing it. |
--delete |
Removes files from destination that no longer exist in source. Makes it a true mirror. |
--exclude |
Exclude patterns. |
--bwlimit |
Limit bandwidth in KB/s. |
Local Backup¶
# Basic sync
rsync -av /home/major/ /backup/home/
# Mirror — destination matches source exactly (deletes removed files)
rsync -av --delete /home/major/ /backup/home/
# Exclude directories
rsync -av --exclude='.cache' --exclude='Downloads' /home/major/ /backup/home/
# Multiple excludes from a file
rsync -av --exclude-from=exclude.txt /home/major/ /backup/home/
The trailing slash matters:
- /source/ — sync the contents of source into destination
- /source — sync the source directory itself into destination (creates /destination/source/)
Almost always want the trailing slash on the source.
Remote Backup¶
# Local → remote
rsync -avz /home/major/ user@server:/backup/major/
# Remote → local (pull backup)
rsync -avz user@server:/var/data/ /local/backup/data/
# Specify SSH port or key
rsync -avz -e "ssh -p 2222 -i ~/.ssh/id_ed25519" /source/ user@server:/dest/
Incremental Backups with Hard Links¶
The --link-dest pattern creates space-efficient incremental backups. Each backup looks like a full copy but only stores changed files — unchanged files are hard links to previous versions.
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/backup"
SOURCE="/home/major"
DATE="$(date +%Y-%m-%d)"
LATEST="${BACKUP_DIR}/latest"
DEST="${BACKUP_DIR}/${DATE}"
rsync -av --delete \
--link-dest="$LATEST" \
"$SOURCE/" \
"$DEST/"
# Update the 'latest' symlink
rm -f "$LATEST"
ln -s "$DEST" "$LATEST"
Each dated directory looks like a complete backup. Storage is only used for changed files. You can delete any dated directory without affecting others.
Backup Script with Logging¶
#!/usr/bin/env bash
set -euo pipefail
SOURCE="/home/major/"
DEST="/backup/home/"
LOG="/var/log/rsync-backup.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }
log "Backup started"
rsync -av --delete \
--exclude='.cache' \
--exclude='Downloads' \
--log-file="$LOG" \
"$SOURCE" "$DEST"
log "Backup complete"
Run via cron:
Or systemd timer (preferred):
# /etc/systemd/system/rsync-backup.timer
[Unit]
Description=Daily rsync backup
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Gotchas & Notes¶
- Test with
--dry-runfirst. Especially when using--delete. See what would be removed before actually removing it. --deleteis destructive. It removes files from the destination that don't exist in the source. That's the point, but know what you're doing.- Large files and slow connections: Add
-Pfor progress and partial transfer resume. An interrupted rsync picks up where it left off. - For network backups to untrusted locations, consider using rsync over SSH + encryption at rest. rsync over SSH handles transit encryption; storage encryption is separate.
- rsync vs Restic: rsync is fast and simple. Restic gives you deduplication, encryption, and multiple backend support (S3, B2, etc.). For local backups, rsync. For offsite with encryption needs, Restic.
See Also¶
- [[self-hosting-starter-guide]]
- [[bash-scripting-patterns]]