┌──────────────────────────────────────────────────────────────┐

rsync(1)

└──────────────────────────────────────────────────────────────┘
$ a fast, versatile, remote (and local) file-copying tool  ·  comprehensive cheatsheet

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.

FlagMeaning
-aArchive mode. Expands to -rlptgoD: recursive, symlinks-as-symlinks, perms, times, group, owner, devices/specials.
-v / -vvVerbose; add more vs for more noise.
-zCompress during transfer. Helpful over slow links, useless over LAN/local.
-PShort for --partial --progress. Resume partials & show progress.
-hHuman-readable numbers (K/M/G).
-n / --dry-runShow what would happen. Use it. Every time.
--deleteDelete files in DEST that no longer exist in SRC. Makes it a true mirror.
--exclude=PATSkip paths matching PAT (e.g. '.git', '*.log').
-e sshUse ssh as the transport (default for remote). Customize: -e 'ssh -p 2222'.
-HPreserve hard links. Required for some backup setups.
-A, -XPreserve ACLs, extended attributes.
-uSkip files newer on the receiver.
-cCompare by checksum, not size+mtime. Slow but bulletproof.
--statsPrint a summary block at the end.
[ TIP ] Memorize -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
[ NOTE ] Trailing slash on the destination doesn't matter. Only the source's trailing slash changes behavior.

04 Source & destination paths

rsync accepts four shapes of path. Mix at will.

PatternExample
Local → Localrsync -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)
Daemonrsync -av rsync://host/module/path/ ./
[ NOTE ] Remote-to-remote isn't a direct copy. Both sides go through your machine. For server-to-server, run rsync on one of the servers instead.

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/
[ TIP ] Stick your SSH options in ~/.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

PatternMatches
*.logAny .log file at any depth.
/build/A build/ directory at the source root only (leading / anchors).
cache/Any directory named cache at any depth.
**/tmpA 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/
[ NOTE ] Backups should usually pull from the source, not be pushed to a backup host. If the source is compromised, push-style backups can poison the backup too.

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/
[ TIP ] --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

SituationUse -z?
Local-to-local copyNo — 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 uplinkYes.

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:

CodeMeaning
< or >File transferred to remote / from remote.
cItem is being created (didn't exist on dest).
hItem is a hard link.
.Position has no change in this attribute.
*deletingGoing to delete this on dest (because of --delete).
[ DANGER ] 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/
[ NOTE ] Daemon mode (:: / 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.

CodeMeaning
0Success.
1Syntax / usage error.
2Protocol incompatibility.
5Error starting client-server protocol.
10Error in socket I/O.
11Error in file I/O.
12Error in rsync protocol data stream.
23Partial transfer due to error (some files unreadable, vanished, etc).
24Partial transfer due to vanished source files. Often safe to ignore on busy filesystems.
30Timeout in data send/receive.
255SSH transport failed.

14 Gotchas & tips

The ones that bite. Pin them above your desk.

[ 01 ] The trailing slash. Always. src/src. (See §03.)
[ 02 ] --delete + a wrong path = data loss. Dry-run first. Add --max-delete as a seatbelt.
[ 03 ] Quotes matter on globs. Without quotes, your shell expands --exclude=*.log first — rsync never sees the pattern. Always quote: --exclude='*.log'.
[ 04 ] Anchored vs unanchored patterns. /build means "build at the source root"; build means "any directory named build, anywhere".
[ 05 ] Preserving owner/group requires root on the receiver, or you'll get warnings. For non-root deploys, drop -o and -g: use -rltD or --no-o --no-g.
[ 06 ] -a does not include -H (hard links), -A (ACLs), or -X (xattrs). Add them explicitly when they matter.
[ 07 ] Symlinks: -l (in -a) copies them as symlinks. Use -L to dereference (copy the target instead). -K handles symlinks-to-directories on the receiver.
[ 08 ] Cross-filesystem mtime fuzz. FAT32, SMB, some object stores round mtimes. Use --modify-window=2 (or --size-only) to avoid endless re-transfers.
[ TIP ] --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.

FlagLong formWhat it does
-a--archiveArchive mode (-rlptgoD).
-A--aclsPreserve ACLs.
-b--backupMake backups of overwritten/deleted files.
--backup-dir=DIRWhere backups go (relative to dest).
--bwlimit=RATELimit socket I/O bandwidth.
-c--checksumCompare by checksum, not size+mtime.
--copy-links / -LFollow symlinks; copy target.
-DSame as --devices --specials.
--deleteDelete files in dest missing from source.
--delete-excludedAlso delete files that match excludes.
--dry-run / -nDon't actually do anything.
-e CMD--rsh=CMDSet the remote shell (e.g. 'ssh -p 22').
--exclude=PATTERNSkip matching paths.
--exclude-from=FILERead exclude patterns from FILE.
--filter=RULEAdvanced include/exclude rule.
-g--groupPreserve group.
-h--human-readableK/M/G units in output.
-H--hard-linksPreserve hard links.
-i--itemize-changesOutput per-file change codes.
--include=PATTERNDon't skip these paths.
--info=FLAGSFine-grained output verbosity (e.g. progress2).
--link-dest=DIRHardlink unchanged files from DIR.
-l--linksCopy symlinks as symlinks.
--max-delete=NAbort if more than N deletions would occur.
--max-size=SIZESkip files larger than SIZE.
--min-size=SIZESkip files smaller than SIZE.
--modify-window=NAllow N-second mtime fuzz.
-o--ownerPreserve owner (root only).
-p--permsPreserve permissions.
-P= --partial --progress.
--partialKeep partial files on failure.
--progressShow per-file progress.
-r--recursiveRecurse into directories.
--remove-source-filesDelete source files after transfer.
--rsync-path=PATHWhat to run on the remote (e.g. "sudo rsync").
--size-onlySkip mtime check; compare size only.
--statsPrint transfer statistics.
-t--timesPreserve modification times.
-u--updateSkip files newer on receiver.
-v--verboseVerbose output (repeatable).
-W--whole-fileDon't compute deltas; copy whole files.
-x--one-file-systemDon't cross filesystem boundaries.
-X--xattrsPreserve extended attributes.
-z--compressCompress during transfer.
--compress-level=N0–9 (default depends on algorithm).