Cheat Sheet

rsync Commands

rsync copies files and directories locally or over SSH and only sends the parts that changed, which makes it the tool for backups, deployments, and moving a server. This sheet covers the commands and flags people look up: the -avz set, remote copies with a port and key, dry runs, exclude patterns, delete for mirroring, resuming, and what the exit codes mean.

Last updated August 29, 2026

Basic Copy

(archive) is the flag you almost always want: recursive, keeps permissions, times, owner, group, symlinks, and devices. The rsync command builder assembles one of these interactively.

CommandWhat it does
Copy the contents of into , showing each file
Copy the directory itself, so you end up with
Copy one file
With a per-file progress bar
With one overall progress line for the whole transfer
Dry run: show what would be copied, change nothing
Print a summary of bytes sent and files transferred
Skip files that are newer on the destination
Only copy files that do not exist on the destination
Only update files that already exist there
Create missing destination directories (rsync 3.2.3 and newer)

with no prints nothing on success, which is what you want in cron.

Over SSH

Remote paths are . rsync must be installed at both ends. It runs over SSH by default, so keys, aliases, and agent forwarding all apply; see the SSH cheat sheet.

CommandWhat it does
Local to remote (push)
Remote to local (pull)
Non-standard SSH port
Specific key
Port and key
Use a Host alias from , which holds the port and key
Write as root on the remote side (needs passwordless sudo for rsync)
Quote remote paths with spaces
Same, without the shell interpreting the remote path ()
Several sources from the same host
Give up if no data moves for 60 seconds

Copy between two remote servers: rsync cannot take two remote paths in one command. Log into one and push to the other, or forward your agent so the first server can authenticate to the second.

ssh -A user@server1 'rsync -avz /var/www/ user@server2:/var/www/'

A connection that fails with exit code 255 is an SSH problem, not an rsync one. See Connection refused and Permission denied (publickey).

Flags Explained

Paste any command into the rsync flag explainer to decode it.

FlagWhat it does
, Same as : recursive, symlinks as symlinks, permissions, times, group, owner, devices
List each file as it is copied ( for more, for none)
Compress in transit; helps over slow links, wastes CPU on a LAN or for already-compressed files
Human-readable sizes in output
: keep partial files for resuming, and show progress
, Show what would happen without doing it
, Skip files that are newer on the destination
, Compare by checksum instead of size and time (slow, but catches same-size edits)
Recursive; part of
Copy symlinks as symlinks; part of
Copy the file a symlink points to instead of the link
, , , Preserve permissions, times, owner, group; all part of
Preserve hard links (not in )
, Preserve ACLs and extended attributes (not in )
Stay on one filesystem; skip mounted volumes
Itemize changes: a code per file showing what differed
The remote shell command and its options
Remove destination files that no longer exist in the source
Skip matching files
Cap bandwidth, in KiB/s by default ( for 5 MiB/s)
One progress line for the whole run
Transfer summary at the end
Write what happened to a file

is the standard remote copy, the standard local one, and when the link might drop.

Dry Run

Run every new rsync command with first, and always before .

CommandWhat it does
List what would be transferred
Also list what would be deleted, shown as
Itemized dry run: one line per change with a code like
Count and total size without copying

In itemized output, is a file being sent, a directory being created, a deletion, and the dots after the type mark which attributes changed ( size, time, permissions, owner).

Exclude and Include

Patterns match relative to the top of the transfer. A trailing matches directories only, a leading anchors the pattern to the transfer root, and does not cross a ( does).

CommandWhat it does
Skip every at any depth
Skip files by extension
Skip a directory (trailing slash means directories only)
Skip only the top-level , not
Several patterns; repeat the flag
Same, using bash brace expansion (no spaces inside the braces)
Patterns from a file, one per line
Copy only files, keeping the directory tree
Only files in the top level (no include, so no descent)
As above, but prune empty directories
Copy only the paths listed in a file, relative to
Skip files over 100 MiB ( for the other end)
Also delete files on the destination that an exclude now covers

Include and exclude rules are checked in order and the first match wins, which is why comes before . An exclude that "does not work" is almost always anchored wrong: does not match when the transfer root is , but does.

Delete and Mirror

