A lot of people paste rsync -avz --delete into a deploy script and move on without ever unpacking what each letter means. It works, so there’s rarely a reason to stop and ask. But understanding what each flag actually instructs the command to do makes it much easier to reason about exactly how far --delete reaches, and why the other flags are there in the first place. This post breaks down the main options and shows how they’re actually used in a real theme deployment.
Note: rsync (“remote sync”) is a file-synchronization tool that compares source and destination and transfers only what has changed. Unlike a plain copy, it uses a delta-transfer algorithm internally, which is where it earns its keep when the same files get synced over and over.
Quick reference
| Option | Meaning | What it does |
|---|---|---|
-a |
archive mode | Recursively copies files while preserving permissions, timestamps, symlinks, and ownership |
-v |
verbose | Prints the name of every file transferred |
-z |
compress | Compresses data in transit to reduce bandwidth usage |
--delete |
delete sync | Removes files from the destination that no longer exist in the source, making the destination an exact mirror |
-e |
remote shell | Specifies how to connect (which SSH key, port, and options to use) |
-a isn’t one flag — it’s a bundle of flags
-a looks like a single option, but it’s shorthand for several flags combined. Expanded, it’s equivalent to -rlptgoD:
| Expanded | Meaning |
|---|---|
-r |
recursive — process directories recursively |
-l |
copy symlinks as symlinks, not as the files they point to |
-p |
preserve permissions |
-t |
preserve timestamps (modification time) |
-g |
preserve group ownership |
-o |
preserve owner |
-D |
preserve device and special files |
The one that’s easy to overlook is -t (preserve timestamps). rsync decides whether a file needs re-transferring by comparing file size and modification time against what it saw last time. If timestamps got reset to “now” on every sync instead of being preserved, rsync would see a different modification time on every run even when the file’s content hadn’t changed at all — and it would fall back to re-transferring everything, which defeats the point of a delta-transfer tool. Using -a isn’t just about keeping metadata intact; it’s what lets rsync’s own change-detection logic work correctly in the first place.
How far does --delete actually reach?
--delete makes the destination match the source exactly: anything removed from the source also gets removed from the destination. The part worth understanding precisely is that it only operates within the specific source and destination directories you point it at.
If you run rsync -avz --delete "$SRC" "$DEST" against a theme-specific directory, --delete only affects whatever lives under $DEST — it has no effect on sibling directories outside that path (for example, other themes sitting alongside it under a shared themes/ folder). In practice, the blast radius of --delete is entirely determined by how narrowly you scope the destination path.
A real example: this project’s theme deployment command
Deploying this blog’s theme (wpmm-blog) to the server actually uses this command:
SRC=server/wpmm-blog-theme/wpmm-blog/
DEST=layer2024@layer2024.xsrv.jp:wpmm.jp/public_html/blog/wp-content/themes/wpmm-blog/
rsync -avz --delete \
-e "ssh -i ~/.ssh/layer2024_xserver.key -p 10022 -o BatchMode=yes" \
"$SRC" "$DEST"
Here, --delete is scoped to the theme’s own directory (wp-content/themes/wpmm-blog/), so the only files it can actually remove are old theme files that have been deleted from the local source but are still lingering on the server. If the destination were mistakenly pointed at wp-content/themes/ (the entire themes folder) instead, other themes — including WordPress’s own bundled default themes — would be exposed to deletion too. Scoping the destination down to exactly the directory you intend to deploy is what keeps --delete safe to use in practice.
The -e flag bundles the SSH key file, port number, and BatchMode=yes (which makes SSH fail immediately instead of waiting at an interactive password prompt) into a single connection specification. That matters specifically for unattended scripts — without it, a script running with no one watching could hang indefinitely at a prompt no one is there to answer.
The trailing slash changes the result
rsync has a well-known quirk: whether or not the source path ends in a slash (/) changes what actually gets copied.
rsync -av /path/to/src/ /path/to/dest/— copies the contents ofsrcdirectly intodestrsync -av /path/to/src /path/to/dest/— copies thesrcdirectory itself, landing asdest/src
In the theme deployment example above, SRC=server/wpmm-blog-theme/wpmm-blog/ ends in a slash, so the contents of wpmm-blog/ land directly under the destination’s wpmm-blog/ directory rather than nesting one level deeper. Building a command without paying attention to the trailing slash is an easy way to end up with files one directory level off from where you meant them to go.
Summary
| Option | In one line |
|---|---|
-a |
Recursive copy that preserves file attributes (bundles -rlptgoD) |
-v |
Prints what got transferred |
-z |
Compresses data to save bandwidth |
--delete |
Mirrors the destination to the source — scoping the destination path is the key to using it safely |
-e |
Specifies the connection method (SSH key, port, etc.) |
-avz --delete is a common combination, but each character carries its own distinct responsibility. --delete in particular only reaches as far as the destination path you give it, so being deliberate about that scope is the basic safeguard against deleting more than you intended.