Rsync Dry Run on Mac: Preview Safe Developer Backups

Rsync dry run Mac guide: preview copy and delete behavior, avoid path mistakes, and back up developer folders safely.

Mac developer reviewing a safe rsync dry run before syncing a project backup

An rsync dry run on Mac is the habit that keeps a backup command from turning into a file deletion story. If your command includes --delete, exclude rules, an external drive, or a cloud-synced destination, run it with -n first and read the output before you let it touch the destination.

Rsync dry run Mac command for safe project backups

The basic dry-run pattern is this:

rsync -avn --delete \
  ~/Developer/my-app/ /Volumes/Backup/my-app/

The important flag is -n, short for --dry-run. It tells rsync to calculate what it would copy, update, and delete without actually changing files. The other flags in -avn are normal backup defaults: -a for archive mode and -v for verbose output.

For developer folders, the dry run gets more useful when you add the exclusions you intend to use in the real command:

rsync -avn --delete \
  --exclude 'node_modules/' \
  --exclude '.git/' \
  --exclude '.venv/' \
  --exclude 'venv/' \
  --exclude 'dist/' \
  --exclude 'build/' \
  --exclude '.next/cache/' \
  ~/Developer/my-app/ /Volumes/Backup/my-app/

Do not treat the dry run as a formality. It is the review step. The output tells you whether rsync understood your source path, destination path, trailing slash, delete behavior, and exclude patterns the way you meant them.

Why an rsync dry run matters on macOS

rsync is predictable, but it is literal. It does not know that ~/Developer/my-app is your source of truth or that /Volumes/Backup/my-app contains the only copy of something you still need. It follows the paths and rules you gave it.

That is especially important on a Mac because developer backups often involve moving pieces:

  • External drives mount under /Volumes/, and macOS may append a number if a stale mount point exists.
  • Desktop and Documents may be under iCloud Drive depending on system settings.
  • Cloud destinations can keep syncing long after the local copy command finishes.
  • Project folders contain thousands of generated files that you probably do not want to copy.
  • --delete is useful for mirrors, but it removes destination files that are missing from the source.

The dry run catches boring mistakes before they become expensive ones. A reversed path, missing trailing slash, broad exclusion, or wrong volume name is obvious when the output shows a huge delete list. It is much less obvious after the command has already cleaned the destination.

Dry run first, real sync second Source project src/ package.json node_modules/ dist/ Preview copy list delete list excluded paths -n Real mirror src/ package.json generated files skipped deletes expected A preview makes the dangerous decisions visible before the destination changes.
A dry run is a checkpoint between your intended backup plan and the filesystem changes rsync is about to make.

How to read rsync dry run output

A simple verbose dry run prints paths that would be transferred. That is enough for small jobs, but for real review you usually want itemized output:

rsync -avnih --delete \
  --exclude-from "$HOME/Developer/.rsync-dev-excludes" \
  ~/Developer/my-app/ /Volumes/Backup/my-app/

The -i flag adds an itemized change code. The -h flag makes sizes easier to read. Now the output can tell you more than “this file appears in the transfer.” It can show whether a file would be created, updated, have metadata changed, or deleted.

Common output patterns look like this:

>f.st...... src/index.ts
>f+++++++++ src/new-feature.ts
deleting dist/bundle.js
cd+++++++++ docs/
  • >f means a file would be sent to the destination.
  • +++++++++ means the item is new on the destination.
  • deleting means --delete would remove that destination path.
  • cd+++++++++ means a directory would be created.

Do not worry about memorizing every itemized field on day one. Start with the big questions: Are the copied paths the paths you expect? Are generated folders missing from the transfer? Are deletions limited to files you really want removed from the destination?

Abstract rsync preview showing safe and dangerous sync paths for a Mac developer backup
Most rsync accidents are visible in the preview: wrong direction, wrong destination, missing exclusions, or a delete list that is far too large.

Use dry run before --delete

--delete is the flag that makes a destination a mirror. If a file exists at the destination but no longer exists at the source, rsync removes it. That is exactly what you want for a clean backup mirror and exactly what you do not want when your source path is wrong.

Use this review loop:

  1. Run with -n and --delete.
  2. Search the output for deleting.
  3. If the delete list is surprising, stop and fix the paths or filters.
  4. Run the same command again with -n still enabled.
  5. Only remove -n when the copy and delete list matches your intent.

If you are nervous, add --delete-after for the real run. That tells rsync to perform deletions after transfers, which can make logs easier to reason about. It does not make a wrong command safe, so keep the dry run.

Check the trailing slash before the real sync

The most common rsync path mistake is the source trailing slash. These two commands do different things:

# Copies the contents of my-app into the destination
rsync -avn ~/Developer/my-app/ /Volumes/Backup/my-app/

# Copies the my-app directory itself into the destination
rsync -avn ~/Developer/my-app /Volumes/Backup/

Both can be correct. The trouble starts when you expected one behavior and typed the other. A dry run shows the resulting paths before the real copy. If you see my-app/my-app/ nesting, stop and fix the command.

Also be careful with shell variables in backup scripts. An empty variable near --delete is not a theoretical risk. Print the command or run the script in dry-run mode before trusting it:

