rsync Basics

Chapter 7 — rsync Basics

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 -av source/ 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/
Think: "copy what's inside source"
Without trailing slash — sync directory
rsync -av source dest/ Copies the source directory itself inside dest/: dest/ └── source/ ├── file1.txt ├── file2.txt └── subdir/
Think: "copy the source folder"
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/website philip@debian:~$ rsync -av ~/projects/website/ ~/backups/website/ sending incremental file list index.html style.css js/app.js images/logo.png sent 248,291 bytes received 89 bytes 496,760.00 bytes/sec total 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 transfers philip@debian:~$ rsync -av ~/projects/website/ ~/backups/website/ sending incremental file list index.html sent 2,341 bytes received 35 bytes 4,752.00 bytes/sec total 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 anything philip@debian:~$ rsync -avn --delete ~/projects/website/ ~/backups/website/ sending incremental file list index.html deleting old-page.html sent 1,823 bytes received 27 bytes 3,700.00 bytes/sec total size is 247,820 speedup is 134.78 (DRY RUN) ← nothing was actually changed # Looks right — run for real philip@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 directory philip@debian:~$ rsync -av --exclude='.git' source/ dest/ # Exclude multiple patterns philip@debian:~$ rsync -av \ --exclude='.git' \ --exclude='node_modules/' \ --exclude='*.log' \ --exclude='*.tmp' \ source/ dest/ # Load exclusions from a file — one pattern per line philip@debian:~$ rsync -av --exclude-from='.rsyncignore' source/ dest/ # .rsyncignore file contents: .git node_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 overwriting philip@debian:~$ rsync -av \ --backup \ --backup-dir=~/backups/$(date +%Y-%m-%d) \ ~/projects/website/ \ ~/backups/current/ sending incremental file list index.html sent 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 bar philip@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 end philip@debian:~$ rsync -av --stats source/ dest/ Number of files: 12 (reg: 10, dir: 2) Number of created files: 0 Number of deleted files: 0 Number of regular files transferred: 1 Total file size: 247,820 bytes Total transferred file size: 2,341 bytes Literal data: 2,341 bytes Matched data: 0 bytes

Quick Reference

Command / flagWhat 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=fileLoad exclude patterns from a text file
--backup --backup-dir=pathMove overwritten files to a dated backup directory
-i / --itemize-changesShow an 11-character status code for each file
rsync --versionCheck installed rsync version
rsync --helpFull 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.