ZetCode

Linux rsync Command

last modified October 23, 2026

The rsync command synchronizes files and directories, locally or between machines over SSH. On repeated runs, it transfers only what has changed: by default, it skips files whose size and modification time match the destination, and over the network, it sends only the changed parts of modified files (the delta-transfer algorithm). This makes it the standard tool for backups, mirroring, and deployments.

The syntax is rsync [options] source... destination, where a remote location is written as host:path. The examples use rsync 3.2.7 on Debian 12, with a sample project directory and a second machine reachable as web, an alias defined in ~/.ssh/config.

Copying a directory

The -a (archive) option copies recursively and preserves permissions, times, symbolic links, and, when run as root, owners. The -v option lists the transferred files.

rsync -av project/ backup/

The destination directory is created if needed. Hidden files such as .editorconfig are included. The summary shows how many bytes were sent and received; the speedup is the ratio of the total size to the transferred amount, which is about 1 for a first copy.

$ rsync -av project/ backup/
sending incremental file list
created directory backup
./
.editorconfig
readme.txt
cache/
cache/page.tmp
docs/
docs/manual.txt
images/
images/logo.png
logs/
logs/app.log

sent 116,085 bytes  received 190 bytes  232,550.00 bytes/sec
total size is 115,530  speedup is 0.99

The trailing slash

A trailing slash on the source changes what is copied.

rsync -a project backup2/
rsync -a project/ backup3/
ls backup2 backup3
rsync -a project/ deploy/2026/site/
rsync -a --mkpath project/ deploy/2026/site/
ls deploy/2026/site

