Skip to main content

Handling Deleted Files

By default, rsync never deletes files at the destination. If a file exists at the destination but not at the source, rsync simply ignores it. To make the destination an exact mirror of the source, you need deletion flags — but they must be used carefully.

danger

--delete is the most dangerous rsync flag. Used incorrectly, it can wipe out files at the destination that you never intended to remove. Always test with --dry-run first.

Default Behavior (No Deletion)

# Default: adds new/changed files, but never removes
rsync -av /var/www/html/ /backup/www/

If backup/www/ has files that no longer exist in /var/www/html/, they remain untouched. Over time, the backup accumulates orphaned files — old code, deleted plugins, removed configs.

The --delete Family

--delete (Most Common)

Removes files from the destination that don't exist in the source:

# Make destination an exact mirror of source
rsync -av --delete /var/www/html/ /backup/www/

Delete Timing Variants

FlagWhen Deletion HappensBest For
--deleteDefault timing (currently same as --delete-during)Most use cases
--delete-beforeDeletes first, then transfersLow disk space — free space before copying
--delete-duringDeletes while transferringBetter performance — no extra scan pass
--delete-afterTransfers first, then deletesSafer — files exist at destination during transfer
--delete-delayCalculates deletions during transfer, executes afterBalance of performance and safety
# Free space first (useful when destination is almost full)
rsync -av --delete-before /var/www/html/ /backup/www/

# Delete after ensuring all files are transferred safely
rsync -av --delete-after /var/www/html/ /backup/www/

Safe Delete Workflow

Always follow this pattern:

Step 1: Preview with Dry Run

rsync -av --delete --dry-run /var/www/html/ /backup/www/

Look for deleting lines in the output:

deleting old-plugin/readme.txt
deleting old-plugin/styles.css
deleting old-plugin/
sending incremental file list
new-file.php

Step 2: Verify the Deletions Are Expected

Review each deleting line. Ask yourself:

  • Are these files actually obsolete?
  • Could any of them be destination-only files that should be kept?

Step 3: Execute

rsync -av --delete /var/www/html/ /backup/www/

Keeping a Safety Net: --backup and --backup-dir

Instead of permanently deleting, move deleted files to a quarantine directory:

rsync -av --delete \
--backup \
--backup-dir="/backup/deleted/$(date +%F)" \
/var/www/html/ /backup/www/

This:

  1. Makes /backup/www/ an exact mirror of source
  2. Moves any deleted files to /backup/deleted/2024-01-15/
  3. You can review and permanently delete later
# Check what was "deleted" (actually moved)
ls -la /backup/deleted/2024-01-15/

# Restore a file if needed
cp /backup/deleted/2024-01-15/important-file.php /backup/www/

# Clean up old quarantine directories
find /backup/deleted/ -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;
tip

--backup-dir is the safest way to use --delete. You get a clean mirror AND a safety net for accidental deletions.

Alternatives to --delete

--ignore-existing (Add Only, Never Overwrite)

Copy new files to the destination but never touch existing ones:

rsync -av --ignore-existing /var/www/html/ /backup/www/

Use case: Adding new uploads to a backup without risking overwriting modified files.

--existing (Update Only, Never Add)

Only update files that already exist at the destination — never add new files:

rsync -av --existing /staging/ /production/

Use case: Updating code on production without accidentally deploying new, untested files.

--update (Skip Newer Files)

Skip files at the destination that are newer than the source:

rsync -av --update /staging/ /production/

Use case: Prevent overwriting files that were hotfixed directly on production.

Combining --delete with Excludes

Protect specific directories from deletion while mirroring everything else:

# Mirror site code but protect uploads from deletion
rsync -av --delete \
--exclude='wp-content/uploads/' \
/staging/html/ user@production:/var/www/html/

# Mirror everything except config and user content
rsync -av --delete \
--exclude='.env' \
--exclude='storage/logs/' \
--exclude='public/uploads/' \
/deploy/release/ /var/www/html/
info

The --exclude flag protects matched paths from both transfer and deletion. The excluded directories at the destination won't be modified or removed.

Common Scenarios

Deploy Code Without Affecting User Content

rsync -avz --delete \
--exclude='wp-content/uploads/' \
--exclude='wp-config.php' \
--exclude='.env' \
/deploy/release/ user@production:/var/www/html/

Clean Mirror for Disaster Recovery

# Complete mirror — destination becomes exact copy
rsync -av --delete /var/www/html/ /backup/mirror/

Clean Up Stale Cache

# Mirror only the cache directory (removes stale entries)
rsync -av --delete /var/www/html/wp-content/cache/ \
/backup/cache/

Git-Style Deployment (Remove Old Files)

# Deploy like git: destination matches source exactly
rsync -avz --delete \
--exclude='.git/' \
--exclude='node_modules/' \
/repo/ user@server:/var/www/html/

Logging Deletions

Always log when using --delete in automated scripts:

rsync -av --delete \
--log-file=/var/log/rsync-mirror.log \
/var/www/html/ /backup/www/

# After run: check what was deleted
grep "deleting" /var/log/rsync-mirror.log

Common Pitfalls

PitfallConsequencePrevention
--delete without --dry-run firstUnexpected file deletion on productionAlways preview first
--delete on uploads directoryUser-generated content permanently lostExclude uploads: --exclude='uploads/'
Wrong source/destination with --deleteDestination files wipedDouble-check paths before running
--delete without --exclude for configs.env, wp-config.php removedExclude environment-specific files
Not using --backup-dirNo recovery if deletions were wrongUse --backup-dir for safety net
Running --delete in cron without loggingNo audit trail for deleted filesAlways use --log-file in automation

Quick Reference

# Preview deletions (safe)
rsync -av --delete --dry-run /src/ /dest/

# Mirror with delete
rsync -av --delete /src/ /dest/

# Delete with safety backup
rsync -av --delete --backup --backup-dir="/quarantine/$(date +%F)" /src/ /dest/

# Mirror but protect uploads
rsync -av --delete --exclude='uploads/' /src/ /dest/

# Add new files only (never delete, never overwrite)
rsync -av --ignore-existing /src/ /dest/

# Log deletions
rsync -av --delete --log-file=/var/log/rsync.log /src/ /dest/
grep "deleting" /var/log/rsync.log

What's Next