Exit Codes and Error Handling
Every rsync run returns an exit code that tells you exactly what happened — success, partial failure, network error, or something else. Building scripts that check and respond to these codes is what separates a fragile backup from a reliable one.
Exit Code Reference
| Code | Name | Meaning | Recoverable? |
|---|---|---|---|
| 0 | Success | All files transferred | |
| 1 | Syntax error | Invalid flags or arguments | Fix command |
| 2 | Protocol incompatibility | Version mismatch between client/server | Upgrade rsync |
| 3 | File selection error | Source path doesn't exist | Fix paths |
| 5 | Error starting client-server | Daemon/module issue | Check rsyncd config |
| 10 | Socket I/O error | Network connection dropped | Retry |
| 11 | File I/O error | Disk read/write failure | Check disk |
| 12 | Protocol data stream error | Corrupted transfer | Retry |
| 13 | Diagnostics error | Error with program diagnostics | Check setup |
| 14 | IPC error | Internal process communication failed | |
| 20 | Signal received | rsync killed (e.g., SIGINT) | Restart |
| 21 | waitpid() error | Process management error | |
| 22 | Memory allocation error | Out of RAM | Reduce scope |
| 23 | Partial transfer (I/O) | Some files failed (permissions, locked) | Investigate |
| 24 | Partial transfer (vanished) | Files disappeared during scan | Usually safe |
| 25 | Max delete limit reached | --max-delete was hit | Intentional |
| 30 | Send/receive timeout | Transfer stalled | Retry with --timeout |
| 35 | Daemon connection timeout | Couldn't reach rsync daemon | Retry |
| 255 | SSH error | SSH connection failed | Debug SSH |
Basic Error Handling
Check Exit Code
rsync -avz /src/ user@remote:/dest/
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo " Backup completed successfully"
elif [ $EXIT_CODE -eq 24 ]; then
echo " Some files vanished (usually harmless)"
else
echo " Backup failed with exit code $EXIT_CODE"
exit 1
fi
One-Liner for Cron
rsync -avz /src/ user@remote:/dest/ || echo "rsync failed ($?) at $(date)" >> /var/log/rsync-errors.log
Retry Logic
Simple Retry
for attempt in 1 2 3; do
rsync -avzP /src/ user@remote:/dest/ && break
echo "Attempt $attempt failed, retrying in 60s..."
sleep 60
done
Smart Retry (Only on Network Errors)
#!/bin/bash
# smart-retry-rsync.sh
MAX_RETRIES=3
RETRY_DELAY=60
NETWORK_ERRORS="10 12 30 35 255"
for attempt in $(seq 1 $MAX_RETRIES); do
rsync -avzP /src/ user@remote:/dest/
EXIT_CODE=$?
# Success
[ $EXIT_CODE -eq 0 ] && exit 0
# Vanished files (harmless)
[ $EXIT_CODE -eq 24 ] && exit 0
# Check if error is retryable
if echo "$NETWORK_ERRORS" | grep -qw "$EXIT_CODE"; then
echo "Network error (code $EXIT_CODE), retry $attempt/$MAX_RETRIES..."
sleep $RETRY_DELAY
else
echo "Fatal error (code $EXIT_CODE), not retrying."
exit $EXIT_CODE
fi
done
echo "All $MAX_RETRIES attempts failed"
exit 1
Alerting on Failure
Email Alert
#!/bin/bash
rsync -avz /src/ user@remote:/dest/ --log-file=/var/log/rsync/backup.log
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ] && [ $EXIT_CODE -ne 24 ]; then
mail -s "Rsync backup failed (code $EXIT_CODE) on $(hostname)" \
admin@example.com < /var/log/rsync/backup.log
fi
Slack Webhook Alert
#!/bin/bash
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
rsync -avz /src/ user@remote:/dest/
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ] && [ $EXIT_CODE -ne 24 ]; then
curl -s -X POST -H 'Content-type: application/json' \
--data "{\"text\":\" Rsync failed on $(hostname) — exit code $EXIT_CODE\"}" \
"$WEBHOOK_URL"
fi
Production Backup Script
#!/bin/bash
# production-backup.sh — Full error handling, retry, and alerting
set -o pipefail
LOG="/var/log/rsync/backup_$(date +%F).log"
MAX_RETRIES=3
log() { echo "[$(date '+%H:%M:%S')] $1" | tee -a "$LOG"; }
run_backup() {
rsync -avz --stats \
--exclude='cache/' --exclude='*.log' --exclude='tmp/' \
--log-file="$LOG" \
/var/www/html/ user@backup:/backups/$(date +%F)/
return $?
}
# Retry loop
for attempt in $(seq 1 $MAX_RETRIES); do
log "Attempt $attempt/$MAX_RETRIES"
run_backup
EXIT_CODE=$?
case $EXIT_CODE in
0)
log " Backup completed successfully"
exit 0
;;
24)
log " Some files vanished (harmless)"
exit 0
;;
10|12|30|35|255)
log " Network error (code $EXIT_CODE), retrying..."
sleep 60
;;
*)
log " Fatal error (code $EXIT_CODE)"
mail -s "Backup FAILED on $(hostname)" admin@example.com < "$LOG"
exit $EXIT_CODE
;;
esac
done
log " All $MAX_RETRIES attempts failed"
mail -s "Backup FAILED after $MAX_RETRIES retries on $(hostname)" admin@example.com < "$LOG"
exit 1
Common Pitfalls
| Pitfall | Consequence | Prevention |
|---|---|---|
| Ignoring exit codes in cron | Silent failures, no backups for days | Always check $? and alert |
| Treating exit 24 as failure | False alarms (temp files vanished) | Handle 24 as warning, not error |
| Retrying non-recoverable errors | Wasted time, same failure | Retry only network errors (10, 12, 30) |
| No logging | Can't investigate after-the-fact | Always use --log-file |
set -e with rsync | Exit 24 kills the script | Use explicit exit code checking instead |
Quick Reference
# Check exit code
rsync -av /src/ /dest/; echo "Exit: $?"
# Retry on failure
for i in 1 2 3; do rsync -avzP /src/ /dest/ && break || sleep 60; done
# Alert on failure
rsync -av /src/ /dest/ || echo "Failed ($?) at $(date)" >> /var/log/rsync-errors.log
# Handle vanished files gracefully
rsync -av /src/ /dest/; [ $? -eq 24 ] && echo "Vanished files, OK"
What's Next
- Troubleshooting and Debugging — Diagnose specific error types
- Cron Automation — Schedule with proper error handling
- Logging and Monitoring — Capture and analyze logs