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.
| Command | What 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.
| Command | What 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.
| Flag | What 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 .
| Command | What 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).
| Command | What 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.
| Command | What 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
| Command | What 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
| Command | What 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.
| Command | Result |
|---|---|
| , | |
| , | |
| 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/latestDeploy 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
| rsync | scp | |
|---|---|---|
| Second run of the same copy | Sends only changes | Sends everything again |
| Resume after a drop | Start over | |
| Delete extra files at the destination | No | |
| Exclude patterns | Yes | No |
| Installed by default | Usually, not on minimal images or stock Windows | Wherever 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.
| Code | Meaning |
|---|---|
| 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.