rsync: The Complete Linux Backup and Sync Tool Guide

rsync: The Complete Linux Backup and Sync Tool Guide

Tested on: Ubuntu 26.04 LTS · Debian 12 · Arch Linux · Fedora 44 — Last updated: June 2026

rsync is the standard Linux tool for copying and synchronizing files locally and over SSH. It transfers only what changed between source and destination — so a second run of the same backup is nearly instant. Unlike cp or scp, rsync compares file metadata (size and modification time by default) and builds a delta, making it indispensable for backups, deployments, and ongoing directory synchronization.

Contents
  1. Prerequisites
  2. Installing rsync
  3. Basic Syntax and the Trailing Slash Rule
  4. Core Flags Reference
  5. Local File and Directory Copying
  6. Remote Transfers Over SSH
    1. Setting Up Passwordless SSH for Automated Backups
  7. Excluding Files and Directories
  8. Dry Run and Inspecting What rsync Will Do
  9. Incremental Snapshot Backups with --link-dest
  10. Understanding --delete Variants
  11. Automating Backups with Cron
  12. Bandwidth and Resource Limiting
    1. Further Reading

Prerequisites

  • Basic terminal usage and familiarity with file paths
  • For remote transfers: SSH access to the target server and an existing user account
  • For automated cron jobs: a passwordless SSH key pair set up between source and destination
  • rsync installed on both machines for remote transfers (the remote side needs rsync in $PATH)

Installing rsync

rsync ships pre-installed on most distributions. Verify your version before starting — some flags covered here require rsync 3.1+.

# Ubuntu/Debian:
sudo apt install rsync -y

# Fedora/RHEL:
sudo dnf install rsync -y

# Arch Linux:
sudo pacman -S rsync

# Check version:
rsync --version

Expected output:

rsync  version 3.2.7  protocol version 31
Copyright (C) 1996-2022 by Andrew Tridgell, Wayne Davison, and others.
Web site: https://rsync.samba.org/

Basic Syntax and the Trailing Slash Rule

The general form is rsync [OPTIONS] SOURCE DESTINATION. The single most common rsync mistake is misunderstanding the trailing slash on the source path — it changes behavior fundamentally.

# WITH trailing slash — sync the CONTENTS of src/ into dest/:
rsync -av /home/user/src/ /backup/dest/
# Result: /backup/dest/file1.txt, /backup/dest/file2.txt

# WITHOUT trailing slash — sync the DIRECTORY ITSELF into dest/:
rsync -av /home/user/src /backup/dest/
# Result: /backup/dest/src/file1.txt, /backup/dest/src/file2.txt

The destination trailing slash is optional — rsync creates the destination directory if it doesn't exist. The rule only applies to the source.

Core Flags Reference

These are the flags you'll use in nearly every rsync command:

  • -a / --archive — Archive mode. Equivalent to -rlptgoD: recursive, preserves symlinks, permissions, timestamps, owner, group, and device files. Use this for all backups.
  • -v / --verbose — Print each file as it's transferred.
  • -z / --compress — Compress data in transit. Useful over slow WAN links; adds CPU overhead on fast LAN — omit it for local or gigabit transfers.
  • -P — Combines --progress (show transfer progress) and --partial (keep partial files for resume). Essential for large transfers.
  • -n / --dry-run — Simulate the transfer without touching any files. Always use this first on destructive operations.
  • -u / --update — Skip files that are newer at the destination. Useful for two-way sync scenarios.
  • -h / --human-readable — Show file sizes in KB/MB/GB.
  • --delete — Remove files from destination that no longer exist in source. Makes destination a true mirror.
  • -e / --rsh — Specify the remote shell, e.g., -e "ssh -p 2222".
  • -A / --acls — Preserve ACLs (extends -a).
  • -X / --xattrs — Preserve extended attributes (extends -a).

Local File and Directory Copying

# Copy a single file:
rsync -av file.txt /backup/file.txt