makes the destination match the source exactly, including removing files. Combined with a wrong path or a missing trailing slash it can empty a directory, so dry run first.

CommandWhat it does
Mirror: copy changes and remove anything not in
Preview the deletions first
Refuse to delete more than 100 files (exit code 25 when it trips)
Delete after the transfer, not before; safer for a live site
Delete first, when the destination is short on space
Move deleted and overwritten files into a dated directory instead of losing them
Delete each source file after it is copied (a move); empty source directories remain
Mirror but leave the destination's alone (excludes are protected from deletion by default)

only deletes inside directories that are part of the transfer. (no trailing slash) syncs and never touches the rest of .

Progress and Resume

CommandWhat it does
Keep partial files and show per-file progress
One overall percentage, speed, and time remaining
Keep partials in a hidden directory rather than in place
Run again after an interruption; partial files continue from where they stopped
Append to a partially transferred file and checksum the result; for single large files only
Cap at about 5 MB/s so you do not saturate the link
Cap at 50 MiB/s
Abort if the transfer stalls for two minutes
Run in the background and survive logout

For a transfer that takes hours, start it inside or so a dropped SSH session does not kill it; see the tmux cheat sheet. If the SSH session itself keeps dropping, see Broken pipe.

Permissions and Ownership

CommandWhat it does
Preserve permissions, times, group; owner too when running as root
Set a fixed owner and group on everything copied (needs root at the destination)
Set directories to 755 and files to 644 as they land
Copy without permission or ownership bits (exFAT, SMB shares)
Same for owner and group only, keeping permissions
Recursive, links, and times, but no permissions or ownership at all
Keep UID and GID numbers instead of mapping names (full system copies)
Also copy ACLs and extended attributes
Compare by size only, for filesystems that cannot store exact times
Treat times within one second as equal (FAT stores 2-second stamps)

Preserving owner means rsync on the receiving end runs as root, either because you are root or via . Otherwise files are owned by the connecting user and rsync prints nothing about it.

Trailing Slash

The one rsync rule people get wrong. A trailing slash on the source means "the contents of", no slash means "the directory itself". The destination's slash makes no difference.

CommandResult
,
,
Same as the first row
Same as , occasionally seen in scripts
The contents of into
Creates

Think of as including hidden files, and as "put a copy of this directory in there".

Common Recipes

Back up a home directory to an external drive:

rsync -avh --delete --exclude '.cache/' --exclude 'node_modules/' ~/ /Volumes/Backup/home/

Mirror a website from a server, keeping a rotating set of dated snapshots that share unchanged files via hard links:

rsync -avz --delete --link-dest=/backup/www/latest user@host:/var/www/ /backup/www/$(date +%F)/
ln -sfn /backup/www/$(date +%F) /backup/www/latest

Deploy a build, excluding what should not ship, then reload:

rsync -avz --delete --exclude '.git' --exclude '.env' --exclude 'node_modules' \
  ./dist/ deploy@host:/var/www/app/ && ssh deploy@host 'sudo systemctl reload nginx'

Copy only files changed in the last day:

find src -type f -mtime -1 -printf '%P\n' | rsync -av --files-from=- src/ dest/

Sync two directories both ways (run once in each direction; rsync is one-way, keeps the newer copy):

rsync -avu a/ b/ && rsync -avu b/ a/

Move a whole server's data directory to a new host over a slow link, resumable:

rsync -avzP --bwlimit=20m -e "ssh -p 2222" /srv/data/ root@newhost:/srv/data/

Clone a disk or root filesystem to another mounted volume, staying off , , and other mounts:

sudo rsync -aAXHvx --numeric-ids --exclude={'/dev/*','/proc/*','/sys/*','/tmp/*','/run/*','/mnt/*','/media/*','/lost+found'} / /mnt/newdisk/

rsync vs scp

rsyncscp
Second run of the same copySends only changesSends everything again
Resume after a dropStart over
Delete extra files at the destinationNo
Exclude patternsYesNo
Installed by defaultUsually, not on minimal images or stock WindowsWherever SSH is
One file, once (slightly quicker to type)

OpenSSH's has used the SFTP protocol under the hood since 9.0, which fixed its security problems but did not add any of the above.

Exit Codes

rsync reports its result in the exit code ( right after it runs), which is what a cron job or a script should check.

