rsync Explained: Flags, Trailing Slashes, and Not Deleting Your Data

rsync Explained: Flags, Trailing Slashes, and Not Deleting Your Data

rsync copies files and synchronises directories. Its distinguishing feature is that on repeat runs it transfers only what changed, which makes it the default tool for backups, mirrors, and moving large trees between machines.

Our scp, sftp, and rsync comparison covers where each fits. This is rsync specifically.

The trailing slash

Address this first, because it causes more rsync accidents than anything else.

A trailing slash on the source means “the contents of this directory”. No trailing slash means “this directory”.

# copies the CONTENTS of src into dst
rsync -av src/ dst/
# result: dst/file1, dst/file2

# copies src ITSELF into dst
rsync -av src dst/
# result: dst/src/file1, dst/src/file2

The destination trailing slash is ignored. Only the source one matters.

This interacts catastrophically with --delete. If you meant to sync contents and omitted the slash, rsync creates a subdirectory, sees that everything else at the destination is absent from the source, and deletes it.

Archive mode

-a is what you want almost always.

rsync -av /source/ /dest/

-a expands to -rlptgoD:

FlagMeaning
-rRecursive
-lCopy symlinks as symlinks
-pPreserve permissions
-tPreserve modification times
-gPreserve group
-oPreserve owner, needs root
-DPreserve device and special files

-t matters more than it looks. Without preserved timestamps, rsync cannot use its quick check and re-transfers everything every run.

What -a does not include:

-H    preserve hard links
-A    preserve ACLs
-X    preserve extended attributes
-S    handle sparse files efficiently

For a full system backup you want -aHAX. Missing -A and -X silently drops ACLs and SELinux contexts, which produces a backup that restores into a system that does not work properly.

How the delta algorithm works

The clever part, and the reason rsync exists.

For a file that exists at both ends, the receiver splits its copy into fixed-size blocks and computes two checksums per block: a fast rolling checksum and a strong MD5-class hash. It sends those to the sender.

The sender rolls a checksum across its version of the file byte by byte. Where the rolling checksum matches a known block, it verifies with the strong hash. Matching blocks are referenced rather than sent; non-matching bytes are transmitted literally.

The result is that changing 1KB in the middle of a 1GB file transfers roughly 1KB plus the checksum exchange, not 1GB.

The rolling checksum is what allows this to work when data has been inserted rather than overwritten, because block boundaries shift and the algorithm still finds the matches.

For local copies rsync disables this by default. Reading both files to compute checksums costs more disk I/O than just writing the new file. Force it with --no-whole-file if you are copying to a network filesystem that looks local.

Everyday commands

Progress you can read

rsync -av --info=progress2 --human-readable src/ dst/

--info=progress2 shows overall progress for the whole transfer. The older --progress shows per-file progress, which is useless when copying fifty thousand small files.

Over SSH

rsync -av -e ssh src/ user@host:/path/
rsync -av -e "ssh -p 2222 -i ~/.ssh/backup_key" src/ user@host:/path/

SSH is the default transport, so -e ssh is redundant unless you are passing options.

Compression helps on slow links and hurts on fast ones, because the CPU becomes the bottleneck:

rsync -avz src/ user@host:/path/       # slow link
rsync -av src/ user@host:/path/        # fast LAN, skip -z

Already-compressed data (video, JPEG, archives) does not benefit from -z at all.

Excluding things

rsync -av --exclude='*.tmp' --exclude='node_modules/' src/ dst/

# a list from a file, which is more maintainable
rsync -av --exclude-from='exclude.txt' src/ dst/
# exclude.txt
.git/
node_modules/
*.log
/var/cache/

A leading / anchors the pattern to the transfer root rather than matching at any depth.

Mirroring with —delete

# ALWAYS do this first
rsync -av --delete --dry-run src/ dst/

# then, having read the output
rsync -av --delete src/ dst/

