Linux rsync: Efficient File Synchronization

rsync is the Swiss Army knife of file synchronization in Linux environments. Unlike simple copy commands like `cp` or `scp` that transfer entire files regardless of existing content, rsync implements...

Key Insights

  • rsync’s delta-transfer algorithm only copies file differences, making it vastly more efficient than cp or scp for repeated synchronization tasks—especially critical when syncing large directories over network connections
  • The trailing slash in rsync source paths fundamentally changes behavior: /path/to/dir/ copies contents while /path/to/dir copies the directory itself, a distinction that trips up even experienced administrators
  • Combining --dry-run with -v flags before production syncs prevents catastrophic data loss from mistyped commands, while --delete requires extreme caution as it removes destination files absent from source

Introduction to rsync

rsync is the Swiss Army knife of file synchronization in Linux environments. Unlike simple copy commands like cp or scp that transfer entire files regardless of existing content, rsync implements a sophisticated delta-transfer algorithm that identifies and transmits only the differences between source and destination files.

This efficiency becomes transformative when you’re synchronizing large directories repeatedly. Consider a 10GB database backup directory where only 100MB changes daily. Using scp transfers all 10GB every time. rsync transfers only the 100MB delta, saving bandwidth, time, and system resources.

Here’s the fundamental difference in syntax:

# Traditional copy - always transfers everything
cp -r /source/directory /destination/

# scp for remote - also transfers everything
scp -r /source/directory user@remote:/destination/

# rsync - transfers only changes
rsync -av /source/directory /destination/

The delta-transfer algorithm works by dividing files into blocks, generating checksums, and comparing them between source and destination. Only blocks with different checksums get transferred. This happens transparently—you don’t need to configure anything.

Basic rsync Syntax and Common Options

The standard rsync command follows this structure:

rsync [OPTIONS] SOURCE DESTINATION

The most important flag is -a (archive mode), which preserves permissions, timestamps, symbolic links, and recursively copies directories. It’s essentially shorthand for -rlptgoD. You’ll use this in 90% of scenarios.

# Basic local synchronization with archive mode
rsync -av /home/user/documents/ /backup/documents/

# Add verbosity and human-readable progress
rsync -avh --progress /var/www/ /backup/www/

# Include compression for network transfers
rsync -avz /large/dataset/ user@backup-server:/mnt/backup/

The trailing slash behavior is crucial and frequently misunderstood:

# WITH trailing slash - copies CONTENTS of source_dir into dest_dir
rsync -av /path/to/source_dir/ /path/to/dest_dir/
# Result: /path/to/dest_dir/file1, /path/to/dest_dir/file2

# WITHOUT trailing slash - copies source_dir ITSELF into dest_dir
rsync -av /path/to/source_dir /path/to/dest_dir/
# Result: /path/to/dest_dir/source_dir/file1, /path/to/dest_dir/source_dir/file2

This distinction matters enormously in production scripts. I recommend always using trailing slashes on both source and destination for predictable behavior.

The --delete flag makes rsync mirror the source exactly by removing files from destination that don’t exist in source:

# Mirror source to destination, deleting extra files
rsync -av --delete /source/ /destination/

Use --delete with extreme caution. It’s powerful for maintaining exact mirrors but catastrophic if you reverse source and destination accidentally.

Remote Synchronization with SSH

rsync shines when synchronizing across network connections. It uses SSH by default for remote transfers, providing encryption and authentication without additional configuration.

Push files to a remote server:

# Basic remote push
rsync -avz /local/path/ user@remote.server.com:/remote/path/

# Show progress for large transfers
rsync -avzh --progress /var/www/ deploy@web-server:/var/www/

Pull files from a remote server:

# Pull from remote to local
rsync -avz user@remote.server.com:/remote/data/ /local/backup/

# Pull with deletion to create exact mirror
rsync -avz --delete backup@backup-server:/critical/data/ /local/mirror/

When using non-standard SSH ports or identity files:

# Custom SSH port
rsync -avz -e "ssh -p 2222" /local/ user@remote:/destination/

# Specific SSH key
rsync -avz -e "ssh -i ~/.ssh/deploy_key" /app/ deploy@server:/var/www/app/

# Both custom port and key
rsync -avz -e "ssh -p 2222 -i ~/.ssh/backup_key" \
  backup@remote:/data/ /local/backup/

The -e flag specifies the remote shell command, allowing you to pass any SSH options needed for your environment.