Without the slash, the directory itself is copied into the destination (backup2/project). With the slash, its contents are copied (think of it as project/*, including hidden files). A slash on the destination makes no difference. rsync creates only the last component of a destination path; the --mkpath option, added in rsync 3.2.3, creates the missing parents too.

$ rsync -a project backup2/
$ rsync -a project/ backup3/
$ ls backup2 backup3
backup2:
project

backup3:
cache
docs
images
logs
readme.txt
$ rsync -a project/ deploy/2026/site/
rsync: [Receiver] mkdir "/home/jano/work/deploy/2026/site" failed: No such file or directory (2)
rsync error: error in file IO (code 11) at main.c(800) [Receiver=3.2.7]
$ rsync -a --mkpath project/ deploy/2026/site/
$ ls deploy/2026/site
cache
docs
images
logs
readme.txt

Transferring only changes

Running the same command again transfers nothing, unless files have changed.

rsync -av project/ backup/
echo "Version 2" >> project/readme.txt
rsync -av project/ backup/

The second run finds nothing to do and sends only the file list. After a change, only readme.txt is transferred. This is why a daily backup with rsync is fast even for large directories.

$ rsync -av project/ backup/
sending incremental file list

sent 246 bytes  received 16 bytes  524.00 bytes/sec
total size is 115,530  speedup is 440.95
$ echo "Version 2" >> project/readme.txt
$ rsync -av project/ backup/
sending incremental file list
readme.txt

sent 330 bytes  received 39 bytes  738.00 bytes/sec
total size is 115,540  speedup is 313.12

Itemizing changes

The -i (--itemize-changes) option explains why each file is transferred.

echo "error: disk full" >> project/logs/app.log
touch project/docs/manual.txt
rsync -ai project/ backup/

In >f.st......, > means the file is sent, f that it is a regular file, s that the size differs, and t that the modification time differs. The manual has only a new time, but the quick check cannot tell whether its content changed, so the file is transferred; over the network, the delta-transfer algorithm would find that almost nothing needs to be sent. Other letters include c (checksum), p (permissions), and o/g (owner/group).

$ echo "error: disk full" >> project/logs/app.log
$ touch project/docs/manual.txt
$ rsync -ai project/ backup/
>f..t...... docs/manual.txt
>f.st...... logs/app.log

Dry run

The -n (--dry-run) option shows what would be done without changing anything.

rm project/cache/page.tmp
echo "new" > project/notes.txt
rsync -av --delete --dry-run project/ backup/
ls backup/cache

The output says that notes.txt would be copied and cache/page.tmp deleted, and ends with (DRY RUN). The file is still in the backup. Always do a dry run before commands with --delete.

$ rm project/cache/page.tmp
$ echo "new" > project/notes.txt
$ rsync -av --delete --dry-run project/ backup/
sending incremental file list
./
notes.txt
deleting cache/page.tmp
cache/

sent 312 bytes  received 47 bytes  718.00 bytes/sec
total size is 115,557  speedup is 321.89 (DRY RUN)
$ ls backup/cache
page.tmp

Mirroring with --delete

By default, rsync never deletes files in the destination. The --delete option removes files that no longer exist in the source, so the destination becomes a mirror.

rsync -av --delete project/ backup/

Be careful with the source path: a typo or an empty source directory makes rsync delete the whole destination. Excluded files are protected from deletion unless --delete-excluded is used.

$ rsync -av --delete project/ backup/
sending incremental file list
./
notes.txt
deleting cache/page.tmp
cache/

sent 356 bytes  received 63 bytes  838.00 bytes/sec
total size is 115,557  speedup is 275.79

Excluding and including files

The --exclude option skips files that match a pattern. --include rules, given before a broader exclude, keep specific files.

rsync -av --exclude='*.log' --exclude='images/' project/ site/
rsync -avm --include='*/' --include='*.txt' --exclude='*' project/ texts/

A pattern ending with a slash matches only directories. The rules are checked in order, and the first match wins. The second command copies only .txt files: '*/' lets rsync descend into every directory, '*.txt' includes the text files, and '*' excludes everything else. The -m (--prune-empty-dirs) option drops the directories that would remain empty. Longer lists of patterns can be kept in a file and read with --exclude-from.

$ rsync -av --exclude='*.log' --exclude='images/' project/ site/
sending incremental file list
created directory site
./
.editorconfig
notes.txt
readme.txt
cache/
docs/
docs/manual.txt
logs/

sent 13,559 bytes  received 142 bytes  27,402.00 bytes/sec
total size is 13,132  speedup is 0.96
$ rsync -avm --include='*/' --include='*.txt' --exclude='*' project/ texts/
building file list ... done
created directory texts
./
notes.txt
readme.txt
docs/
docs/manual.txt

sent 13,468 bytes  received 107 bytes  27,150.00 bytes/sec
total size is 13,121  speedup is 0.97

Showing progress

--info=progress2 shows the progress of the whole transfer, and --progress the progress of each file.

rsync -a --info=progress2 video.bin media/
rsync -a --progress --bwlimit=5000 video.bin web:/tmp/

In a terminal, the progress line is updated in place; the output shows its final state. The local copy of the 50 MB file is limited only by the disk. The upload to web is limited to 5000 KiB/s by --bwlimit, so it takes about ten seconds.

$ rsync -a --info=progress2 video.bin media/
     50,000,000 100%    1.45GB/s    0:00:00 (xfr#1, to-chk=0/1)
$ rsync -a --progress --bwlimit=5000 video.bin web:/tmp/
sending incremental file list
video.bin
     50,000,000 100%    4.90MB/s    0:00:09 (xfr#1, to-chk=0/1)

Comparing contents with checksums

The quick check compares only size and modification time. The -c (--checksum) option compares the contents.

printf 'AAAA\n' > a/config.ini
printf 'BBBB\n' > b/config.ini
touch -r a/config.ini b/config.ini
rsync -ai a/ b/
cat b/config.ini
rsync -aic a/ b/
cat b/config.ini

The two files have the same size and, thanks to touch -r, the same modification time, so the quick check considers them equal and the outdated b/config.ini is kept. With -c, rsync reads and hashes both files, finds the difference (c in the itemized output), and copies the file. Checksums are slower, because every file must be read on both sides.

$ printf 'AAAA\n' > a/config.ini
$ printf 'BBBB\n' > b/config.ini
$ touch -r a/config.ini b/config.ini
$ rsync -ai a/ b/
$ cat b/config.ini
BBBB
$ rsync -aic a/ b/
>fc........ config.ini
$ cat b/config.ini
AAAA

Keeping backups of replaced files

The --backup option keeps files that would be overwritten or deleted. --backup-dir collects them in a separate directory.

echo "Version 3" >> project/readme.txt
rsync -a --backup --backup-dir=../backup-old project/ backup/
ls backup-old
cat backup-old/readme.txt

A relative --backup-dir is relative to the destination, so the old version of readme.txt ends up in backup-old next to backup. Without --backup-dir, the old files stay next to the new ones with a ~ suffix.

$ echo "Version 3" >> project/readme.txt
$ rsync -a --backup --backup-dir=../backup-old project/ backup/
$ ls backup-old
readme.txt
$ cat backup-old/readme.txt
Project readme
Version 2

Synchronizing over SSH

When the source or the destination contains a host name, rsync connects through SSH and starts rsync on the other side, which must therefore be installed there.

rsync -avz project/ web:/home/jano/project-copy/
ssh web 'ls -A project-copy'
rsync -av web:/home/jano/project-copy/docs/ restored-docs/

The first command uploads the project; -z compresses the data in transit, which helps on slow links but not on a fast LAN. The third command downloads a directory. The host alias, user, port, and key come from ~/.ssh/config; other SSH options can be passed with -e, for example -e 'ssh -p 2222'.

$ rsync -avz project/ web:/home/jano/project-copy/
sending incremental file list
./
notes.txt
readme.txt
cache/
docs/manual.txt
logs/app.log

sent 467 bytes  received 238 bytes  1,410.00 bytes/sec
total size is 115,567  speedup is 163.92
$ ssh web 'ls -A project-copy'
cache
docs
.editorconfig
images
logs
notes.txt
readme.txt
$ rsync -av web:/home/jano/project-copy/docs/ restored-docs/
receiving incremental file list
created directory restored-docs
./
manual.txt

sent 46 bytes  received 13,219 bytes  26,530.00 bytes/sec
total size is 13,092  speedup is 0.99

Limiting bandwidth

The --bwlimit option sets the maximum transfer rate, in KiB per second unless a suffix is given.

head -c 1000000 /dev/urandom > upload.bin
time rsync -a --bwlimit=250 upload.bin web:/tmp/

At 250 KiB/s, the 1 MB file takes about four seconds. Limiting the rate keeps a large transfer from saturating a shared connection.

$ head -c 1000000 /dev/urandom > upload.bin
$ time rsync -a --bwlimit=250 upload.bin web:/tmp/

real	0m4.136s
user	0m0.113s
sys	0m0.026s

Incremental snapshots with hard links

The --link-dest option compares the source with an earlier backup and hard-links unchanged files to it instead of copying them.

mkdir snapshots
rsync -a project/ snapshots/day1/
echo "Version 4" >> project/readme.txt
rsync -a --link-dest=../day1 project/ snapshots/day2/
ls -li snapshots/day1/images/logo.png snapshots/day2/images/logo.png
ls -li snapshots/day1/readme.txt snapshots/day2/readme.txt
du -sh snapshots/day1 snapshots/day2

The unchanged logo.png has the same inode number in both snapshots and a link count of 2, so it is stored once. The changed readme.txt is a separate file. Each snapshot looks like a full copy, but du shows that the second one uses only 24K of new space. This is the technique behind tools such as rsnapshot and Time Machine-style backups. Like --backup-dir, a relative --link-dest path is relative to the destination.

$ mkdir snapshots
$ rsync -a project/ snapshots/day1/
$ echo "Version 4" >> project/readme.txt
$ rsync -a --link-dest=../day1 project/ snapshots/day2/
$ ls -li snapshots/day1/images/logo.png snapshots/day2/images/logo.png
2628596 -rw-r--r-- 2 jano jano 102400 Oct 20 09:00 snapshots/day1/images/logo.png
2628596 -rw-r--r-- 2 jano jano 102400 Oct 20 09:00 snapshots/day2/images/logo.png
$ ls -li snapshots/day1/readme.txt snapshots/day2/readme.txt
2628594 -rw-r--r-- 1 jano jano 35 Oct 23 13:39 snapshots/day1/readme.txt
2628603 -rw-r--r-- 1 jano jano 45 Oct 23 13:39 snapshots/day2/readme.txt
$ du -sh snapshots/day1 snapshots/day2
152K	snapshots/day1
24K	snapshots/day2

Moving files

The --remove-source-files option deletes source files after they have been transferred successfully.

mkdir outbox; touch outbox/report1.csv outbox/report2.csv
rsync -av --remove-source-files outbox/ archive/
ls outbox

The files are moved, but the source directories are left in place. This is useful for queues, for example to upload finished reports to a server and remove them locally only when the upload succeeded.

$ mkdir outbox; touch outbox/report1.csv outbox/report2.csv
$ rsync -av --remove-source-files outbox/ archive/
sending incremental file list
created directory archive
./
report1.csv
report2.csv

sent 181 bytes  received 135 bytes  632.00 bytes/sec
total size is 0  speedup is 0.00
$ ls outbox

Transfer statistics

The --stats option prints a detailed summary.

echo "Version 5" >> project/readme.txt
rsync -a --stats project/ backup/

Of the 11 entries, only one regular file was transferred. Literal data is data sent as is, and Matched data is data the receiver could reuse from its old copy. For local copies, rsync sends whole files, so all data is literal.

$ echo "Version 5" >> project/readme.txt
$ rsync -a --stats project/ backup/

Number of files: 11 (reg: 6, dir: 5)
Number of created files: 0
Number of deleted files: 0
Number of regular files transferred: 1
Total file size: 115,587 bytes
Total transferred file size: 55 bytes
Literal data: 55 bytes
Matched data: 0 bytes
File list size: 0
File list generation time: 0.001 seconds
File list transfer time: 0.000 seconds
Total bytes sent: 405
Total bytes received: 39

sent 405 bytes  received 39 bytes  888.00 bytes/sec
total size is 115,587  speedup is 260.33

Best practices for rsync

Source

rsync manual page

In this article, we have used the rsync command to copy and mirror directories, filter files, compare checksums, keep backups and hard-linked snapshots, and synchronize with a remote machine over SSH.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Linux tutorials.