When you back up servers, deploy applications or synchronise directories between machines, rsync is the reference tool. Unlike cp or scp, rsync only transfers the differences: if 1% of a file changed, only that 1% crosses the network. The result: fast backups, resumable after a cut, and fine control over what gets copied.
What you will learn
- Synchronise directories locally with
-av, and remotely with-avz - Transfer to and from a remote server over SSH
- Exclude files with
--excludeand--exclude-from - Use
--deletefor an exact mirror (with precautions) - Create incremental backups with
--link-dest
Where rsync fits among Linux tools
rsync belongs to the transfer and backup tools of Linux. Each tool has its speciality, and the first table places them side by side. The second one answers the practical question: for this situation, which tool should you pick? rsync wins as soon as the transfer repeats, because each pass only sends what changed.
| Command | Speciality | When to use it |
|---|---|---|
rsync | Smart synchronisation (delta) | Backups, deployments, large files |
cp | Simple local copy | One-off files, simple scripts |
scp | Remote copy over SSH | Quick one-shot transfer |
sftp | Interactive transfer session over SSH | Explore a server, several files in one session |
tar | Archiving with compression | Create archives, full backups |
dd | Bit-for-bit copy | Disk images, low-level cloning |
When to use rsync rather than the others:
| Situation | Best tool | Why |
|---|---|---|
| Daily backup | rsync | Only transfers the changes |
| Copy one file to a server | scp or rsync | scp simpler, rsync resumes if cut |
| Two-way synchronisation | unison | rsync is one-way |
| Clone a disk | dd | Exact bit-for-bit copy |
| Compressed archive to send | tar + ssh | A single file to transfer |
Frequent combinations:
rsync combines with three everyday tools: cron to schedule, find to choose the files, ssh for the transport. The second form passes the find list to --files-from, which keeps the tree: an -exec rsync {} file by file would drop everything flat at the root of the destination.
# rsync + cron: automatic daily backup0 2 * * * rsync -avz --delete /data/ backup-srv:/backups/
# rsync + find: back up only the files changed in the last 24 hfind /data -mtime -1 -type f -printf '%P\n' | rsync -av --files-from=- /data/ backup:/data/
# rsync + custom ssh: non-standard portrsync -avz -e "ssh -p 2222" source/ user@server:/dest/Understand rsync in 2 minutes
The model below sums up the command: a source, filters that decide what goes through, a comparison with the destination, then sending only the differences. Keep the order in mind, it is the same for a local copy and for a transfer over SSH.
🧠 Mental model: How rsync works
rsync = Source → [Filters] → Destination (only transfers the differences)
Key points
- rsync compares files by size and date (or checksum with -c)
- Only the changed blocks are transferred (delta algorithm)
- The trailing slash on the source changes everything: /source/ ≠ /source
- Archive mode (-a) preserves permissions, timestamps, symbolic links
- Always test with --dry-run before --delete
- SSH is the default transport for remote transfers
Golden rules
Essential vocabulary
- -a (archive)
- Preserves permissions, timestamps, links, recursive
- -v (verbose)
- Shows the files processed
- -z (compress)
- Compresses the data during the transfer
- -n (dry-run)
- Simulates without changing anything
- --delete
- Deletes the files missing from the source
- --progress
- Shows the transfer progress
📚 Going further: 12 advanced options
- --exclude
- Exclude files/directories by pattern
- --exclude-from
- Read the exclusions from a file
- --include
- Include explicitly: the first matching rule wins, put it before --exclude
- --partial
- Keep partially transferred files
- --append
- Append to the end of a file that only grows (logs), without checking the existing part
- --bwlimit
- Limit the bandwidth (KiB/s)
- --link-dest
- Incremental backups with hard links
- -e ssh
- Set the SSH command (port, key...)
- --inplace
- Update files in place (risky)
- -c (checksum)
- Compare by checksum instead of size/date
- --max-delete
- Stop deletions beyond N files (exit code 25)
- -P
- Equivalent to --partial --progress
The delta algorithm: rsync splits files into blocks, computes checksums, and only transfers the changed blocks. On a 1 GB file changed by 1%, only ~10 MB of data travel, plus the checksums. This algorithm only helps across the network: between two local paths, the manual states that --whole-file is the default, because reading both files would cost more than copying again.
Minimal syntax
Two rules govern this syntax. The source is always on the left, the destination on the right: swapping them overwrites your working data with a stale backup. And only one of the two ends can be remote: a server-to-server transfer is refused with The source and destination cannot both be remote. and exit code 1. Also remember that the trailing slash on the source changes the result, detailed below.
rsync [OPTIONS] SOURCE DESTINATIONThe 3 usage scenarios
Going from local to remote changes nothing in the syntax: you simply add user@server: in front of the path. rsync detects the colon and automatically switches to SSH as the transport, with no extra option. It also explains a common error: a local path containing a colon is read as a remote destination. rsync -av a:b/ /tmp/y/ thus tries to connect to a machine called a and answers ssh: Could not resolve hostname a. Prefix the path with ./ to remove the ambiguity.
| Scenario | Syntax | Example |
|---|---|---|
| Local → Local | rsync source/ dest/ | rsync -av ~/docs/ /backup/docs/ |
| Local → Remote | rsync source/ user@srv:dest/ | rsync -avz ~/data/ admin@srv:/backup/ |
| Remote → Local | rsync user@srv:source/ dest/ | rsync -avz srv:/logs/ ~/local-logs/ |
The trailing slash trap
This is the number one source of errors with rsync, because it produces no message: the command succeeds, but the tree arrives shifted by one level. The table and the block below show both results side by side.
| Command | Result |
|---|---|
rsync -av source dest/ | Creates dest/source/... (copies the directory) |
rsync -av source/ dest/ | Copies the content into dest/... |
Simple rule: slash on the source = copies the content. No slash = copies the directory.
rsync -av ~/project /backup/
# With slash, result: /backup/src/... (content directly)rsync -av ~/project/ /backup/The essential options
These six options cover almost every use, and the order of the table reflects how often they are really used. Two of them deserve particular attention. -a is not a mere abbreviation of "recursive": it preserves permissions, owners, timestamps and symbolic links, which is essential for a restorable backup. -n is the safeguard: run once before each destructive command, it costs a few seconds and prevents irreversible losses.
| Option | Meaning | Usage |
|---|---|---|
-a | Archive | Standard mode: recursive + preserves permissions, timestamps, links |
-v | Verbose | Shows the files processed |
-z | Compress | Compresses during the transfer (useful over a network) |
-n | Dry-run | Simulates without changing (essential before --delete) |
-P | Progress + Partial | Shows progress + keeps partial files |
--delete | Delete | Removes from dest what no longer exists in source |
The standard combo:
# Basic local backuprsync -av ~/Documents/ /media/backup/Documents/
# Remote transfer with compressionrsync -avz ~/project/ deployer@prod:/var/www/app/
# Test before deletionrsync -avn --delete ~/master/ /backup/mirror/Delta transfer in action
The advantage of rsync over a plain cp or scp: it only transfers the differences. The first synchronisation copies everything, the following ones only touch what changed. Demonstration on three files, output recorded on Debian 13 (rsync 3.5.0):
# 1. First sync: everything is copied$ rsync -av src/ dst/sending incremental file listcreated directory dst./app.confdata.txtlogs/logs/app.log
sent 320 bytes received 114 bytes 868.00 bytes/sectotal size is 24 speedup is 0.06
# 2. Nothing changed: no file is transferred$ rsync -av src/ dst/sending incremental file list
sent 149 bytes received 13 bytes 324.00 bytes/sectotal size is 24 speedup is 0.15
# 3. A single file changed: only that one goes again$ echo "listen 8443 ssl" > src/app.conf$ rsync -av src/ dst/sending incremental file listapp.conf
sent 220 bytes received 36 bytes 512.00 bytes/sectotal size is 28 speedup is 0.11That is what makes rsync fast and thrifty on large volumes: on a multi-GB backup across the network, only the changed blocks travel, not whole files. The absence of any file name at step 2 proves that nothing was sent again.
To decide that a file changed, rsync does not read its content: it compares the size and the modification date. A file rewritten with the same number of bytes and the same date, to the second, is therefore silently skipped, which the measurement reproduces by copying the date with touch -r. The -c option forces the comparison by checksum and catches this case, at the cost of a full read on both sides.
Exclude files and directories
Two methods let you skip some files: --exclude options on the command line for two or three patterns, an exclusion file as soon as the list grows or is shared between several scripts. The patterns follow the glob syntax, and rsync interprets them, not the shell.
Method 1: --exclude options
rsync -av --exclude '*.log' --exclude 'cache/' source/ dest/Method 2: exclusion file
# Create a .rsyncignore filecat > .rsyncignore << 'EOF'*.log*.tmpcache/.git/node_modules/EOF
# Use the filersync -av --exclude-from=.rsyncignore source/ dest/Rule order: --include must come before --exclude, because rsync applies the first rule that matches. Measured on the three distributions: the wrong form copies no file and reports nothing.
# Wrong: exclude * matches everything before includersync -av --exclude '*' --include '*.txt' source/ dest/
# Correct: include firstrsync -av --include '*.txt' --exclude '*' source/ dest/Exact mirror with --delete
The --delete option makes the destination identical to the source by removing the files that no longer exist on the source side. The danger is real: a path mistake turns the synchronisation into an erasure, without confirmation. Hence the two-step rule, always in this order: preview with -n, then run.
# Step 1: ALWAYS previewrsync -avn --delete ~/source/ /backup/mirror/# Check the list of files that would be deleted
# Step 2: run only if the list is rightrsync -av --delete ~/source/ /backup/mirror/Variants of --delete:
Since rsync 3.0.0, --delete alone is equivalent to --delete-during: deletions happen during the transfer, directory by directory, not before. --delete-before still exists but forces the old non-incremental scan, which uses more memory. The real safety net, by the way, is none of these variants but --max-delete=N, which stops deletions beyond N files: rsync prints Deletions stopped due to --max-delete limit (2 skipped) and exits with code 25.
| Option | Behaviour |
|---|---|
--delete | Deletes during the transfer, directory by directory (equivalent to --delete-during since rsync 3.0.0) |
--delete-before | Deletes before the transfer starts, at the price of the old non-incremental scan |
--delete-after | Deletes once the transfer is finished |
--delete-excluded | Also deletes the files excluded from the transfer |
--max-delete=N | Stops deletions beyond N files, exit code 25 |
Remote transfers over SSH
SSH is the default transport for remote transfers: rsync starts ssh to the designated machine, then launches a second rsync at the other end. rsync must therefore be installed on both sides, and everything your SSH client can do (keys, ~/.ssh/config, jump host) applies with no extra option.
# Basic syntaxrsync -avz source/ user@server:/path/dest/
# Fetch from a serverrsync -avz user@server:/var/log/ ~/server-logs/Custom SSH (different port, specific key): the -e option replaces the transport command, which lets you pass it any ssh option.
# Non-standard SSH portrsync -avz -e "ssh -p 2222" source/ user@srv:/dest/
# Specific SSH keyrsync -avz -e "ssh -i ~/.ssh/backup_key" source/ backup@srv:/data/
# Combinedrsync -avz -e "ssh -p 2222 -i ~/.ssh/backup_key" source/ user@srv:/dest/Transfer resume and bandwidth
For large files or unstable connections, three options make the difference. -P keeps the partial file and shows the progress, --bwlimit caps the rate in KiB/s (units of 1,024 bytes according to the manual), and --append has a much narrower use than it seems.
# -P = --partial + --progress# --partial keeps partially transferred filesrsync -avP bigfile.iso user@server:/data/
# Limit the bandwidth (in KiB/s)rsync -avz --bwlimit=1000 source/ user@server:/dest/
# --append: only for files that just grow (logs)rsync -av --append /var/log/app/ user@server:/archives/logs/-P is enough to resume a broken transfer: the partial file kept serves as the
basis for the delta algorithm at the next pass. --append is not a generic
resume mode: it adds bytes at the end without ever checking the content
already there, and therefore silently corrupts any file that was changed
anywhere other than at its end. Keep it for strictly growing files, or use
--append-verify, which checks the existing part by checksum.
Incremental backups with --link-dest
--link-dest creates backups that look complete but only use space for the changed files: each file identical to the one in the reference directory becomes a hard link to it. A relative path is resolved from the destination, and the parent directory of the destination must exist, otherwise rsync answers mkdir "/backup/today" failed: No such file or directory (2) with code 11.
# Backup structure/backup/├── 2024-01-01/ # Full backup├── 2024-01-02/ # Hard links to 2024-01-01 + new files└── 2024-01-03/ # Hard links to 2024-01-02 + new files
# Commandrsync -av --link-dest=/backup/2024-01-01 ~/data/ /backup/2024-01-02/Incremental backup script:
At the first run, the previous day's backup does not exist yet: rsync prints --link-dest arg does not exist: /backup/2026-09-29, makes a full copy and exits with code 0. The script therefore does not need to handle that case separately.
#!/bin/bashBACKUP_DIR="/backup"SOURCE="/home/user/data"TODAY=$(date +%Y-%m-%d)YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
mkdir -p "$BACKUP_DIR"# Create the backup using the previous one as referencersync -av --link-dest="$BACKUP_DIR/$YESTERDAY" \ "$SOURCE/" "$BACKUP_DIR/$TODAY/"Space savings: with 100 GB of data of which 1% changes per day, seven daily backups take ~106 GB (one full copy, then six times 1 GB) instead of 700 GB. The calculation assumes that this 1% fits in few files: a hard link covers a whole file, and a file changed by a single byte is copied in full.
Automation with cron
cron runs the command at a fixed time, without a terminal: everything rsync prints must therefore go to a log, or a failure goes unnoticed. In a crontab, the % character has a special meaning and must be escaped as \%, hence the form of the date below.
# Edit the crontabcrontab -e
# Add (backup at 2 a.m.)0 2 * * * rsync -avz --delete /data/ backup-server:/backups/$(date +\%Y-\%m-\%d)/ >> /var/log/rsync-backup.log 2>&1Complete script with rotation:
The rotation shifts each snapshot by one day, then freezes the current state with cp -al, which creates hard links instead of copying. Each mv is conditioned on the directory existing: without that precaution, the first six nights would fail for lack of day.6. Replayed over nine consecutive nights on Debian 13: seven snapshots, the most recent in day.1, and the unchanged files shared by hard links.
#!/bin/bash# backup.sh - Backup with a 7-day rotation
REMOTE="backup@srv:/backups"SOURCE="/home/user/data"LOG="/var/log/rsync-backup.log"
echo "=== Backup started: $(date) ===" >> "$LOG"
# Backuprsync -avz --delete "$SOURCE/" "$REMOTE/current/" >> "$LOG" 2>&1
# Rotation (keep 7 days)ssh backup@srv 'cd /backups && rm -rf day.7 && for i in 6 5 4 3 2 1; do if [ -d "day.$i" ]; then mv "day.$i" "day.$((i + 1))"; fi done && cp -al current day.1'
echo "=== Backup completed: $(date) ===" >> "$LOG"Pitfalls to avoid
These mistakes come back constantly with rsync. Knowing them in advance saves you data losses and hours of diagnosis. Each one is presented the same way: the command that goes wrong, the symptom, the cause, then the correct form.
The trailing slash trap
rsync -av /source /destinationSymptom: an unexpected source/ directory appears inside destination/.
Cause: without a trailing slash, rsync copies the directory itself; with a slash, it copies its content.
rsync -av /source/ /destination/--delete without a prior test
rsync -av --delete source/ dest/Symptom: important files disappear from the destination.
Cause: --delete removes everything that is not present in the source, so a path mistake becomes destructive.
rsync -av --delete --dry-run source/ dest/ # check, then run again without --dry-runSwapping source and destination
rsync -av --delete backup/ source/Symptom: the working files are overwritten by an old backup.
Cause: confusion between source and destination; rsync always copies what is on the left to what is on the right.
rsync -av source/ backup/Order of --include / --exclude
rsync -av --exclude "*" --include "*.txt" source/ dest/Symptom: no file is copied despite the --include.
Cause: rsync applies the rules in order; --exclude "*" catches everything before --include is evaluated.
rsync -av --include "*.txt" --exclude "*" source/ dest/Permission errors on the destination
rsync -av ~/files/ /opt/Symptom: one line rsync: [receiver] mkstemp "/opt/.app.conf.ms5nIm" failed: Permission denied (13) per file, then exit with code 23.
Cause: the current user is not allowed to write in the destination. rsync creates each file under a temporary name before renaming it, hence the .app.conf.XXXXXX name. Not to be confused with the owner: a non-root account cannot keep it, and -a then assigns the files to the current account without any error.
rsync -av ~/files/ ~/backup/ # accessible destinationrsync -av --rsync-path="sudo rsync" ~/files/ admin@srv:/opt/ # remote side, if sudo is set up for rsyncSpaces in remote paths
rsync -av user@srv:/path/with spaces/ local/Symptom: unexpected files, or a path not found on the remote side.
Cause: the local shell splits the argument on the space before rsync even sees it.
rsync -av "user@srv:/path/with spaces/" local/Plain quotes are enough: since version 3.2.4, rsync escapes the active
characters itself, spaces included, in the arguments passed to the remote
shell. Adding a backslash by hand leads to a double escape: measured on rsync
3.2.7 and 3.5.0, the command answers
change_dir "/srv/with\ spaces" failed: No such file or directory (2) and exits
with code 23.
On a server running an older rsync, the --old-args option restores the old
behaviour.
Keeping rsync up to date: the 2026 security reflex
rsync is a mature tool, but 2026 has been a busy year for its security. Version 3.4.3 (20 May 2026) fixes six CVEs, including CVE-2026-43618 (CVSS v3.1 8.1, integer overflow in the compressed-token decoder, reachable from a plain pull) and CVE-2026-29518 (CVSS v4.0 7.3, TOCTOU race condition in daemon mode without chroot). Version 3.5.0 (13 August 2026), presented by its maintainers as a "major security release", then fixes 33 issues from an audit of path handling and of the daemon protocol: most exploit a symbolic link planted by a local user that an rsync run as root ends up following. Version 3.5.1 (21 September 2026) fixes the 3.5.0 regressions (source: the project's NEWS file).
The version number is not enough to judge a machine: distributions backport fixes without changing the upstream version. Measured on 2026-09-30:
| Distribution | rsync --version |
|---|---|
| Ubuntu 24.04 | 3.2.7, protocol 31 |
| Debian 13 | 3.5.0, protocol 32 |
| AlmaLinux 10 | 3.5.0, protocol 32 |
rsync --version | head -1# rsync version 3.5.0 protocol version 32On Ubuntu, the distribution's security tracking is what counts (apt changelog rsync), not the 3.2.7 displayed. Two reflexes follow. First, update rsync on the machines that expose it (backup servers, transfer relays). Second, never expose a bare rsync daemon: most uses go through SSH (rsync -e ssh), which encrypts and authenticates the transfer. Daemon mode (rsync://) remains reserved for trusted networks, ideally behind a firewall and with chroot.
Hands-on practice
This lab has you handle rsync on a real tree, from basic synchronisation to incremental backup. Allow 15 minutes.
Prepare the ground
The script below creates a test tree with files of various sizes and dates, plus two trap cases: files to exclude and a name containing a space.
# Create the labmkdir -p ~/rsync-lab/{source,backup,remote-sim}cd ~/rsync-lab/source
# Create various filesecho "Main configuration" > config.confecho "Application logs" > app.logdd if=/dev/zero of=data.bin bs=1M count=5 2>/dev/nullmkdir -p docsecho "Documentation" > docs/readme.mdecho "Notes" > docs/notes.txt
# Files to excludeecho "Temporary" > temp.tmpmkdir -p cacheecho "Cache data" > cache/data.cache
# File with a space in its nameecho "Important report" > "report 2024.pdf"
# Old filetouch -d "30 days ago" docs/old-doc.md
echo "Lab created in ~/rsync-lab"ls -laR ~/rsync-lab/sourceTo clean everything up at the end: rm -rf ~/rsync-lab.
The 8 lab steps
The steps chain on the same tree and the state obtained at each step feeds the next one: do not take them out of order. After each one, compare the two directories with diff -r ~/rsync-lab/source ~/rsync-lab/backup to see the real effect of the option used rather than trusting the message displayed.
-
Basic synchronisation, copy the content of
source/tobackup/in archive mode:rsync -av ~/rsync-lab/source/ ~/rsync-lab/backup/. All files are copied with their attributes preserved. -
Test with
--dry-run, simulate a synchronisation with--deletewithout changing anything:rsync -av --delete --dry-run ~/rsync-lab/source/ ~/rsync-lab/backup/. The output shows what would be done, without touching the files. -
Exclude files, synchronise while skipping the
.tmpfiles and thecache/directory:rsync -av --exclude "*.tmp" --exclude "cache/" ~/rsync-lab/source/ ~/rsync-lab/backup/.temp.tmpandcache/are not copied. -
Exact mirror with
--delete, create an orphan file then synchronise:touch ~/rsync-lab/backup/orphan-file.txtfollowed byrsync -av --delete ~/rsync-lab/source/ ~/rsync-lab/backup/. The orphan file is deleted because it is missing from the source. -
Remote transfer (simulated locally), use compression:
rsync -avz ~/rsync-lab/source/ ~/rsync-lab/remote-sim/. For a real server, the destination would beuser@server:/path/, and with a custom SSH port:rsync -avz -e "ssh -p 2222" source/ user@server:/backup/. -
Show the progress, synchronise
data.binwith the progress bar:rsync -av --progress ~/rsync-lab/source/data.bin ~/rsync-lab/backup/. Size, speed and percentage are displayed during the transfer. -
Limit the bandwidth, transfer
data.binat 500 KiB/s maximum:rsync -avz --bwlimit=500 ~/rsync-lab/source/data.bin ~/rsync-lab/backup/. The transfer is capped, useful not to saturate the network. -
Incremental backup, create a backup with hard links to the previous one:
rsync -av --link-dest=../backup ~/rsync-lab/source/ ~/rsync-lab/backup-new/. Unchanged files become hard links, which saves disk space.
Progressive exercises
These exercises go from the basic reflex to a professional backup. Read the statement, look for the command, then unfold the solution to check.
Foundations level
The first two exercises set the two reflexes that prevent almost every loss: archive mode with the trailing slash, and simulation before any deletion.
Exercise 1, First synchronisation. Synchronise the ~/Documents directory to /media/backup/Documents while preserving the attributes.
Hint: -a for archive, -v to see what happens.
See the solution
rsync -av ~/Documents/ /media/backup/Documents/-a preserves everything (permissions, timestamps, links). Do not forget the trailing slash on the source.
Exercise 2, Test before running. Simulate a synchronisation with --delete without changing anything.
Hint: --dry-run (or -n) simulates without changing.
See the solution
rsync -av --delete --dry-run ~/source/ ~/backup/--dry-run is essential before any --delete command: it shows the real impact without risk.
Composition level
These three exercises combine the transfer with a filter, compression or an exact mirror.
Exercise 3, Excluding files. Synchronise while excluding all .log files and the cache/ directory.
Hint: --exclude can be used several times.
See the solution
rsync -av --exclude "*.log" --exclude "cache/" ~/source/ ~/backup/--exclude accepts glob patterns and can be repeated as many times as needed.
Exercise 4, Transfer to a remote server. Send the ~/project directory to user@server:/var/www/app with compression.
Hint: -z enables compression, useful over the network.
See the solution
rsync -avz ~/project/ user@server:/var/www/app/-z compresses the data during the transfer. SSH is the default transport.
Exercise 5, Exact mirror. Create an exact mirror of ~/master in /backup/mirror while deleting obsolete files.
Hint: --delete removes what no longer exists in the source.
See the solution
rsync -av --delete ~/master/ /backup/mirror/--delete makes the destination identical to the source. Always test with --dry-run first.
Industrialisation level
Here the transfer must hold in a real context: a non-standard SSH port and an unstable connection.
Exercise 6, SSH with a custom port. Synchronise to a server that uses SSH port 2222.
Hint: -e lets you specify the SSH command.
See the solution
rsync -avz -e "ssh -p 2222" ~/data/ user@server:/backup/-e "ssh -p 2222" changes the port. The same syntax accepts -i for a dedicated private key.
Exercise 7, Transfer with resume. Transfer a large file with the ability to resume if the connection is cut.
Hint: -P combines --partial and --progress.
See the solution
rsync -avP bigfile.iso user@server:/data/-P keeps partial files and shows the progress: the transfer resumes where it stopped.
Security level
The last exercise builds an incremental backup, the one that lets you go back to an earlier state without multiplying the disk space.
Exercise 8, Incremental backup. Create a dated backup using hard links to the previous one.
Hint: --link-dest points to the previous backup.
See the solution
rsync -av --link-dest=/backup/2024-01-01 ~/data/ /backup/2024-01-02/--link-dest saves a huge amount of space: only the changed files are really duplicated.
Troubleshooting
When a transfer fails or behaves badly, these diagnosis methods and these frequent errors cover the vast majority of cases. Start with the method, which tells you where to look, then match the message and the exit code against the table.
Diagnosis methods
These four commands each answer a different question. --dry-run answers "what is rsync going to do?", -vvv "why is this file sent again?", the separate SSH test "does the problem come from rsync or from the transport?", and --stats "how much actually travelled?". Always start by isolating the transport: an ssh user@server "ls" that fails makes any rsync-side diagnosis pointless.
# Simulate without changing anythingrsync -av --dry-run source/ dest/
# Very verbose mode: all the detailsrsync -avvv source/ dest/
# Test the SSH connection before rsyncssh -v user@server "ls /path/"
# Show the transfer statisticsrsync -av --stats source/ dest/Frequent errors
Two of these five errors concern the transport, not the copy itself: it is the reflex to keep when a remote transfer fails. rsync returns a distinct exit code for each family of problems, visible with echo $? right after the command and documented in the EXIT VALUES section of the manual: 1 for a syntax error, 10 for a socket error, 11 for a file I/O error, 12 for a protocol error, 23 for a partial transfer due to errors, 24 when source files vanished along the way, 25 when the --max-delete limit stopped the deletions. If the SSH connection fails, 255 comes back. Every message in the table was recorded on rsync 3.2.7 and 3.5.0.
| Error | Probable cause | Solution |
|---|---|---|
mkstemp "..." failed: Permission denied (13), code 23 | No write rights on the destination, or SELinux/AppArmor blocks | Aim at an accessible directory, or --rsync-path="sudo rsync" on the remote side |
ssh: connect to host srv port 22: Connection refused, code 255 | sshd stopped, firewall, or wrong SSH port | Start sshd, open the firewall, or give the right port with -e "ssh -p ..." |
failed to connect to srv (...): Connection refused (111), code 10 | rsync:// URL while no rsync daemon listens | Go through SSH (user@srv:/path) or start the daemon |
Host key verification failed | First connection, or reinstalled server (key changed) | ssh-keygen -R server then ssh user@server to accept the key |
write failed on "...": No space left on device (28), code 11 | Destination disk full or quota reached | Check with df -h, free some space or raise the quota |
rsync cheat sheet
Keep it at hand: the general syntax, the basic options and the filters used most every day.
Syntax and trailing slash
The last two rows are the ones to read again before each backup command. The difference between source/ and source triggers no error, it simply produces a tree shifted by one level, often discovered several backups later. The slash on the destination, on the other hand, has no effect.
| Syntax | Meaning | Example |
|---|---|---|
rsync [OPTIONS] SOURCE DEST | General form of the command | rsync -av source/ dest/ |
rsync ... user@host:path | Remote transfer over SSH | rsync -avz data/ user@srv:/backup/ |
source/ | Copies the content of source | rsync -av source/ dest/ |
source | Copies the source directory | rsync -av source dest/ |
Common options
These options combine freely, with one exception: -c forces the comparison by checksum of every file and cancels the size-and-date optimisation, which considerably lengthens the processing on large volumes. Only enable it to check a suspicious backup, never in a daily scheduled job.
| Syntax | Meaning | Example |
|---|---|---|
-a (archive) | Recursive + preserves everything | rsync -a source/ dest/ |
-v (verbose) | Shows the files processed | rsync -av source/ dest/ |
-z (compress) | Compresses during the transfer | rsync -avz source/ dest/ |
-n (dry-run) | Simulates without changing | rsync -avn --delete source/ dest/ |
-P | --partial + --progress | rsync -avP big.iso dest/ |
--bwlimit=RATE | Limits the bandwidth (KiB/s by default) | --bwlimit=1000 |
-c (checksum) | Compares by checksum | rsync -avc source/ dest/ |
--stats | Shows the statistics | rsync -av --stats source/ dest/ |
-e "ssh ..." | Customises SSH (port, key) | -e "ssh -p 2222" |
Synchronisation and filters
Filters are evaluated in the order they appear on the command line, and the first rule that matches wins. It is the cause of every surprise with --include and --exclude. The pattern follows the shell glob syntax, but rsync interprets it, not the shell: always wrap your patterns in quotes so they reach it intact.
| Syntax | Meaning | Example |
|---|---|---|
--delete | Deletes the files missing from the source | rsync -av --delete source/ dest/ |
--delete-after | Deletes after the transfer | rsync -av --delete-after source/ dest/ |
--update | Skips files that are newer on the destination | rsync -av --update source/ dest/ |
--exclude "pattern" | Excludes files | --exclude "*.log" |
--exclude-from=FILE | Exclusions from a file | --exclude-from=.rsyncignore |
--include "pattern" | Includes (put before --exclude) | --include "*.txt" --exclude "*" |
--link-dest=DIR | Hard links for incremental backups | --link-dest=/backup/prev |
Mastery checklist
You master rsync when you can tick each of these points without hesitating. A point you cannot tick sends you back to the matching section above.
- I can use
-a,-v,-zfor a standard synchronisation. - I can test with
--dry-runbefore running. - I can exclude files with
--exclude. - I can transfer to and from a remote server.
- I can use
--deletefor an exact mirror. - I can customise SSH (port, key).
- I can resume an interrupted transfer with
-P. - I can make incremental backups with
--link-dest. - I understand why the trailing slash on the source matters.
- I can diagnose an SSH connection error.
Key points
-avz: the standard combination (archive + verbose + compression)- Trailing slash:
/source/copies the content,/sourcecopies the directory --dry-run: ALWAYS test before--delete--link-dest: space-saving incremental backups-P: resume possible for interrupted transfers- Size and date: a file changed without altering either is skipped, unless you use
-c - Exit codes:
23partial transfer,25--max-deletelimit,255SSH failure
Knowledge check
Check that the essentials of this guide stuck. The questions cover only what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Chrony and NTP: synchronise a server's clock
A new full quiz with random questions
Work again only on the questions you missed
FAQ: frequently asked questions
The answers below go back over the points that come up most often, in question form: the syntax, the trailing slash, the filters, deletion with --delete and bandwidth. Each one stands on its own and refers to the measurements made above.
unison.rsync -av ~/docs/ /backup/docs/ # local
rsync -avz ~/data/ admin@srv:/backup/data/ # to a server, over SSH
cp and scp copy everything at each run. rsync only sends what changed since the last pass, which makes all the difference for a daily backup.| Need | Tool |
|---|---|
| One-off local copy | cp |
| One-off remote transfer | scp |
| Recurring backup or synchronisation | rsync |
| Large transfer over an unstable link | rsync -P |
-P), filters (--exclude), an exact mirror (--delete) and simulation (-n). For a single file copied once, cp or scp remain simpler.rsync [OPTIONS] SOURCE DESTINATION
The source is always on the left, the destination on the right: swapping them overwrites your data with an old backup. A remote path is written user@machine:/path, and only one of the two ends can be remote.rsync -av source/ destination/
rsync -avz source/ admin@srv:/backup/
-a (archive) groups several options: recursive copy, and preservation of permissions, modification times, symbolic links, special files, owner and group.A measured nuance: run by a non-root account, rsync cannot keep the original owner. The files then belong to the current account, without any error message. ACLs (-A) and extended attributes (-X) are not part of -a.rsync -av --exclude '*.log' --exclude 'cache/' source/ dest/
rsync -av --exclude-from=.rsyncignore source/ dest/
Wrap the patterns in quotes: rsync interprets them, not the shell.Rules apply in order, and the first one that matches wins. To copy only .txt files, write --include '*.txt' --exclude '*'. In the reverse order, --exclude '*' catches everything: rsync copies no file and reports nothing.| Command | Result |
|---|---|
rsync -av ~/project/ /backup/ |
The content of project lands in /backup/ |
rsync -av ~/project /backup/ |
The directory lands as /backup/project/ |
rsync -avz ~/data/ admin@srv:/backup/data/ # upload
rsync -avz admin@srv:/var/log/ ~/srv-logs/ # download
rsync -avz -e "ssh -p 2222 -i ~/.ssh/backup_key" ~/data/ admin@srv:/backup/
rsync must be installed on both sides. If SSH fails, rsync prints the SSH message then connection unexpectedly closed and exits with code 255.--delete makes the destination identical to the source by removing what no longer exists there. A path mistake then becomes an erasure, without confirmation.rsync -avn --delete ~/source/ /backup/mirror/ # 1. preview
rsync -av --delete --max-delete=50 ~/source/ /backup/mirror/ # 2. run with a ceiling
Beyond the ceiling, rsync prints Deletions stopped due to --max-delete limit and exits with code 25.rsync -avn --delete source/ dest/
-n (--dry-run) runs the whole comparison and shows what would be copied or deleted (deleting ... lines), without touching a single file. Combined with -v, it is the only way to check the real effect of a command before running it, especially with --delete.rsync -avz --bwlimit=1000 source/ admin@srv:/backup/
According to the manual, the --bwlimit value is expressed in units of 1,024 bytes per second when no suffix is given: 1000 therefore caps at about 1 MiB/s. A suffix is accepted, for example --bwlimit=1.5m. It is useful not to saturate a shared link during a daytime backup.