How to Use rsync to Sync Files on Linux
Copy, sync, and back up files locally or over SSH with rsync. Includes the most useful flags, real examples, and common pitfalls with trailing slashes.
rsync is the standard tool for syncing files on Linux. It copies only what has changed, works over SSH with no extra setup, and handles large directory trees efficiently. It is faster than scp for repeat transfers and more reliable than cp for keeping two directories in sync.
Basic syntax
rsync [options] source destination
Copy a directory locally
rsync -av /var/www/html/ /backup/html/
-a— archive mode: preserves permissions, ownership, timestamps, symlinks, and recurses into subdirectories-v— verbose: prints each file as it is transferred
The trailing slash rule
This is the most common rsync mistake:
rsync -av /src/mydir/ /dst/mydir/ # copies the CONTENTS of mydir into /dst/mydir/
rsync -av /src/mydir /dst/mydir/ # copies mydir ITSELF into /dst/mydir/ → /dst/mydir/mydir/
A trailing slash on the source means "the contents of this directory." No trailing slash means "this directory." The destination trailing slash makes no difference. When in doubt, use a trailing slash on the source.
Sync over SSH
rsync uses SSH as its transport by default when the destination contains a :.
rsync -avz /var/www/html/ user@192.168.1.10:/var/www/html/
-z— compress data during transfer; useful for text files over slow links, marginal for already-compressed files
Specify a non-default SSH port:
rsync -avz -e "ssh -p 2222" /var/www/html/ user@192.168.1.10:/var/www/html/
Pull files from a remote server to local:
rsync -avz user@192.168.1.10:/var/backups/ /local/backups/
Dry run before syncing
Always preview a destructive sync before running it live:
rsync -avz --dry-run /src/ /dst/
--dry-run (or -n) shows exactly what would be transferred or deleted without touching anything.
Delete files removed from the source
By default rsync is additive — it copies files to the destination but never removes anything. To mirror the source exactly:
rsync -av --delete /src/ /dst/
--delete removes files from the destination that no longer exist in the source. Combine with --dry-run before running this in production.
Exclude files and directories
rsync -av --exclude='*.log' --exclude='.git/' /src/ /dst/
Use an exclude file for longer lists:
rsync -av --exclude-from='rsync-exclude.txt' /src/ /dst/
Where rsync-exclude.txt contains one pattern per line:
*.log
.git/
node_modules/
__pycache__/
.env
Show progress for large transfers
rsync -avz --progress /large-dir/ user@remote:/large-dir/
For a single overall progress bar instead of per-file output:
rsync -avz --info=progress2 /large-dir/ user@remote:/large-dir/
Useful flag combinations
# Mirror a directory, delete removed files, dry-run first
rsync -avz --delete --dry-run /src/ user@remote:/dst/
# Backup with hard links (space-efficient snapshot)
rsync -a --link-dest=/backup/latest/ /src/ /backup/$(date +%Y-%m-%d)/
# Limit bandwidth to 5 MB/s (useful when syncing in the background)
rsync -avz --bwlimit=5000 /src/ user@remote:/dst/
# Preserve hard links (needed for some system directories)
rsync -avH /src/ /dst/
The --link-dest pattern creates an incremental backup where unchanged files are hard-linked from the previous snapshot instead of copied. Each snapshot appears to be a full backup but only the changed files consume new disk space.
Automate with cron
Run a nightly backup at 2 AM:
crontab -e
0 2 * * * rsync -az --delete /var/www/html/ user@backup-server:/backups/html/ >> /var/log/rsync.log 2>&1
Use SSH key authentication (no passphrase) so the cron job can run unattended. See the SSH Key Authentication tutorial for setup. To visually inspect or edit this cron schedule, use the Cron Visual Editor.
Verify the sync
Check that source and destination match exactly:
rsync -avnc /src/ /dst/
-c switches rsync from mtime+size comparison to a full checksum check. It is slower but definitive — any file that differs in content will show up, even if the timestamp looks right.
SysEmperor