HomeTopicsrsync & Delta Transfer › Flags That Matter

rsync Flags That Matter — and the Ones That Bite

rsync accepts well over a hundred options. Nobody knows all of them, and nobody needs to. What every administrator needs is a working vocabulary of perhaps fifteen — understood, not memorized — because the standard failure mode with rsync is not ignorance. It is copying a flag soup from a forum post, half of which does nothing for your situation and one of which quietly deletes files.

This article is the vocabulary lesson. We will unpack what -a actually bundles, decode the trailing-slash rule that fills forums with grief, treat --delete with the respect a loaded tool deserves, untangle include/exclude ordering, and finish with a safe-defaults recipe you can adapt to almost any job. It assumes you know roughly what rsync is for; if the delta-transfer machinery underneath is still fuzzy, start with how the rsync algorithm works in our rsync & Delta Transfer series and come back.

What -a Really Bundles (and What It Leaves Out)

Nearly every rsync command you will ever see starts with -a, for archive mode. It is not one behavior — it is a bundle, exactly equivalent to typing -rlptgoD. Worth unpacking once in your life, letter by letter:

  • -r — recursive. Descend into directories. Without it, rsync copies only the files you name and skips folders entirely.
  • -l — links. Copy symbolic links as links, rather than ignoring them or following them into their targets. The destination gets a symlink pointing at the same path.
  • -p — permissions. Reproduce each file's Unix permission bits (the rwxr-x--- mode) on the destination.
  • -t — times. Preserve each file's modification time. This one is quietly load-bearing — more on it in a moment.
  • -g — group and -o — owner. Preserve which group and user own each file. Setting arbitrary ownership requires root (or equivalent) on the receiving side; as a normal user, these silently fall back to your own ownership.
  • -D — devices and specials. Recreate device nodes and special files such as sockets and FIFOs. Only meaningful for system-level copies, and also root-only.

In plain terms: -a means "copy the tree and keep it looking like itself" — structure, links, timestamps, ownership, permissions. That is the right default for backups and mirrors of Unix filesystems, which is why everyone uses it reflexively.

Just as important is what -a does not include, because people assume otherwise:

  • No -H: hard links between files inside your tree are not detected; the destination gets independent copies. Add -H if your data relies on them (it costs memory and time).
  • No -A or -X: POSIX ACLs and extended attributes are not copied — plain permission bits only.
  • No -z: archive mode does not compress anything in transit.
  • No --delete: -a never removes anything from the destination. Deletion is always an explicit, separate decision — as it should be.

Why -t Is the Quiet Hero of the Bundle

Of the seven letters, -t deserves special attention because it is the one whose absence causes mysterious slowness rather than an error. rsync decides which files to transfer using the quick check: a file is presumed unchanged if its size and modification time match on both sides. Preserved mtimes are what make that comparison meaningful.

Skip -t, and every file lands on the destination stamped with the copy time instead of the source's mtime. On the next run, no destination mtime matches its source, the quick check fails for everything, and rsync re-examines — and with some option combinations re-copies — the entire tree, every single run. The classic symptom is "rsync copies everything every time," and the classic cause is a habit of running rsync -rv instead of -a. (Filesystems with coarse timestamp resolution cause the same symptom for a different reason — see the cross-platform gotchas article for the --modify-window fix.)

Remember: rsync's default definition of "changed" is size or modification time differs. It does not read file contents to decide what to transfer unless you ask with --checksum. Preserving times with -t (or -a) is what keeps that cheap comparison honest from run to run.

The Trailing Slash: One Character, Two Meanings

Here is the rule that has launched a thousand forum threads. On the source path, a trailing slash changes what gets copied:

  • rsync -a src dest — copies the directory itself: you end up with dest/src/…
  • rsync -a src/ dest — copies the contents of the directory: you end up with dest/…

The mnemonic that makes it stick: the slash means "reach inside." Without the slash you are handing rsync the folder as an object; with the slash you are handing it whatever is inside the folder. On the destination path, a trailing slash changes nothing — the rule is entirely about the source.

The diagram below shows the same command run both ways, and the two different trees that result.

rsync -a src dest src (no slash) "copy the directory itself" dest/ └─ src/ ├─ a.txt └─ b.txt rsync -a src/ dest src/ (trailing slash) "reach inside — copy the contents" dest/ ├─ a.txt └─ b.txt Same files, different destination layout — the source's trailing slash is the only difference.

Why this matters more than a cosmetic nesting difference: many rsync jobs run repeatedly, and some include --delete. Get the slash wrong on a mirror job and the destination layout no longer corresponds to what the job expects — at best you double-nest into dest/src/src/… confusion, at worst a --delete run decides the "extra" tree at the destination doesn't belong there. Two habits neutralize the trap: be deliberate about the slash every time you compose a command (beware shell tab-completion, which appends one for you), and preview with a dry run before the first real execution of any new job — the technique our dry runs and verification article turns into a checklist.

--delete: Necessary, Powerful, and Ready to Ruin Your Day

