rsync(1)
01 Anatomy & basics
The shape of every rsync command.
$ rsync [OPTIONS] SRC... DEST
Read it as: copy SRC to DEST, transferring only what's changed. rsync compares file size and mtime by default — checksum mode (-c) is opt-in and slower.
The one-liner you'll actually type
$ rsync -avh --progress ./project/ ~/backup/project/
-a = archive (recursion + permissions + times + symlinks + ...). -v = verbose. -h = human-readable sizes. --progress = show transfer progress per file.
02 Essential flags
The vocabulary that covers ~95% of real use.
| Flag | Meaning |
|---|---|
-a | Archive mode. Expands to -rlptgoD: recursive, symlinks-as-symlinks, perms, times, group, owner, devices/specials. |
-v / -vv | Verbose; add more vs for more noise. |
-z | Compress during transfer. Helpful over slow links, useless over LAN/local. |
-P | Short for --partial --progress. Resume partials & show progress. |
-h | Human-readable numbers (K/M/G). |
-n / --dry-run | Show what would happen. Use it. Every time. |
--delete | Delete files in DEST that no longer exist in SRC. Makes it a true mirror. |
--exclude=PAT | Skip paths matching PAT (e.g. '.git', '*.log'). |
-e ssh | Use ssh as the transport (default for remote). Customize: -e 'ssh -p 2222'. |
-H | Preserve hard links. Required for some backup setups. |
-A, -X | Preserve ACLs, extended attributes. |
-u | Skip files newer on the receiver. |
-c | Compare by checksum, not size+mtime. Slow but bulletproof. |
--stats | Print a summary block at the end. |
-avhP. It's the workhorse combo: archive, verbose, human sizes, partial + progress.
03 The trailing slash
The single biggest source of "wait, why did it do that?" — internalize this once.
SRC with trailing slash
$ rsync -av src/ dst/ # copies the CONTENTS of src/ # into dst/ → dst/file1, dst/file2
SRC without trailing slash
$ rsync -av src dst/ # copies the src DIRECTORY itself # into dst/ → dst/src/file1, dst/src/file2
04 Source & destination paths
rsync accepts four shapes of path. Mix at will.
| Pattern | Example |
|---|---|
| Local → Local | rsync -av /home/me/ /mnt/backup/ |
| Local → Remote (SSH) | rsync -av ./site/ user@host:/var/www/site/ |
| Remote → Local (SSH) | rsync -av user@host:/var/log/ ./logs/ |
| Remote → Remote (via local) | rsync -av user@a:/data/ user@b:/data/ (routes through your machine) |
| Daemon | rsync -av rsync://host/module/path/ ./ |
05 Remote & SSH transport
rsync just shells out to ssh by default. Anything ssh can do, rsync can use.
Custom port
$ rsync -avz -e 'ssh -p 2222' ./ user@host:/srv/app/
Specific identity key
$ rsync -avz -e 'ssh -i ~/.ssh/deploy_ed25519' ./ deploy@host:/srv/app/
Jump through a bastion host
$ rsync -avz -e 'ssh -J jump@bastion' ./ user@internal:/path/
Run sudo on the remote side
$ rsync -avz --rsync-path="sudo rsync" ./ user@host:/etc/myapp/
~/.ssh/config per-host. Then plain rsync -avz ./ host:/path/ just works — port, key, jump host, user, all picked up automatically.
06 Filters, includes & excludes
Choosing what does and doesn't cross the wire.
Simple excludes
$ rsync -av --exclude='*.log' --exclude='.git' ./ dst/
Exclude list from a file
$ cat .rsyncignore .git/ node_modules/ *.log *.tmp .DS_Store $ rsync -av --exclude-from=.rsyncignore ./ user@host:/srv/app/
Include some, exclude the rest
Order matters: the first matching rule wins. Includes guard the path and all its parent directories; the trailing catch-all excludes everything else.
$ rsync -av \ --include='*/' \ --include='*.conf' \ --exclude='*' \ /etc/ ./etc-confs/ # grabs only *.conf files, preserving the directory tree
Delete excluded files on the receiver too
$ rsync -av --delete --delete-excluded --exclude='*.tmp' ./ dst/
Pattern cheatsheet
| Pattern | Matches |
|---|---|
*.log | Any .log file at any depth. |
/build/ | A build/ directory at the source root only (leading / anchors). |
cache/ | Any directory named cache at any depth. |
**/tmp | A tmp entry at any depth (** crosses slashes; * doesn't). |
*/ | Any directory. Useful as --include so includes can descend. |
07 Backups
From dumb-but-safe to Time-Machine-style incremental snapshots.
Simple repeatable backup
$ rsync -aHAX --delete --info=progress2 \ /home/me/ /mnt/backup/home/ # -H hardlinks · -A ACLs · -X xattrs · --delete makes it a mirror
Move changed/deleted files aside instead of overwriting
$ rsync -av --delete \ --backup --backup-dir=/mnt/backup/attic/$(date +%F) \ /home/me/ /mnt/backup/home/ # anything overwritten or deleted goes to attic/2026-05-23/
Hard-linked snapshots (Time Machine style)
--link-dest tells rsync: for every file unchanged since this reference snapshot, just hard-link to it instead of copying. Snapshots are full trees, but unchanged files cost zero disk.
$ TODAY=$(date +%F) $ YESTERDAY=$(date -d 'yesterday' +%F) $ rsync -aH --delete \ --link-dest=/backup/$YESTERDAY \ /home/ /backup/$TODAY/ # /backup/2026-05-23/ looks like a full copy, but unchanged files # are hardlinks back to /backup/2026-05-22/
Pull backups from a remote server
$ rsync -aHAXz --info=progress2 \ user@server:/var/www/ /mnt/backup/server-www/
08 Server deployments
Shipping a built site/app to a server, atomically-ish.
Standard deploy
$ rsync -avz --delete \ --exclude='.git' \ --exclude='node_modules' \ --exclude='.env' \ ./dist/ deploy@web01:/var/www/myapp/
Deploy without nuking server-side state
Drop --delete, or keep it but exclude server-owned paths:
$ rsync -avz --delete \ --exclude='/uploads/' \ --exclude='/storage/' \ --exclude='/.env' \ ./dist/ deploy@web01:/var/www/myapp/ # leading / anchors to deploy root so only top-level uploads/ is spared
Atomic-style: stage, then swap
$ rsync -avz --delete ./dist/ deploy@web01:/var/www/myapp.next/ $ ssh deploy@web01 'ln -sfn /var/www/myapp.next /var/www/myapp.current && systemctl reload nginx'
Preview a deploy before doing it
$ rsync -avzn --delete --itemize-changes \ ./dist/ deploy@web01:/var/www/myapp/
--itemize-changes (or -i) prints a per-file change code like >f.st...... — that's "file, size changed, time changed". Pair with -n for a precise dry-run diff.
09 Local sync & mirroring
Moving stuff between disks, copying to a USB drive, mirroring a directory.
True mirror (DEST becomes identical to SRC)
$ rsync -avh --delete --info=progress2 \ ~/Photos/ /media/usb/Photos/
Move files (copy then remove source)
$ rsync -avh --remove-source-files ~/Downloads/iso/ /mnt/iso/ $ find ~/Downloads/iso/ -type d -empty -delete # --remove-source-files removes files only; clean up empty dirs after
Sync between two filesystems with different capabilities
$ rsync -rvh --size-only --modify-window=2 \ ~/Music/ /run/media/me/SDCARD/Music/ # FAT32 can't store perms or precise mtimes — drop -a, allow 2s mtime fuzz
Sync only new/updated files (don't clobber newer on dest)
$ rsync -avu ~/notes/ /mnt/notes/ # -u: skip files that are newer on the receiver
10 Bandwidth & performance
Squeeze the wire, or politely throttle yourself.
Cap bandwidth
$ rsync -avz --bwlimit=10M ./ user@host:/srv/ # units: bare number = KB/s · K/M/G suffix · "0" = unlimited
Resume big transfers
$ rsync -avzP --append-verify ./bigfile.iso user@host:/data/ # -P keeps partials around · --append-verify resumes & rechecks
Compression: when to use, when to skip
| Situation | Use -z? |
|---|---|
| Local-to-local copy | No — pure CPU overhead. |
| LAN gigabit, mostly already-compressed files (zips, mp4, jpg) | No — CPU bottleneck, no win. |
| WAN, text-heavy files (code, logs) | Yes — big wins. |
| Mobile tether / slow uplink | Yes. |
Whole-file mode (skip delta algorithm)
$ rsync -av -W ./ /mnt/fastdisk/ # -W: don't compute deltas. Faster on local copies where the network # isn't the bottleneck. Default for local-to-local since rsync 3.0.
11 Dry run & safety
The cost of being wrong with --delete is real. Be paranoid.
$ rsync -avn --delete --stats -i src/ dst/
The -i output uses a 9-char change indicator. Most useful columns:
| Code | Meaning |
|---|---|
< or > | File transferred to remote / from remote. |
c | Item is being created (didn't exist on dest). |
h | Item is a hard link. |
. | Position has no change in this attribute. |
*deleting | Going to delete this on dest (because of --delete). |
rsync -av --delete src/ dst/ with the wrong trailing slash or a swapped argument will erase the wrong tree. Always dry-run --delete first. Consider --max-delete=N to abort if more than N files would be removed.
$ rsync -av --delete --max-delete=50 src/ dst/ # bails out before doing anything if more than 50 deletions would occur
12 Daemon mode (rsync://)
When the remote side runs rsync as a network service instead of over SSH. Common for public mirrors.
Fetch from a public rsync module
$ rsync -av rsync://mirror.example.org/ubuntu/dists/ ./ubuntu-dists/
List available modules
$ rsync rsync://mirror.example.org/ ubuntu The Ubuntu archive debian The Debian archive fedora Fedora releases
Double-colon syntax (equivalent)
$ rsync -av mirror.example.org::ubuntu/dists/ ./ubuntu-dists/
:: / rsync://) is unencrypted by default. For private data, prefer SSH transport. The daemon protocol is mostly for public, read-only mirrors.
13 Exit codes
Useful for scripts and cron. The full list is in the manpage; these are the ones you'll actually hit.
| Code | Meaning |
|---|---|
0 | Success. |
1 | Syntax / usage error. |
2 | Protocol incompatibility. |
5 | Error starting client-server protocol. |
10 | Error in socket I/O. |
11 | Error in file I/O. |
12 | Error in rsync protocol data stream. |
23 | Partial transfer due to error (some files unreadable, vanished, etc). |
24 | Partial transfer due to vanished source files. Often safe to ignore on busy filesystems. |
30 | Timeout in data send/receive. |
255 | SSH transport failed. |
14 Gotchas & tips
The ones that bite. Pin them above your desk.
src/ ≠ src. (See §03.)
--delete + a wrong path = data loss. Dry-run first. Add --max-delete as a seatbelt.
--exclude=*.log first — rsync never sees the pattern. Always quote: --exclude='*.log'.
/build means "build at the source root"; build means "any directory named build, anywhere".
-o and -g: use -rltD or --no-o --no-g.
-a does not include -H (hard links), -A (ACLs), or -X (xattrs). Add them explicitly when they matter.
-l (in -a) copies them as symlinks. Use -L to dereference (copy the target instead). -K handles symlinks-to-directories on the receiver.
--modify-window=2 (or --size-only) to avoid endless re-transfers.
--info=progress2 shows an aggregate progress bar across the whole transfer, instead of per-file (--progress). Much nicer for huge transfers.
15 Flag reference
The expanded list. Alphabetized by short flag, then by long flag.
| Flag | Long form | What it does |
|---|---|---|
-a | --archive | Archive mode (-rlptgoD). |
-A | --acls | Preserve ACLs. |
-b | --backup | Make backups of overwritten/deleted files. |
--backup-dir=DIR | Where backups go (relative to dest). | |
--bwlimit=RATE | Limit socket I/O bandwidth. | |
-c | --checksum | Compare by checksum, not size+mtime. |
--copy-links / -L | Follow symlinks; copy target. | |
-D | Same as --devices --specials. | |
--delete | Delete files in dest missing from source. | |
--delete-excluded | Also delete files that match excludes. | |
--dry-run / -n | Don't actually do anything. | |
-e CMD | --rsh=CMD | Set the remote shell (e.g. 'ssh -p 22'). |
--exclude=PATTERN | Skip matching paths. | |
--exclude-from=FILE | Read exclude patterns from FILE. | |
--filter=RULE | Advanced include/exclude rule. | |
-g | --group | Preserve group. |
-h | --human-readable | K/M/G units in output. |
-H | --hard-links | Preserve hard links. |
-i | --itemize-changes | Output per-file change codes. |
--include=PATTERN | Don't skip these paths. | |
--info=FLAGS | Fine-grained output verbosity (e.g. progress2). | |
--link-dest=DIR | Hardlink unchanged files from DIR. | |
-l | --links | Copy symlinks as symlinks. |
--max-delete=N | Abort if more than N deletions would occur. | |
--max-size=SIZE | Skip files larger than SIZE. | |
--min-size=SIZE | Skip files smaller than SIZE. | |
--modify-window=N | Allow N-second mtime fuzz. | |
-o | --owner | Preserve owner (root only). |
-p | --perms | Preserve permissions. |
-P | = --partial --progress. | |
--partial | Keep partial files on failure. | |
--progress | Show per-file progress. | |
-r | --recursive | Recurse into directories. |
--remove-source-files | Delete source files after transfer. | |
--rsync-path=PATH | What to run on the remote (e.g. "sudo rsync"). | |
--size-only | Skip mtime check; compare size only. | |
--stats | Print transfer statistics. | |
-t | --times | Preserve modification times. |
-u | --update | Skip files newer on receiver. |
-v | --verbose | Verbose output (repeatable). |
-W | --whole-file | Don't compute deltas; copy whole files. |
-x | --one-file-system | Don't cross filesystem boundaries. |
-X | --xattrs | Preserve extended attributes. |
-z | --compress | Compress during transfer. |
--compress-level=N | 0–9 (default depends on algorithm). |