Advanced rsync Techniques

Filtering files with --include and --exclude patterns gives granular control over what gets synchronized:

# Exclude common cache and log directories
rsync -av \
  --exclude='*.log' \
  --exclude='*.tmp' \
  --exclude='cache/' \
  --exclude='node_modules/' \
  /var/www/application/ /backup/application/

# Include only specific file types
rsync -av \
  --include='*.php' \
  --include='*.js' \
  --include='*/' \
  --exclude='*' \
  /source/ /destination/

Note that --include='*/' is necessary to include directories when using exclusion patterns, otherwise rsync won’t traverse the directory tree.

Bandwidth limiting prevents rsync from saturating network connections:

# Limit to 5000 KB/s (roughly 5 MB/s)
rsync -av --bwlimit=5000 /large/files/ user@remote:/backup/

# Very conservative limit for background syncs
rsync -av --bwlimit=1000 /data/ backup-server:/data/

Always test destructive operations with --dry-run:

# See what would happen without actually doing it
rsync -av --delete --dry-run /source/ /destination/

# Combine with verbose for detailed preview
rsync -avv --delete --dry-run /critical/data/ /backup/

The --partial flag keeps partially transferred files if a connection drops, allowing resumption:

# Resume interrupted transfers
rsync -av --partial --progress /huge/dataset/ user@remote:/backup/

# Combine with --partial-dir for cleaner organization
rsync -av --partial --partial-dir=.rsync-partial /data/ remote:/backup/

Practical Use Cases and Automation

Here’s a production-ready backup script with logging:

#!/bin/bash
# backup-website.sh

SOURCE="/var/www/production/"
DEST="backup@backup-server:/backups/website/"
LOGFILE="/var/log/rsync-backup.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')

echo "[$DATE] Starting backup..." >> "$LOGFILE"

rsync -avz \
  --delete \
  --exclude='cache/' \
  --exclude='*.log' \
  -e "ssh -i /root/.ssh/backup_key" \
  "$SOURCE" "$DEST" \
  >> "$LOGFILE" 2>&1

if [ $? -eq 0 ]; then
  echo "[$DATE] Backup completed successfully" >> "$LOGFILE"
else
  echo "[$DATE] Backup failed with error code $?" >> "$LOGFILE"
  # Send alert email or notification
fi

Make it executable and add to cron for daily automated backups:

# Make script executable
chmod +x /usr/local/bin/backup-website.sh

# Add to crontab for daily 2 AM execution
# crontab -e
0 2 * * * /usr/local/bin/backup-website.sh

For website deployment workflows:

#!/bin/bash
# deploy-website.sh

STAGING="/var/www/staging/"
PRODUCTION="/var/www/production/"

# Always dry-run first
echo "Performing dry-run..."
rsync -av --delete --dry-run "$STAGING" "$PRODUCTION"

read -p "Proceed with deployment? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
  rsync -av --delete "$STAGING" "$PRODUCTION"
  echo "Deployment complete"
fi

Performance Optimization and Troubleshooting

For maximum performance with large file transfers, consider these optimizations:

# Skip compression for already-compressed files (images, videos)
rsync -av --no-compress /media/library/ backup:/media/

# Use compression only for text-based files
rsync -av --compress-level=9 /var/www/ backup:/www/

# Increase SSH cipher speed with less secure but faster algorithms
rsync -av -e "ssh -c aes128-ctr" /data/ remote:/backup/

Monitor transfer progress with detailed statistics:

# Show detailed progress and statistics
rsync -av --progress --stats /source/ /destination/

# Human-readable sizes with itemized changes
rsync -avh --itemize-changes /source/ /destination/

Common errors and solutions:

  • “Permission denied”: Ensure SSH keys are configured and destination directories have correct permissions
  • “No space left on device”: Check destination disk space with df -h
  • Slow transfers: Disable compression for pre-compressed files, check network bandwidth, consider --whole-file for LAN transfers

For specific use cases, consider alternatives:

  • rclone: Better for cloud storage (S3, Google Drive, Dropbox)
  • syncthing: Continuous bi-directional synchronization across devices
  • restic/borg: Deduplicated, encrypted backups with versioning

However, for straightforward file synchronization between Linux systems, rsync remains the gold standard. Its ubiquity, reliability, and efficiency make it indispensable for system administrators and developers alike. Master these patterns, and you’ll handle everything from simple backups to complex deployment pipelines with confidence.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.