rsync delete files not in source Mac: Safe Mirror Guide

rsync delete files not in source Mac guide: use --delete safely with dry runs, exclusions, and clean developer backups.

Mac developer reviewing a project backup with stale destination files before running rsync delete

rsync delete files not in source Mac is the command pattern you use when a backup destination should match the source, not just receive new files. The useful part is --delete. The dangerous part is also --delete: one wrong path, missing trailing slash, or broad exclude rule can remove files from the destination faster than you can read the terminal output.

rsync delete files not in source Mac: the safe command

The shortest version looks simple:

rsync -av --delete \
  ~/Developer/my-app/ \
  /Volumes/DevBackup/my-app/

That command copies changes from the source folder into the destination folder and deletes destination files that no longer exist in the source. It is a mirror operation. If you removed src/old-api.ts from your project, the next run removes src/old-api.ts from the backup too. That is exactly what you want for a clean project mirror.

Do not run that version first. On a Mac developer workstation, the safer first command is an itemized dry run:

rsync -avnih --delete \
  ~/Developer/my-app/ \
  /Volumes/DevBackup/my-app/
  • -a keeps recursive archive-style copy behavior.
  • -v prints what rsync plans to do.
  • -n means dry run, so no files are copied or deleted.
  • -i shows itemized changes, including delete lines.
  • -h makes sizes easier to read.
  • --delete removes destination files that are missing from the source when you run the real command.

Why --delete is useful and risky for developer backups

A plain rsync copy without --delete is additive. It updates changed files and creates new files at the destination, but it leaves old destination files alone. That can be fine for an archive, but it is not a clean mirror. After a few weeks, the destination may contain renamed files, old build output, deleted docs, stale generated bundles, and folders that no longer represent the project.

--delete fixes that drift. It says: after applying the source and filter rules, remove anything at the destination that should not be there. The destination becomes a current restore point instead of a pile of every file that ever existed.

The risk is that rsync follows paths literally. It does not know the difference between “my source folder is intentionally small” and “I accidentally pointed at the wrong empty folder.” It does not know that your external drive failed to mount and macOS created an ordinary directory under /Volumes/DevBackup. It does not know that a broad exclusion hid the files you wanted to keep. The only safety rail is the preview you read before the real run.

rsync --delete removes destination-only files after comparison Source project src/ tests/ package.json package-lock.json node_modules/ skipped Compare copy new update changed delete stale --delete Destination mirror src/ tests/ package.json package-lock.json stale files removed The destination is cleaned according to the source and your exclude rules, so review both before the real run.
--delete makes a destination match the filtered source. That is powerful for mirrors and unforgiving when paths are wrong.

Start with a delete preview, not a real delete

Use this review loop for any Mac project backup that removes destination-only files:

  1. Make sure the destination exists and is the destination you expect.
  2. Run rsync with -n, -i, and --delete.
  3. Search the output for deleting.
  4. Confirm every delete is expected stale state.
  5. Run the same command again after removing only n from the flags.
# Preview first
rsync -avnih --delete \
  ~/Developer/my-app/ \
  /Volumes/DevBackup/my-app/

# Real run only after review
rsync -avih --delete \
  ~/Developer/my-app/ \
  /Volumes/DevBackup/my-app/

The difference between those two commands is one letter. Do not retype the whole command from memory. Edit the dry-run command so the paths, trailing slash, excludes, and delete behavior stay exactly the same.

Common dry-run output includes lines like this:

>f.st...... src/sync.ts
>f+++++++++ docs/recovery.md
deleting dist/old-bundle.js
deleting tmp/old-export.json

The deleting lines are the review target. A few old generated files may be fine. A delete line for src/, docs/, a client folder, or the whole destination means the command is not ready.

Abstract rsync comparison showing stale destination files and risky delete decisions
Stale destination files are normal. Surprise destination deletes are a signal to stop and fix the command.

Add exclusions before you delete destination files

For developer folders, deletion should happen after filtering. Otherwise rsync spends time comparing disposable folders and may preserve or remove the wrong noise. A clean project backup usually keeps source files, tests, docs, configuration, scripts, migrations, small assets, and lockfiles. It skips dependency folders, generated output, caches, logs, coverage, and machine-local virtual environments.

Create a reusable exclude file:

cat > ~/.rsync-dev-delete-excludes <<'EOF'
node_modules/
.pnpm-store/
.yarn/cache/
.next/cache/
.nuxt/
.turbo/
.vite/
dist/
build/
coverage/
.git/
.venv/
venv/
__pycache__/
.pytest_cache/
.mypy_cache/
vendor/bundle/
tmp/
log/
target/
.gradle/
.DS_Store
EOF

Then use the same file in the dry run and the real run:

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

One subtle point: excluded files are normally protected from deletion too. If node_modules/ exists at the destination and you exclude node_modules/, rsync will not descend into that folder and will generally leave it alone. That can be good if you want local-only destination caches untouched. It can be surprising if you expected the mirror to clean old generated folders that were copied during earlier runs.

