Cron Automation
Cron turns rsync from a manual tool into an automated backup system. By scheduling rsync jobs, you ensure backups run consistently — even when you're not watching.
Cron Basics
Cron Schedule Format
┌─────────── minute (0-59)
│ ┌───────── hour (0-23)
│ │ ┌─────── day of month (1-31)
│ │ │ ┌───── month (1-12)
│ │ │ │ ┌─── day of week (0-7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * * command
Common Schedules
| Schedule | Expression | Use Case |
|---|---|---|
| Every day at 2 AM | 0 2 * * * | Nightly backups |
| Every 6 hours | 0 */6 * * * | Frequent sync for active sites |
| Every Sunday at 3 AM | 0 3 * * 0 | Weekly full backup |
| First of month at 1 AM | 0 1 1 * * | Monthly archive |
| Every weekday at 11 PM | 0 23 * * 1-5 | End-of-business backup |
Editing Cron
# Edit your user's crontab
crontab -e
# View current cron jobs
crontab -l
# Edit system-wide crontab (as root)
sudo nano /etc/crontab
Simple Cron + Rsync
Nightly File Backup
# Daily at 2 AM — sync web files to local backup
0 2 * * * rsync -av /var/www/html/ /backup/www/
Nightly Backup to Remote Server
# Daily at 2 AM — sync to offsite backup
0 2 * * * rsync -avz /var/www/html/ user@backup-server:/backups/www/
Hourly Uploads Sync (High-Traffic Sites)
# Every hour — sync new uploads
0 * * * * rsync -av --ignore-existing /var/www/html/uploads/ user@backup:/backups/uploads/
Cron Date Formatting
In crontab, % has special meaning (newline). You must escape it as \%:
# Correct — escaped percent signs
0 2 * * * rsync -av /var/www/ /backup/www-$(date +\%F)/
# WRONG — will fail silently
0 2 * * * rsync -av /var/www/ /backup/www-$(date +%F)/
Production Backup Script
For anything beyond simple one-liners, use a wrapper script:
/usr/local/bin/daily-backup.sh
#!/bin/bash
# daily-backup.sh — Full server backup with logging and alerts
set -euo pipefail
# Configuration
SOURCE="/var/www/html/"
BACKUP_BASE="/backup"
REMOTE="user@backup-server:/offsite"
TIMESTAMP=$(date +%F_%H%M)
LOG="/var/log/rsync/backup_$TIMESTAMP.log"
mkdir -p /var/log/rsync "$BACKUP_BASE/$TIMESTAMP"
echo "=== Backup started: $TIMESTAMP ===" >> "$LOG"
# Step 1: Database export
if command -v mysqldump &> /dev/null; then
mysqldump --single-transaction --routines --triggers \
--all-databases | gzip > "$BACKUP_BASE/$TIMESTAMP/databases.sql.gz"
echo " Database export complete" >> "$LOG"
fi
# Step 2: Incremental file backup with link-dest
rsync -av --stats \
--link-dest="$BACKUP_BASE/latest" \
--exclude='cache/' \
--exclude='*.log' \
--exclude='*.tmp' \
"$SOURCE" "$BACKUP_BASE/$TIMESTAMP/files/" \
>> "$LOG" 2>&1
# Step 3: Update latest symlink
ln -sfn "$BACKUP_BASE/$TIMESTAMP" "$BACKUP_BASE/latest"
echo " File backup complete" >> "$LOG"
# Step 4: Offsite transfer
rsync -avz --stats \
"$BACKUP_BASE/$TIMESTAMP/" "$REMOTE/$TIMESTAMP/" \
>> "$LOG" 2>&1
echo " Offsite transfer complete" >> "$LOG"
# Step 5: Cleanup old backups (keep 14 days)
find "$BACKUP_BASE" -maxdepth 1 -type d -name "20*" -mtime +14 -exec rm -rf {} \;
find /var/log/rsync/ -name "*.log" -mtime +30 -delete
echo " Cleanup complete" >> "$LOG"
echo "=== Backup finished: $(date +%F_%H%M) ===" >> "$LOG"
Make it executable and schedule it:
sudo chmod +x /usr/local/bin/daily-backup.sh
# Add to crontab
crontab -e
# Add this line:
0 2 * * * /usr/local/bin/daily-backup.sh
Error Handling and Alerts
Email on Failure
0 2 * * * /usr/local/bin/daily-backup.sh 2>&1 || \
echo "Backup failed on $(hostname)" | mail -s " Backup Failed" admin@example.com
Script with Error Handling
/usr/local/bin/backup-with-alerts.sh
#!/bin/bash
set -euo pipefail
LOG="/var/log/rsync/backup_$(date +%F).log"
if rsync -avz --stats \
/var/www/html/ user@backup:/backups/www/ \
>> "$LOG" 2>&1; then
echo "[$(date)] Backup SUCCESS" >> "$LOG"
else
EXIT_CODE=$?
echo "[$(date)] Backup FAILED (exit: $EXIT_CODE)" >> "$LOG"
echo "Rsync backup failed. Exit code: $EXIT_CODE. See $LOG" | \
mail -s " Backup Failed - $(hostname)" admin@example.com
exit $EXIT_CODE
fi
Systemd Timers (Alternative to Cron)
Systemd timers provide better logging, dependency management, and failure tracking:
/etc/systemd/system/rsync-backup.service
[Unit]
Description=Daily rsync backup
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/daily-backup.sh
User=root
StandardOutput=journal
StandardError=journal
/etc/systemd/system/rsync-backup.timer
[Unit]
Description=Run rsync backup daily at 2 AM
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
# Enable and start the timer
sudo systemctl daemon-reload
sudo systemctl enable rsync-backup.timer
sudo systemctl start rsync-backup.timer
# Check timer status
systemctl list-timers | grep rsync
# View logs
journalctl -u rsync-backup.service --since today
Systemd vs Cron
| Feature | Cron | Systemd Timer |
|---|---|---|
| Setup complexity | Low — one-line entry | Medium — two files |
| Logging | Manual (--log-file) | Built-in (journalctl) |
| Failure tracking | None built-in | Tracks failed runs |
| Missed runs | Skipped | Persistent=true catches up |
| Dependencies | None | Can wait for network, etc. |
Monitoring Cron Jobs
Verify Cron Is Running
# Check cron service status
systemctl status cron # Debian/Ubuntu
systemctl status crond # CentOS/RHEL
# View recent cron activity
grep CRON /var/log/syslog | tail -20 # Debian/Ubuntu
grep CRON /var/log/cron | tail -20 # CentOS/RHEL
Verify Backups Are Fresh
# Check latest backup timestamp
ls -la /backup/latest
# Check backup size is reasonable
du -sh /backup/latest/
# Alert if no backup in 48 hours
find /backup/ -maxdepth 1 -name "latest" -mmin +2880 \
-exec echo " No backup in 48 hours!" \;
Common Pitfalls
| Pitfall | Consequence | Prevention |
|---|---|---|
Unescaped % in crontab | Command silently fails | Use \% in crontab date formatting |
No PATH in cron environment | rsync: command not found | Use full path: /usr/bin/rsync |
| No SSH key for cron user | Remote rsync prompts for password (hangs) | Set up key-based auth for cron user |
| No error handling | Failures go unnoticed | Check exit codes, send alerts |
| No log rotation | /var/log/rsync/ fills disk | Use logrotate or find -mtime -delete |
| Overlapping runs | Two backup jobs fight over files | Use flock to prevent overlap |
Preventing Overlapping Runs
# Use flock to ensure only one backup runs at a time
0 2 * * * flock -n /tmp/backup.lock /usr/local/bin/daily-backup.sh
Quick Reference
# Simple daily backup
0 2 * * * rsync -av /var/www/ /backup/www/
# Daily with logging
0 2 * * * rsync -av --stats --log-file="/var/log/rsync/backup_$(date +\%F).log" /var/www/ /backup/
# Incremental with link-dest
0 2 * * * rsync -av --link-dest=/backup/latest /var/www/ /backup/$(date +\%F)/ && ln -sfn /backup/$(date +\%F) /backup/latest
# With error notification
0 2 * * * /usr/local/bin/daily-backup.sh 2>&1 || mail -s "Backup Failed" admin@example.com
# Prevent overlapping runs
0 2 * * * flock -n /tmp/backup.lock /usr/local/bin/daily-backup.sh
# Check cron status
crontab -l
systemctl status cron
What's Next
- CI/CD Automation — Integrate rsync into deployment pipelines
- Backup Strategies — Design a comprehensive backup architecture
- Logging and Monitoring — Track backup operations