# Copy directory contents with progress:
rsync -avP /home/user/Documents/ /backup/Documents/

# Mirror — make destination identical to source (deletes extras in dest):
rsync -av --delete /home/user/ /backup/home/

# Preserve ACLs and extended attributes — required for system-level backups:
sudo rsync -aAXvh /etc/ /backup/etc/

# Backup to external drive, excluding cache and trash:
sudo rsync -aAXvh --delete 
  --exclude='/home/user/.cache' 
  --exclude='/home/user/.local/share/Trash' 
  --exclude='/home/user/.thumbnails' 
  /home/user/ /mnt/external/backup/home/

Remote Transfers Over SSH

rsync tunnels over SSH by default when the source or destination contains a colon. No extra daemon or port is needed — just working SSH access.

# Push local directory to remote server:
rsync -avz /home/user/projects/ user@192.168.1.100:/home/user/projects/

# Pull from remote to local:
rsync -avz user@192.168.1.100:/home/user/backups/ /local/backups/

# Use a non-standard SSH port:
rsync -avz -e "ssh -p 2222" /source/ user@server:/destination/

# Use a specific SSH key:
rsync -avz -e "ssh -i ~/.ssh/backup_key" /source/ user@server:/dest/

# Combine port and key:
rsync -avz -e "ssh -p 2222 -i ~/.ssh/backup_key" /source/ user@server:/dest/

# Deploy a built static site to a VPS:
rsync -avz --delete 
  --exclude='.git' 
  --exclude='node_modules' 
  ./dist/ user@yourserver.com:/var/www/mysite/

Setting Up Passwordless SSH for Automated Backups

Cron jobs and scripts cannot enter a passphrase interactively. Generate a dedicated key with no passphrase:

# Generate dedicated backup key (no passphrase):
ssh-keygen -t ed25519 -f ~/.ssh/backup_key -N "" -C "rsync-backup-$(hostname)"

# Copy public key to backup server:
ssh-copy-id -i ~/.ssh/backup_key.pub user@backup-server

# Test the connection:
ssh -i ~/.ssh/backup_key user@backup-server "echo connection ok"

# Now rsync runs without prompts:
rsync -avz -e "ssh -i ~/.ssh/backup_key" /source/ user@backup-server:/dest/

Excluding Files and Directories

Exclude patterns match against the relative path of each file from the source root. A pattern ending with / matches directories only.

# Single exclude:
rsync -av --exclude='*.log' /source/ /dest/

# Multiple inline excludes:
rsync -av 
  --exclude='*.log' 
  --exclude='*.tmp' 
  --exclude='.git/' 
  --exclude='__pycache__/' 
  --exclude='node_modules/' 
  /source/ /dest/

# Exclude file — more maintainable for complex rules:
cat > ~/.rsync-exclude << 'EOF'
*.log
*.tmp
*.swp
.git/
.DS_Store
node_modules/
__pycache__/
.cache/
.thumbnails/
Thumbs.db
EOF

rsync -av --exclude-from="$HOME/.rsync-exclude" /source/ /dest/

Exclude rules are processed in order — first match wins. Use --include before --exclude to whitelist specific patterns inside an otherwise excluded tree:

# Sync only .conf files from /etc, skip everything else:
rsync -av 
  --include='*.conf' 
  --include='*/' 
  --exclude='*' 
  /etc/ /backup/etc-conf/

Dry Run and Inspecting What rsync Will Do

Before running any destructive rsync command — especially those using --delete — always simulate with -n.

# Show what would be transferred without doing it:
rsync -avn /source/ /dest/

# Show what would be deleted:
rsync -av --delete --dry-run /source/ /dest/

# Show transfer statistics after a run:
rsync -av --stats /source/ /dest/
# Output includes: total files, transferred count, bytes sent/received, transfer speed

# Itemize changes — shows exactly WHY each file was transferred:
rsync -av --itemize-changes /source/ /dest/

