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.
--deleteis 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.
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/
>fmeans a file would be sent to the destination.+++++++++means the item is new on the destination.deletingmeans--deletewould 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?
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:
- Run with
-nand--delete. - Search the output for
deleting. - If the delete list is surprising, stop and fix the paths or filters.
- Run the same command again with
-nstill enabled. - Only remove
-nwhen 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 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.
- Keep active work in a local folder. Use
~/Developeror~/Codeinstead of running dependency-heavy projects directly inside iCloud Drive. - Write a reusable exclude file. Start with generated folders for Node.js, Python, Ruby, Rust, Java, and macOS metadata. Add project-specific rules later.
- Run an itemized dry run. Use
-avnihwith the exact source, destination, excludes, and delete behavior you plan to use. - Review copies and deletes. Look for wrong nesting, unexpected destination paths, generated folders, and unexplained deletions.
- Run the real command. Remove only
-n. Do not rewrite the command from memory. - 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.
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
-nor--dry-runevery time you change a source, destination, exclusion, or delete option. - Add
-ifor itemized output when reviewing anything destructive. - Keep the source trailing slash intentional. It changes the destination layout.
- Search dry-run output for
deletingbefore running a mirror. - Use
--exclude-fromfor 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.
Related reading
- rsync exclude-from Mac — keep reusable exclude files for developer backups instead of rewriting long commands.
- Time Machine stuck preparing backup on Mac — see when a filtered mirror helps Time Machine avoid generated-file churn.
- Rsync exclude node_modules on Mac — copyable exclude patterns for Node.js, Python, Ruby, and build caches.
- Sync folder to external hard drive on Mac — apply dry-run habits to external SSD developer backups.
- Rsync GUI for Mac — how to evaluate visual tools for previews, exclusions, logs, and schedules.
- Mac sync two folders — compare Finder,
ditto,rsync, and filtered sync apps.
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.