Dry Run and Testing
The --dry-run flag is rsync's undo button — except it works before you make a mistake. It simulates the entire operation, showing exactly what would happen without actually transferring, deleting, or modifying anything.
If it touches production, dry-run it first. This applies to any rsync command using --delete, deploying to live servers, or running a new automation script for the first time.
How --dry-run Works
# Simulated run — shows what WOULD happen
rsync -av --dry-run /var/www/html/ user@server:/backup/
# Equivalent short form
rsync -avn /var/www/html/ user@server:/backup/
Rsync performs all the scanning and comparison it would normally do, but skips the actual file transfers and deletions. You see the full output — which files would be sent, updated, or deleted.
Essential Dry Run Patterns
Preview a Basic Sync
rsync -avn /var/www/html/ /backup/www/
Preview with --delete (Critical)
# See what files would be DELETED before running for real
rsync -av --delete --dry-run /var/www/html/ user@production:/var/www/html/
Look for deleting lines in the output:
deleting old-plugin/readme.txt
deleting old-plugin/
sending incremental file list
new-file.php
updated-style.css
Preview Exclude/Include Patterns
Verify your filters are matching correctly:
rsync -av --dry-run \
--exclude='cache/' \
--exclude='*.log' \
--exclude='node_modules/' \
/var/www/html/ /backup/www/
If a file you expected to see is missing from the output, your exclude pattern is too broad.
Combining with Other Flags
--dry-run + --stats
Get transfer statistics without actually transferring:
rsync -avz --dry-run --stats /var/www/html/ user@backup:/backups/
This tells you:
- How many files would be transferred
- How much data would be sent
- The expected speedup ratio
--dry-run + --itemize-changes
See exactly what would change for each file:
rsync -av --dry-run --itemize-changes /var/www/html/ /backup/www/
Output:
>f.st...... index.php # Would transfer (size+time changed)
>f..t...... style.css # Would transfer (time changed)
cd+++++++++ new-directory/ # Would create directory
*deleting old-file.txt # Would delete (if --delete used)
.f unchanged.php # No action needed
--dry-run + --log-file
Save the dry-run output for review:
rsync -av --dry-run --stats \
--log-file=/tmp/rsync-preview.log \
/var/www/html/ user@production:/var/www/html/
# Review the preview
cat /tmp/rsync-preview.log
Testing Workflows
Workflow 1: First-Time Deployment
When deploying to a new server for the first time:
# Step 1: Dry run to see what will be copied
rsync -avz --dry-run /var/www/html/ user@newserver:/var/www/html/
# Step 2: Review output, confirm paths are correct
# Step 3: Run for real
rsync -avz /var/www/html/ user@newserver:/var/www/html/
Workflow 2: Mirroring with Deletion
# Step 1: Preview — focus on "deleting" lines
rsync -av --delete --dry-run /source/ /mirror/
# Step 2: Count deletions
rsync -av --delete --dry-run /source/ /mirror/ 2>&1 | grep -c "deleting"
# Step 3: If count is reasonable, execute with backup
rsync -av --delete --backup --backup-dir="/quarantine/$(date +%F)" /source/ /mirror/
Workflow 3: Testing New Exclude Patterns
# Test: Does this exclude pattern work as expected?
rsync -av --dry-run \
--exclude='wp-content/cache/' \
--exclude='*.log' \
/var/www/html/ /backup/
# Check: Are the intended files still in the output?
rsync -av --dry-run \
--exclude='wp-content/cache/' \
--exclude='*.log' \
/var/www/html/ /backup/ | grep "wp-config"
# If wp-config.php appears, it's being synced (good for backups, bad for deployments)
Workflow 4: Validation in CI/CD
#!/bin/bash
# ci-deploy.sh — Dry run in CI, real run only after approval
set -e
# Always dry run first
echo "=== DRY RUN ==="
rsync -avz --delete --dry-run \
--exclude='.env' \
/deploy/build/ user@production:/var/www/html/ 2>&1 | tee /tmp/deploy-preview.txt
# Count changes
CHANGES=$(grep -c "^" /tmp/deploy-preview.txt || true)
DELETIONS=$(grep -c "deleting" /tmp/deploy-preview.txt || true)
echo "Files affected: $CHANGES"
echo "Files to delete: $DELETIONS"
# Safety check: abort if too many deletions
if [ "$DELETIONS" -gt 50 ]; then
echo " Too many deletions ($DELETIONS). Aborting. Review /tmp/deploy-preview.txt"
exit 1
fi
# Real deployment
echo "=== DEPLOYING ==="
rsync -avz --delete \
--exclude='.env' \
/deploy/build/ user@production:/var/www/html/
What Dry Run Does NOT Test
--dry-run tests... | --dry-run does NOT test... |
|---|---|
| File selection (include/exclude) | Actual write permissions on destination |
| Deletion preview | Disk space availability |
| File comparison logic | Network reliability during transfer |
| Path resolution | Timeout behavior on slow connections |
| SSH connectivity | Post-sync scripts or hooks |
Common Pitfalls
| Pitfall | Consequence | Prevention |
|---|---|---|
Forgetting to remove --dry-run | Nothing actually transfers | Remove -n or --dry-run after verification |
| Not reading dry-run output carefully | Missed unexpected deletions or additions | Pipe to file, review, count changes |
| Assuming dry-run tests permissions | Deploy fails due to write permission denied | Test with a small real sync first |
| Skipping dry-run for "simple" commands | "Simple" mistake deletes production data | Always dry-run --delete commands |
| Running dry-run and real command as different users | Different results due to permission differences | Use the same user for both |
Quick Reference
# Basic dry run
rsync -avn /src/ /dest/
# Dry run with stats
rsync -avz --dry-run --stats /src/ user@remote:/dest/
# Preview deletions
rsync -av --delete --dry-run /src/ /dest/
# Detailed changes preview
rsync -av --dry-run --itemize-changes /src/ /dest/
# Log dry run output
rsync -av --dry-run --log-file=/tmp/preview.log /src/ /dest/
# Count expected changes
rsync -avn /src/ /dest/ 2>&1 | wc -l
# Count expected deletions
rsync -av --delete --dry-run /src/ /dest/ 2>&1 | grep -c "deleting"
What's Next
- Handling Deleted Files — Use
--deletesafely after testing - Logging and Monitoring — Track what rsync actually does
- Exclude and Include Patterns — Build and test filter rules