The --itemize-changes output uses an 11-character code per file. The most useful characters: f = file, d = directory, > = transferred to destination, c = checksum differs, s = size differs, t = timestamp differs. For example, >f.st...... means a file was sent because size and timestamp changed.

Incremental Snapshot Backups with --link-dest

--link-dest is rsync's most powerful backup feature. Each snapshot directory appears to contain a full backup, but unchanged files are hard-linked to the previous snapshot rather than copied — so each incremental backup costs only the disk space of what actually changed.

#!/bin/bash
# snapshot-backup.sh — run daily via cron

SOURCE="/home/user/"
BACKUP_DIR="/backup/snapshots"
TODAY=$(date +%Y-%m-%d)
LATEST="$BACKUP_DIR/latest"

# Create today's snapshot, hard-linking unchanged files from the last run:
rsync -aAXvh --delete 
  --exclude='/home/user/.cache' 
  --link-dest="$LATEST" 
  "$SOURCE" 
  "$BACKUP_DIR/$TODAY/"

# Update the 'latest' symlink:
ln -snf "$BACKUP_DIR/$TODAY" "$LATEST"

echo "Snapshot complete: $BACKUP_DIR/$TODAY"
# After several runs:
ls -lh /backup/snapshots/
# drwxr-xr-x  2026-06-01/
# drwxr-xr-x  2026-06-02/
# drwxr-xr-x  2026-06-03/
# lrwxrwxrwx  latest -> 2026-06-03

# Restore from a specific date (non-destructive — goes to /tmp first):
rsync -aAXvh /backup/snapshots/2026-06-01/ /tmp/restore-test/

# Check actual disk usage — hard-links mean all snapshots together
# use far less space than their apparent size:
du -sh /backup/snapshots/*/

Understanding --delete Variants

Without --delete, rsync only ever adds files. Files removed from source persist in the destination indefinitely. Add --delete to create a true mirror — but understand the variants before choosing one.

# Standard delete — removes destination files not in source:
rsync -av --delete /source/ /dest/

# Safe delete — move deleted files to a separate directory instead of removing them:
rsync -av --delete --backup --backup-dir=/backup/deleted-$(date +%Y%m%d) 
  /source/ /dest/

# Delete excluded files too (normally excluded files are left in place even with --delete):
rsync -av --delete --delete-excluded --exclude='*.tmp' /source/ /dest/

# Only delete after transfer is complete (safer, prevents partial-sync data loss):
rsync -av --delete-after /source/ /dest/

Automating Backups with Cron

crontab -e
# Daily backup at 2:00 AM — local to external drive:
0 2 * * * rsync -aAXh --delete 
  --exclude='/home/user/.cache' 
  /home/user/ /mnt/external/backup/ >> /var/log/rsync-daily.log 2>&1

# Hourly sync to remote backup server:
0 * * * * rsync -az -e "ssh -i /home/user/.ssh/backup_key" 
  /home/user/projects/ backup@nas.local:/backups/projects/ >> /var/log/rsync-remote.log 2>&1

# Weekly snapshot to remote (uses the snapshot script above):
0 3 * * 0 /usr/local/bin/snapshot-backup.sh >> /var/log/rsync-snapshot.log 2>&1

Log rotation prevents these log files from growing unbounded. Add a logrotate config:

# /etc/logrotate.d/rsync-backup
/var/log/rsync-*.log {
    weekly
    rotate 8
    compress
    missingok
    notifempty
}

Bandwidth and Resource Limiting

# Limit to 10 MB/s (value is in KB/s):
rsync -avz --bwlimit=10240 /source/ user@server:/dest/

# 1 MB/s limit for background backup over metered connection:
rsync -avz --bwlimit=1024 /source/ user@server:/dest/

# Reduce CPU priority so backup doesn't impact foreground work:
nice -n 19 rsync -avz /source/ /dest/

# Combine bandwidth and priority limiting:
nice -n 19 ionice -c 3 rsync -avz --bwlimit=5120 /source/ user@server:/dest/
# ionice -


Go up