By default, rsync only adds and updates. Files that exist on the destination but not on the source are left alone — which means a plain -a job produces an accumulating copy, not a true mirror. Renamed and removed source files linger at the destination forever. To make the destination genuinely match the source, you add --delete, which removes destination files that no longer exist on the source side.

Three facts define its safe use:

  • It only ever deletes on the destination. The source is never touched. Whatever horror stories you have heard, they are about destinations.
  • It deletes relative to what the source claims to contain. This is the danger zone. If the source path is wrong — a typo, a missing trailing slash, or worst of all an unmounted or empty source directory — rsync faithfully concludes that the correct contents of the destination is "nothing much" and deletes accordingly. An external drive that failed to mount plus a nightly --delete mirror is the canonical self-inflicted data loss.
  • It only applies to directories being synced — it rides along with a recursive transfer; it is not a general-purpose cleanup command.

Treat every --delete job as armed, and give it guardrails:

# Always preview first: -n is --dry-run, -i itemizes what would happen
rsync -avin --delete /data/projects/ backup@host:/srv/mirror/projects/

# Guardrail 1: refuse to mass-delete (abort past a sane threshold)
rsync -avi --delete --max-delete=50 /data/projects/ backup@host:/srv/mirror/projects/

# Guardrail 2: move deletions aside instead of destroying them
rsync -avi --delete --backup --backup-dir=/srv/mirror/.trash/ \
      /data/projects/ backup@host:/srv/mirror/projects/

# Guardrail 3 (in scripts): verify the source is really there first
mountpoint -q /data || exit 1

The --max-delete=50 line means: if this run would delete more than fifty files, stop — a tripwire against the empty-source disaster. The --backup-dir variant demotes "deleted" to "moved into a dated holding area you can prune later." Timing variants exist too — --delete-during (the usual default behavior), --delete-delay, and --delete-after, which postpone removals until the transfer has succeeded; the after/delay forms are kinder to mirrors that might be interrupted mid-run.

Include and Exclude: First Match Wins

Real jobs rarely want the whole tree. Caches, .git directories, editor droppings, and log spools usually stay home. rsync's filter system is powerful and has exactly one rule you must internalize: patterns are checked in the order given, and the first one that matches a file decides its fate. Nothing later gets a vote.

# Skip caches and VCS metadata anywhere in the tree
rsync -av --exclude='.git/' --exclude='node_modules/' --exclude='*.tmp' src/ dest/

# Anchored: exclude only the top-level logs dir, not every dir named logs
rsync -av --exclude='/logs/' src/ dest/

# The classic "only this file type" pattern — order is everything:
rsync -av --include='*/' --include='*.conf' --exclude='*' /etc/ backup/etc/

That last one reads like a riddle until you apply first-match-wins: directories match */ and are allowed in (so rsync can descend); .conf files match the second rule and are copied; everything else falls through to --exclude='*' and is skipped. Reverse the order — --exclude='*' first — and the first rule swallows everything: an empty transfer, no error, no warning. Two more details that bite: a pattern starting with / is anchored to the top of the transfer, while logs/ alone matches a directory of that name at any depth; and a trailing / on a pattern makes it match directories only. For elaborate rule sets, --filter and per-directory .rsync-filter files exist, but the three patterns above cover most working life.

One relationship worth knowing: excluded files are, by default, also protected from deletion on a --delete run — rsync won't delete what it was told not to look at (the --delete-excluded flag exists precisely to override that mercy, deliberately).

The Rest of the Working Vocabulary

The flags below round out the set worth knowing cold. Each earns its place in recurring jobs:

Flag What it does When it matters
-n / --dry-run Analyze and report, change nothing Before the first run of anything, always before --delete
-i / --itemize-changes Per-file code showing exactly what changed and why Reading dry runs; auditing what a job did
-z Compress data in flight Slow links and compressible data; useless for media/archives
-P Bundles --partial (keep interrupted files for resume) and --progress Big files over unreliable links; interactive runs
--stats End-of-run report: files examined, literal vs matched data, speedup Measuring whether delta transfer is paying off
-H Preserve hard links within the transferred tree System backups, snapshot trees, package mirrors
-x Stay on one filesystem; don't descend into other mounts Backing up / without dragging in /proc, NFS mounts, etc.
--bwlimit Cap transfer bandwidth Sharing a WAN link with people who need it
-c / --checksum Decide "changed" by hashing full contents, not size+mtime Occasional integrity audits — far too slow for routine runs

The Ones That Bite

A short bestiary of flags whose names promise more than they deliver, or deliver more than they promise:

  • -u / --update skips any destination file newer than its source counterpart. Sounds protective; in a mirror it means destination-side edits silently survive forever and your "mirror" quietly diverges. Use it only when the destination is legitimately also edited.
  • --inplace rewrites destination files directly instead of building a temp file and renaming. It helps with huge files on tight disks, but an interrupted run leaves a corrupt half-written file where a good old one used to be, and it undermines hard-link snapshot schemes (see mirroring patterns). Know why you want it before you type it.
  • --ignore-existing never updates a file that already exists at the destination, however stale. Fine for "top up the archive" jobs; poison for mirrors.
  • --append assumes files only ever grow and sends just the tail. Aimed at logs; corrupts anything that was edited in the middle. Its safer sibling --append-verify at least checks the existing part — still a specialist's flag.
  • -W / --whole-file switches delta transfer off. Correct on fast LANs (and the default for local copies); an accidental paste of it turns your WAN job back into full copies.
  • -a itself bites in one scenario: pushing to filesystems or servers that cannot represent Unix owners and permissions, where -o -g -p produce a wall of errors. The fix is covered in cross-platform gotchas.

