SFTP and SCP copy files. rsync synchronises them. The difference is
efficiency: rsync compares the source and destination before transferring anything,
then sends only the bytes that actually changed. Copy a 500 MB directory after
editing one small file — SFTP sends 500 MB, rsync sends a few kilobytes.
This chapter covers how rsync works, local directory syncing, and the flags
you'll reach for in almost every rsync command. Remote transfers over SSH come
in Chapter 8.
How rsync Works
1
Build a file list
rsync walks the source directory and records every file's name, size, modification time, and permissions.
2
Compare with the destination
It does the same for the destination and compares the two lists. Files that match on size and modification time are considered up to date — no transfer needed.
3
Block-level delta transfer
For files that differ, rsync doesn't send the whole file. It splits the destination file into fixed-size blocks, checksums each block, and only sends the blocks from the source that don't already exist at the destination.
4
Reconstruct on the destination
The destination reassembles the file from existing blocks (unchanged parts) and the newly received blocks (changed parts). The result is a perfect copy of the source.
5
Set metadata
Permissions, ownership, and timestamps are applied to match the source — assuming you passed the right flags (archive mode handles this automatically).
Result: on a first run, rsync behaves like a full copy.
On every subsequent run over the same source and destination, it does the
minimum possible work — which is why it's the standard tool for backups and
deploys.
Basic Syntax
# rsync [OPTIONS] SOURCE DESTINATION
rsync -avsource/destination/# SOURCE and DESTINATION can be:# a local path: /home/philip/docs/# a remote path: server:/home/philip/docs/ (Chapter 8)
The Essential Flags
-a
--archive
The single most important flag. Enables recursive copy and preserves permissions, ownership, timestamps, symlinks, and device files. Equivalent to -rlptgoD. Use it on almost every rsync command.
-v
--verbose
Prints the name of each file transferred. Add a second -v (-vv) for even more detail. Essential when learning; drop it in cron jobs where the output goes to waste.
-n
--dry-run
Simulates the sync without actually transferring or deleting anything. Combine with -v to see exactly what would happen. Always run a dry-run before using --delete for the first time.
-z
--compress
Compresses data during transfer. Helpful on slow connections or for text-heavy files (HTML, CSS, JS, logs). Skip it for already-compressed formats like JPG, MP4, or zip — compression adds CPU overhead for no gain.
--delete
(no short form)
Deletes files at the destination that no longer exist at the source. What makes rsync a true mirror rather than just a copy. Without it, deleted source files accumulate at the destination forever.
--progress
(no short form)
Shows a per-file progress bar with transfer speed and time remaining. Useful for large files. For a single summary at the end instead, use --info=progress2.
--exclude
(no short form)
Skip files or directories matching a pattern. Accepts shell globs. Can be specified multiple times: --exclude='*.log' --exclude='.git'. Patterns match relative to the source root.
-h
--human-readable
Prints sizes in human-readable format (KB, MB, GB) rather than bytes. Combine with --progress or --stats.
The Trailing Slash Rule
rsync's most common source of confusion: whether you put a trailing slash on
the source changes what gets synced.
With trailing slash — sync contents
rsync -av source/ dest/
Copies the contents of source/
directly into dest/:
dest/
├── file1.txt
├── file2.txt
└── subdir/
The trailing slash only matters on the source, not the destination.rsync -av source/ dest and rsync -av source/ dest/
behave identically. The rule only applies to the left side.
Local Sync — Practical Examples
Mirror one directory to another
philip@debian — basic local sync
# Mirror ~/projects/website to ~/backups/websitephilip@debian:~$rsync -av ~/projects/website/ ~/backups/website/sending incremental file listindex.htmlstyle.cssjs/app.jsimages/logo.pngsent 248,291 bytes received 89 bytes 496,760.00 bytes/sectotal size is 247,820 speedup is 1.00
Second run — only changed files transfer
philip@debian — subsequent run after editing one file
# Only index.html was changed — only index.html transfersphilip@debian:~$rsync -av ~/projects/website/ ~/backups/website/sending incremental file listindex.htmlsent 2,341 bytes received 35 bytes 4,752.00 bytes/sectotal size is 247,820 speedup is 104.48 ↑ speedup factor: rsync skipped 104× worth of data
Dry-run first — always before --delete
philip@debian — dry-run to preview changes
# -n = dry run. Shows exactly what WOULD happen without doing anythingphilip@debian:~$rsync -avn --delete ~/projects/website/ ~/backups/website/sending incremental file listindex.htmldeleting old-page.htmlsent 1,823 bytes received 27 bytes 3,700.00 bytes/sectotal size is 247,820 speedup is 134.78(DRY RUN)← nothing was actually changed# Looks right — run for realphilip@debian:~$rsync -av --delete ~/projects/website/ ~/backups/website/
--delete permanently removes files at the destination.
There is no recycle bin. If you accidentally flip source and destination,
you could delete the files you meant to keep. Always -n dry-run
first when using --delete until the pattern is muscle memory.
Reading rsync Output
With -v and --itemize-changes (-i),
rsync prints an 11-character status code before each filename showing exactly
what changed:
philip@debian — itemized output
philip@debian:~$rsync -avi --delete source/ dest/sending incremental file list>f+++++++++ newfile.txt← new file, will be created>f.st...... index.html← size and time changed.f......... unchanged.css← no change, skipped*deleting removed.html← deleted from source, removed at dest
>f
File transferred (sent to destination)
cd
Directory created
*d
Deletion (file or directory)
.f
File unchanged / skipped
Excluding Files and Directories
philip@debian — exclusion patterns
# Exclude a specific directoryphilip@debian:~$rsync -av --exclude='.git' source/ dest/# Exclude multiple patternsphilip@debian:~$rsync -av \
--exclude='.git' \
--exclude='node_modules/' \
--exclude='*.log' \
--exclude='*.tmp' \
source/ dest/# Load exclusions from a file — one pattern per linephilip@debian:~$rsync -av --exclude-from='.rsyncignore' source/ dest/# .rsyncignore file contents:.gitnode_modules/*.log*.tmp.DS_Store
Patterns are matched against the path relative to the source root.--exclude='logs/' excludes any directory named logs
anywhere in the tree. --exclude='/logs/' (with leading slash)
only excludes a logs directory at the top level of the source.
Backup with Timestamps — a Simple Local Backup
A useful pattern: back up a directory to a timestamped folder so you keep
a history of snapshots. rsync's --backup and
--backup-dir flags move overwritten files to a separate location
rather than just overwriting them.
philip@debian — timestamped backup
# Store changed/deleted files in a dated subfolder before overwritingphilip@debian:~$rsync -av \
--backup \
--backup-dir=~/backups/$(date +%Y-%m-%d) \
~/projects/website/ \
~/backups/current/sending incremental file listindex.htmlsent 2,341 bytes received 35 bytes 4,752.00 bytes/sec# Result: ~/backups/current/ has the latest version
# ~/backups/2026-06-17/ has the previous version of index.html
Progress and Stats
philip@debian — watching progress
# --progress: per-file progress barphilip@debian:~$rsync -avh --progress source/ dest/large-video.mp4 384,000,000 100% 12.50MB/s 0:00:29 (xfer#3, to-check=0/12)# --info=progress2: single overall progress line (less noisy)philip@debian:~$rsync -ah --info=progress2 source/ dest/ 248,291 100% 1.23MB/s 0:00:00 (xfer#4, to-check=0/12)# --stats: summary at the endphilip@debian:~$rsync -av --stats source/ dest/Number of files: 12 (reg: 10, dir: 2)Number of created files: 0Number of deleted files: 0Number of regular files transferred: 1Total file size: 247,820 bytesTotal transferred file size: 2,341 bytesLiteral data: 2,341 bytesMatched data: 0 bytes
Quick Reference
Command / flag
What it does
rsync -av src/ dest/
Archive + verbose — the standard starting point
rsync -avn src/ dest/
Dry run — preview without changing anything
rsync -av --delete src/ dest/
Mirror — delete at dest what's gone from src
rsync -avz src/ dest/
With compression — for slow links and text files
rsync -avh --progress src/ dest/
Human-readable sizes + per-file progress bar
rsync -av --stats src/ dest/
Summary stats at the end of the run
--exclude='pattern'
Skip files matching a glob pattern
--exclude-from=file
Load exclude patterns from a text file
--backup --backup-dir=path
Move overwritten files to a dated backup directory
-i / --itemize-changes
Show an 11-character status code for each file
rsync --version
Check installed rsync version
rsync --help
Full flag reference
Next — Chapter 8: rsync over SSH.
Everything from this chapter applies directly to remote transfers — the only
difference is the destination format. Chapter 8 covers push and pull syncs
to a remote server, bandwidth limiting, the --rsh flag,
excluding large directories, and practical one-liners for website deploys
and server backups.