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.
--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
| Flag | When Deletion Happens | Best For |
|---|---|---|
--delete | Default timing (currently same as --delete-during) | Most use cases |
--delete-before | Deletes first, then transfers | Low disk space — free space before copying |
--delete-during | Deletes while transferring | Better performance — no extra scan pass |
--delete-after | Transfers first, then deletes | Safer — files exist at destination during transfer |
--delete-delay | Calculates deletions during transfer, executes after | Balance 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:
- Makes
/backup/www/an exact mirror of source - Moves any deleted files to
/backup/deleted/2024-01-15/ - 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 {} \;
--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/
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
| Pitfall | Consequence | Prevention |
|---|---|---|
--delete without --dry-run first | Unexpected file deletion on production | Always preview first |
--delete on uploads directory | User-generated content permanently lost | Exclude uploads: --exclude='uploads/' |
Wrong source/destination with --delete | Destination files wiped | Double-check paths before running |
--delete without --exclude for configs | .env, wp-config.php removed | Exclude environment-specific files |
Not using --backup-dir | No recovery if deletions were wrong | Use --backup-dir for safety net |
Running --delete in cron without logging | No audit trail for deleted files | Always 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
- Dry Run and Testing — Validate destructive commands safely
- Exclude and Include Patterns — Fine-tune what gets synced
- Logging and Monitoring — Track what rsync does