--dry-run (or -n) shows exactly what would happen. On a --delete run, read the output rather than skimming it.

Safer variants:

# move deletions aside instead of removing them
rsync -av --delete --backup --backup-dir=/tmp/deleted src/ dst/

# abort if more than 50 files would be deleted
rsync -av --delete --max-delete=50 src/ dst/

--max-delete is a genuinely good safety net for a scripted job. If something goes wrong with the source, the job fails loudly rather than emptying the destination quietly.

This produces snapshot-style backups where unchanged files cost no extra space:

rsync -aHAX --delete \
  --link-dest=/backups/2026-09-13 \
  /data/ /backups/2026-09-14/

--link-dest hard-links unchanged files to the previous backup instead of copying them. Each dated directory looks like a complete backup and only changed files consume space.

du -sh /backups/*      # each appears full size
du -sh /backups/       # total is far smaller

The caveat: hard links mean the copies share inodes, so this protects against deletion and corruption of the source, not against a bad disk holding the backups. It is also not a substitute for an off-site copy.

For most people, restic or Borg is the better backup tool, because they add encryption, deduplication across the whole repository, and a proper snapshot model. rsync with --link-dest is worth knowing because it needs nothing but rsync.

Verification

By default rsync decides a file needs transferring based on size and modification time, which is fast and occasionally wrong.

# checksum every file, slow but certain
rsync -avc src/ dst/

# verify without transferring
rsync -avcn --delete src/ dst/

Worth doing after a migration you care about. Not worth doing nightly.

Bandwidth and interruptions

rsync -av --bwlimit=5000 src/ user@host:/path/    # 5 MB/s cap
rsync -av --partial --append-verify src/ dst/      # resumable large files

--partial keeps incomplete files so a resumed transfer continues mid-file. Without it, an interrupted 40GB file starts over.

Interrupted transfers resume naturally in general: rerun the same command and rsync skips what is already correct.

The mistakes worth avoiding

Missing trailing slash with --delete. Covered above, and it is the one that destroys data.

Forgetting -A and -X on a system backup. ACLs and SELinux contexts vanish silently.

Running without -n on a --delete command you have not run before. One extra command, and it catches the typo.

Assuming -a preserves ownership as a normal user. -o requires root. Without it, everything arrives owned by the running user, and a restore produces a system with wrong ownership throughout.

Using -z on a fast LAN. Slower, not faster.

Frequently Asked Questions

What does the trailing slash do in rsync?

A trailing slash on the source means copy the contents of this directory, while no trailing slash means copy the directory itself. So rsync src/ dst puts the files directly in dst, and rsync src dst creates dst/src containing them. It only matters on the source; the destination trailing slash is ignored.

What does the -a flag actually include?

Archive mode is shorthand for -rlptgoD, which means recursive, copy symlinks as symlinks, preserve permissions, times, group, owner, and device and special files. It does not include -H for hard links, -A for ACLs, or -X for extended attributes, which you add separately when you need them.

Is rsync —delete dangerous?

It can be, because it deletes anything in the destination that is not in the source. The danger is usually a typo in the source path or a missing trailing slash, which can make rsync think the source is nearly empty and delete almost everything at the destination. Always run with —dry-run first.

Does rsync really only transfer changed parts of files?

Over a network, yes, using a rolling checksum to identify matching blocks and send only the differences. For local copies rsync skips the delta algorithm by default because reading both files costs more than simply copying, unless you pass —no-whole-file.

How do I resume an interrupted rsync transfer?

Just run the same command again. rsync compares source and destination and transfers only what is missing or changed, so it resumes naturally. Add —partial to keep partially transferred files so a large single file resumes mid-file rather than restarting.

Should I use rsync or scp for copying files?

rsync for almost everything. It is faster on repeat transfers, resumable, can preserve permissions and ownership properly, and shows progress usefully. scp is fine for a single small file and has been deprecated in OpenSSH in favour of sftp for its underlying protocol.