set -u
SOURCE="$HOME/Developer/my-app/"
DEST="/Volumes/Backup/my-app/"

rsync -avnih --delete \
  --exclude-from "$HOME/Developer/.rsync-dev-excludes" \
  "$SOURCE" "$DEST"

set -u makes the shell fail when a variable is unset. It will not catch every bad value, but it catches a class of backup script mistakes that should never reach rsync.

Test your exclude file with dry run

For developer projects, an exclude file is easier to maintain than a long pile of inline flags. Create something like ~/Developer/.rsync-dev-excludes:

node_modules/
.npm/
.pnpm-store/
.yarn/cache/
.next/cache/
.nuxt/
dist/
build/
coverage/
.turbo/
.vite/
.venv/
venv/
__pycache__/
.pytest_cache/
.mypy_cache/
vendor/bundle/
target/
.gradle/
.DS_Store

Then test it:

rsync -avnih --delete \
  --exclude-from "$HOME/Developer/.rsync-dev-excludes" \
  ~/Developer/my-app/ /Volumes/Backup/my-app/

If node_modules, .next/cache, venv, or dist appear in the transfer output, your patterns are not matching the way you think. Common causes are a typo in the exclude file, a leading slash that anchors the rule to the transfer root, or an exclude file saved somewhere other than the path in the command.

A good dry run shows Source files, lockfiles, docs, tests, migrations, and configuration moving to the destination, with a small delete list you can explain.
A bad dry run shows Dependency trees, build caches, unexpected nested folders, a wrong volume path, or a delete list that makes you pause.

A safe Mac backup workflow with rsync

Here is a practical workflow for a local project mirror to an external drive or a clean cloud-safe destination.

  1. Keep active work in a local folder. Use ~/Developer or ~/Code instead of running dependency-heavy projects directly inside iCloud Drive.
  2. Write a reusable exclude file. Start with generated folders for Node.js, Python, Ruby, Rust, Java, and macOS metadata. Add project-specific rules later.
  3. Run an itemized dry run. Use -avnih with the exact source, destination, excludes, and delete behavior you plan to use.
  4. Review copies and deletes. Look for wrong nesting, unexpected destination paths, generated folders, and unexplained deletions.
  5. Run the real command. Remove only -n. Do not rewrite the command from memory.
  6. Test restore occasionally. Copy the destination to a scratch folder and run npm ci, pnpm install --frozen-lockfile, pip install -r requirements.txt, or your stack's equivalent.

That last step matters. A backup is not just a copy. It is something you can restore from when the source is gone or broken.

Organized Mac developer backup workflow after rsync dry-run review
The calm version of rsync is repetitive: preview, review, run, verify. The point is to make sync boring.

When LSyncer is simpler than rsync dry runs

A careful rsync workflow is a good tool. Keep using it if you like scripts, inspect dry-run output, and already have logs or scheduling handled. The friction appears when the script becomes a small app you maintain privately: several folder pairs, different destinations, saved exclusions, external drive checks, schedules, logs, and reminders to notice stale backups.

LSyncer is built for that local Mac developer workflow. It skips common generated folders such as node_modules, .git, virtual environments, build output, and caches; lets you choose folder pairs; runs scheduled syncs; and shows status without turning every backup into a command review. It is a one-time $19.99 Mac App Store purchase, not a subscription.

Use rsync when you need SSH transfers, advanced flags, or scripts you can version with infrastructure. Use LSyncer when you want clean local project backups on macOS with developer-aware exclusions and visible status.

Best practices for rsync dry run on Mac

  • Use -n or --dry-run every time you change a source, destination, exclusion, or delete option.
  • Add -i for itemized output when reviewing anything destructive.
  • Keep the source trailing slash intentional. It changes the destination layout.
  • Search dry-run output for deleting before running a mirror.
  • Use --exclude-from for developer folders instead of rewriting long commands.
  • Keep active projects outside cloud-synced folders, then sync a filtered copy if you want cloud availability.
  • Do not sync secrets, private keys, local database dumps, or production credentials into shared destinations unless you have a deliberate encrypted backup plan.

FAQ

What is the rsync dry run flag on Mac?

The dry run flag is -n, also available as --dry-run. A common command is rsync -avn source/ destination/. It previews what would be copied or deleted without changing the destination.

Does rsync --dry-run delete files?

No. With -n enabled, rsync prints the changes it would make but does not copy or delete files. That is why you should run rsync -avn --delete before a destructive mirror.

How do I see which files rsync would delete?

Run a dry run with --delete and search the output for deleting. For more detail, use rsync -avnih --delete source/ destination/ so the preview includes itemized change output.

Should I use rsync dry run before excluding node_modules?

Yes. Add your --exclude 'node_modules/' rule, keep -n enabled, and confirm that dependency folders do not appear in the transfer list. Do the same for .venv, dist, build, framework caches, and any project-specific generated folders.

Why does the trailing slash matter in rsync?

A source path ending in / copies the contents of the directory. A source path without the trailing slash copies the directory itself. A dry run shows which layout you are about to create before the destination changes.