CodeMeaning
Success
Syntax or usage error; a flag is wrong or unsupported by the remote rsync
Protocol incompatibility; very old rsync on one end
Errors selecting input files; usually a source path that does not exist
Error starting the client-server protocol
Socket I/O error; the connection dropped or the remote host closed it
File I/O error; disk full or a read failure
Error in the rsync protocol data stream; remote disk full, rsync missing on the remote, or a login script printing to stdout
Errors with program diagnostics
Killed by a signal (Ctrl+C)
Partial transfer due to error; some files skipped, usually permissions
Partial transfer due to vanished source files; benign for live directories
Stopped by
Timeout in data send or receive ()
Timeout waiting for the daemon connection
SSH failed to connect; not an rsync error at all

Gotchas

  • with the wrong trailing slash or a mistyped destination removes real files. first, every time.
  • rsync must exist on the remote host. from the far end and exit code 12 or 127 mean there, not here.
  • A on the remote that prints something (a banner, a , an unguarded ) corrupts the protocol stream and gives or code 12. Guard it with at the top.
  • macOS ships an old rsync 2.6.9, and macOS 15 replaced it with openrsync, which lacks flags such as . gives you 3.x; on Windows, rsync comes with WSL, Cygwin, or MSYS2, not with Git Bash by default.
  • slows down a LAN copy and does nothing for files that are already compressed (images, video, archives). Use it over the internet, drop it locally.
  • Copying to a drive formatted exFAT, FAT32, or NTFS from Linux recopies everything each run because the filesystem cannot store the same times and permissions. Add or .
  • means the remote path is missing its colon ( instead of ) or you gave two remote paths in one command.
  • Changing only a file's permissions or owner does not change its size or time, so a plain run does not resend it but does update the attributes. Content edits that keep the same size and time (rare, but and some build tools do it) need to be noticed.

rsync Cheat Sheet FAQ

Does rsync copy or move files?
It copies. The source is untouched after a run, and running the same command again sends only what changed since. To get a move, add --remove-source-files, which deletes each file from the source after it has been transferred and verified. That flag leaves the now-empty directories behind, so follow it with find src -type d -empty -delete if you want them gone. There is no flag that removes source directories in one step.
Does rsync copy permissions and ownership?
With -a, yes: it preserves permissions, modification times, group, and owner (plus symlinks and device files). Two limits apply. Owner and group are only set when rsync runs as root on the receiving side, otherwise files land owned by the user you connected as; use --rsync-path='sudo rsync' or --chown=user:group to control that. And a destination that does not support Unix permissions (an exFAT drive, a Windows share, some NAS mounts) makes every run recopy files; use --no-perms --no-owner --no-group or --size-only there. Without -a or -t, times are not preserved and rsync thinks every file changed next time.
Does rsync copy symbolic links?
With -a it copies a symlink as a symlink (the -l part of -a), so the link is recreated on the destination pointing at the same path, which may or may not exist there. Add -L (--copy-links) to copy the file the link points at instead, --copy-unsafe-links to only dereference links that point outside the tree you are copying, or --safe-links to skip those. Without -a or -l, rsync skips symlinks and prints skipping non-regular file for each one.
What is the difference between rsync and scp?
scp copies everything you name every time. rsync compares source and destination first and sends only new or changed files, and only the changed parts of a large file, so a second run of a 50 GB directory takes seconds. rsync also resumes an interrupted transfer (-P), excludes patterns, mirrors with --delete, and preserves more metadata. scp wins on one thing: it is on every box, whereas rsync must be installed at both ends. For a one-off copy of a single file either is fine; for anything you will run twice, use rsync.
What does rsync error code 23 mean?
Code 23 is a partial transfer: rsync finished, but some files or attributes were skipped, almost always because of a permission denied on the source or destination, or because it could not set an owner or a time. Scroll up for the specific rsync: ... failed lines. Code 24 is the benign cousin, files vanished on the source during the run. Code 255 is not from rsync at all; it means ssh could not connect, so test ssh user@host on its own. Code 12 (error in rsync protocol data stream) usually means the destination disk filled up, rsync is missing on the remote side, or the remote shell prints something on login, which corrupts the stream.

Related cheat sheets