If the destination already contains junk from old unfiltered backups, do one deliberate cleanup pass. Either remove those folders manually after verifying the destination path, or use a separate reviewed command for those generated directories. Do not add advanced delete-excluded behavior casually. The readable, safer habit is: filter clean going forward, and clean historical junk explicitly.

Avoid the four rsync --delete mistakes on Mac

1. Reversed source and destination

rsync --delete makes the second path match the first path. If you reverse them, the wrong folder becomes the source of truth. The dry run should show files moving from your working project toward the backup, not the other way around.

2. Missing or wrong trailing slash

~/Developer/my-app/ copies the contents of my-app. ~/Developer/my-app copies the folder itself into the destination. Both can be correct, but the preview should not show accidental my-app/my-app/ nesting.

3. External drive not mounted

Check /Volumes/DevBackup before running the real command. If the drive is missing, a script may write into a local folder named like the drive, and the next run can compare against the wrong tree.

4. Broad excludes hiding real source files

An exclude such as .* may skip important files like .gitignore, .editorconfig, .env.example, or tool configuration. Prefer explicit cache and metadata rules over broad dotfile deletion logic.

When to use --delete-delay, --delete-after, or backups

The default --delete behavior is usually enough for a local developer mirror after a dry run. If you want the log to be easier to inspect, consider --delete-after. It delays deletions until after transfers. If a run fails mid-transfer, that ordering can be easier to reason about.

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

--delete-delay is another option that computes deletes during the scan and applies them near the end. For most local Mac backups, the operational habit matters more than the exact delete mode: dry run, read deletes, run real command, and verify a restore occasionally.

If the destination contains work you might need to recover, do not rely on a mirror as your only copy. Use Git remotes for history, Time Machine for whole-Mac recovery, and a separate archive or snapshot when you need point-in-time retention. A mirror is current by design; it is not an archive of every old state.

Where LSyncer fits for delete-based sync

A careful rsync setup is a good fit if you want command-line control, SSH support, and scripts you can keep in a dotfiles repo. The maintenance cost appears when you have several project folders, external drives that are not always mounted, repeated exclude lists, and no visible reminder that the last backup failed.

LSyncer is built for the local Mac developer version of this workflow. You choose folder pairs, keep generated folders such as node_modules, .git, virtual environments, build output, and caches out of the sync, and run jobs manually or on a schedule with visible status. It is a one-time $19.99 Mac App Store purchase, not a subscription.

Use rsync when you need exact flags and terminal-first review. Use LSyncer when the job is a recurring local folder sync and you want exclusions, scheduling, and status without maintaining another shell script.

Organized Mac developer backup workflow with filtered source files and clean destination mirror
The calm version of delete-based sync is a repeatable filter, a reviewed destination, and a mirror you can restore from.

Best practices for rsync --delete on Mac

  • Run -avnih --delete before the first real delete-based sync and whenever paths or filters change.
  • Search dry-run output for deleting and explain each destination removal before proceeding.
  • Keep active projects under a local path such as ~/Developer or ~/Code, then sync a filtered copy outward.
  • Use an explicit --exclude-from file for dependency folders, caches, build output, logs, and macOS metadata.
  • Keep lockfiles and project manifests. They are the restore contract for rebuilding skipped dependencies.
  • Verify external drives and NAS mounts before running real commands under /Volumes/.
  • Do an occasional restore test in a scratch folder with npm ci, pnpm install --frozen-lockfile, pip install -r requirements.txt, bundle install, or your stack's equivalent.

FAQ

How do I make rsync delete files not in the source on Mac?

Use rsync -av --delete source/ destination/. For safety, preview first with rsync -avnih --delete source/ destination/ and review every deleting line before removing n from the flags.

Does rsync --delete delete files from the source?

No. In the normal source-to-destination form, --delete removes files from the destination when they do not exist in the source. If you reverse the paths, the wrong folder becomes the destination, which is why a dry run matters.

Should I use --delete for Mac project backups?

Use --delete when the destination should be a current mirror of the source after exclusions. Do not use it for archival backups where old versions should be retained. For archives, use snapshots, Time Machine, Git history, or a destination that preserves versions.

Will excluded folders be deleted at the destination?

Usually excluded folders are protected because rsync does not process them. If old generated folders already exist at the destination, clean them deliberately after verifying the path, or use a carefully reviewed separate cleanup command. Do not add advanced delete-excluded behavior unless you understand the effect.

What is the safest rsync delete command for node_modules projects?

Use an exclude file and an itemized dry run: rsync -avnih --delete --exclude-from "$HOME/.rsync-dev-delete-excludes" ~/Developer/my-app/ /Volumes/DevBackup/my-app/. Confirm node_modules/, virtual environments, caches, and build output are not part of the transfer before the real run.