A Safe-Defaults Recipe You Can Reuse

Here is a compact sequence that embodies everything above. It suits the common case — mirroring a directory tree to another machine — and degrades gracefully when you trim it:

# Step 1 — PREVIEW. Nothing changes; read the itemized plan.
rsync -avin --delete --exclude='.git/' --exclude='*.tmp' \
      /data/site/ deploy@web01:/var/www/site/

# Step 2 — RUN, with tripwire and safety net.
rsync -avi  --delete --max-delete=100 \
      --backup --backup-dir=.rsync-trash \
      --exclude='.git/' --exclude='*.tmp' \
      /data/site/ deploy@web01:/var/www/site/

# Step 3 — VERIFY. Re-run the preview: a converged mirror prints nothing.
rsync -avin --delete --exclude='.git/' --exclude='*.tmp' \
      /data/site/ deploy@web01:/var/www/site/

Adapt by subtraction: drop --delete (and its guardrails) for accumulating copies, drop the excludes if you truly want everything, add -z when the link is slow and the data compressible. The three-step shape — preview, run, verify — is the part to keep, and it transplants directly into scheduled jobs: cron on Unix systems, or on Windows a scheduler built for transfers such as Sysax FTP Automation, where the run/verify/alert cycle becomes configuration instead of shell scripting.

Reading a Command You Inherited

Sooner or later you inherit a cron job containing someone else's flag soup, and the skill that matters is reading it before you trust it. Take this specimen, typical of what turns up in handover documents:

rsync -avzHu --inplace --delete-excluded --exclude='cache/' /srv/app node2:/srv/app

Decode it letter by letter and the questions write themselves. -avzH — faithful archive copy, verbose, compressed in flight, hard links preserved: reasonable. -u — destination files that are newer get skipped: is someone editing on node2, or is this hiding a past mistake? --inplace — files are rewritten in place: what happens if this dies mid-run on a file the application is serving? --delete-excluded — the destination's cache/ directory is actively deleted every run, not merely ignored: is that intentional cache-busting or an accident nobody noticed? And the source has no trailing slash, so this populates /srv/app/app/ — unless the job has "worked" for years precisely because of that nesting. None of these are necessarily wrong; all of them are decisions, and every decision in an inherited command deserves to be re-made deliberately. A dry run with -i answers most of the questions in thirty seconds.

The Version to Tell a Colleague

The short course: -a means "copy the tree faithfully" and bundles -rlptgoD — recursion, symlinks, permissions, times, group, owner, devices — but includes no compression and never deletes. The trailing slash on the source means "reach inside": src/ copies contents, src copies the directory itself. --delete makes true mirrors by removing destination files missing from the source, which makes it exactly as dangerous as your source path is wrong — so preview with -n, cap with --max-delete, and soften with --backup-dir. Filters are first-match-wins. Everything else is refinement.

From here, the flags meet reality in mirroring and one-way sync patterns, and the preview-and-verify discipline gets its full treatment in dry runs and verification — the article to read before your first production --delete.

Frequently Asked Questions

Does rsync -a delete files on the destination?
No. Archive mode only adds and updates; files that vanished from the source remain at the destination indefinitely. Deletion happens only when you explicitly add --delete, and it then removes destination files absent from the source — never anything on the source itself.
What does the trailing slash on the source path actually do?
It means "copy the contents" rather than "copy the directory." rsync -a src dest creates dest/src/..., while rsync -a src/ dest puts the files directly into dest/. A trailing slash on the destination changes nothing — the rule applies to the source only.
Why does my rsync job re-copy everything on every run?
Usually because modification times aren't being preserved — you ran without -t (or -a), so destination mtimes never match and the quick check fails for every file. The other common cause is a destination filesystem with coarser timestamps than the source, fixed with --modify-window.
Does -a compress data during transfer?
No — archive mode preserves file attributes but sends data uncompressed. Add -z to compress in flight, which helps on slow links with text-like data and does nothing useful for already-compressed files such as images, video, or zip archives.
Why did my include/exclude rules copy nothing at all?
Almost certainly an ordering problem: rsync applies filter rules first-match-wins, so a broad --exclude='*' placed before your includes swallows every file silently. Put the narrow --include rules first (including '*/' so directories can be entered) and the catch-all exclude last.
Is there a safe way to test a command that includes --delete?
Yes: run it first with -n (--dry-run) plus -i and -v, which prints exactly what would be transferred and deleted without touching anything. Check the deletion lines, add --max-delete as a tripwire, and only then run for real.

From the Sysax team: we build secure file transfer software for Windows — Sysax Multi Server, an FTP, FTPS, SFTP, and HTTPS server, and Sysax FTP Automation for scheduled, scripted transfers. Free trials are on the download page.