rsync Command in Linux with Practical Examples

If you move files around on Linux for a living, rsync is one of those tools worth learning properly rather than copying commands for. It’s fast, it’s everywhere, and once it clicks you stop reaching for cp and scp almost entirely.

This guide covers the flags you’ll actually use, the trailing slash rule that catches everyone at least once, how to read what rsync tells you it did, and how to build a backup you can restore from rather than one you hope works. If you’re setting up key-based access first, passwordless SSH, rsync and scp covers that side.

What rsync does that cp doesn’t

rsync copies files, but it’s cleverer about it than plain cp. It compares source and destination first, then sends only what actually differs. Copy a 40GB directory where one file changed and rsync moves that one file, not 40GB. That difference is the whole point of the tool, and it’s why it turns up in backup scripts everywhere.

By default it decides a file needs copying if the size or the modification time differs. That check is cheap and it’s right nearly all the time. When you need certainty instead of speed, -c makes it checksum every file, which is much slower and worth it maybe once a year to confirm a backup hasn’t quietly rotted.

For files that already exist at both ends, rsync goes further and sends only the changed blocks rather than the whole file. That’s the delta-transfer algorithm, and it’s why syncing a database dump that grew by a few megabytes doesn’t retransmit the whole thing.

The trailing slash, which everyone gets wrong once

This is the single most common rsync mistake, so it’s worth getting straight before anything else. A trailing slash on the source means “the contents of this directory”. No trailing slash means “this directory itself”.

rsync -av /home/asif/docs/ /backup/docs/     # docs contents land in /backup/docs/
rsync -av /home/asif/docs  /backup/docs/     # creates /backup/docs/docs/

The second one isn’t an error. rsync did exactly what you asked. You just asked for something you didn’t want, and you usually find out a week later when the nested directory turns up in a restore.

The destination trailing slash makes no difference. Only the source matters. If you can’t remember which way round it goes, run it with -n first and read the paths in the output.

What archive mode actually turns on

-a is the flag you’ll use most, and it’s shorthand for a group of others:

Flag What it preserves
-r Recurses into directories
-l Copies symlinks as symlinks
-p Permissions
-t Modification times
-g Group
-o Owner, root only
-D Device and special files, root only

Preserving timestamps matters more than it sounds. Without -t every file looks new on the next run, so rsync recopies everything and you’ve thrown away the reason you used rsync in the first place.

Three things -a does not include, which surprises people restoring a backup and finding something missing:

-H    preserve hard links
-A    preserve ACLs
-X    preserve extended attributes

If you’re backing up a system that uses ACLs or SELinux labels, you want -aAX at minimum. Plain -a will silently drop them. Worth knowing if you’ve set up ACLs with setfacl on the source.

Everyday examples

Local copy, keeping everything intact:

rsync -av /home/asif/documents/ /media/backup/documents/

Push to a remote server over SSH. rsync uses SSH by default now, so -e ssh is no longer needed unless you’re changing the SSH options:

rsync -avz /var/www/ asif@backup.example.com:/backups/www/

-z compresses in transit. Worth it over a slow link or for anything text-heavy. Less worth it for video, photos or already-compressed archives, where you’re burning CPU to compress something twice and it can end up slower.

Pull instead of push by swapping the arguments:

rsync -avz asif@backup.example.com:/backups/www/ /var/www/

If SSH is on a non-standard port, or you need a specific key:

rsync -av -e "ssh -p 2222 -i ~/.ssh/backup_key" /var/www/ asif@host:/backups/www/

One requirement people trip over: rsync has to be installed on both machines for remote transfers. If it isn’t, you get a confusing error about the remote command not being found, which looks like an SSH problem but isn’t. Details on getting SSH itself set up are in installing and configuring SSH.

Dry run first, every time

-n makes rsync do everything except actually write. Combine it with -v and you get the full list of what would happen.

$ rsync -avn --delete /home/asif/docs/ /backup/docs/
sending incremental file list
notes.txt
reports/q3.pdf
deleting old-draft.odt

sent 1,204 bytes  received 78 bytes  2,564.00 bytes/sec
total size is 4,891,203  speedup is 3,815.29 (DRY RUN)

That (DRY RUN) at the end is your confirmation nothing was touched. If you don’t see it, the run was real.

Reading a dry run properly is the habit that separates people who trust their backups from people who find out at restore time. Especially watch the deleting lines.

Reading what rsync tells you with -i

Verbose mode gives you filenames. -i gives you filenames plus a code saying what changed about each one, which is far more useful when you’re checking whether a sync did what you expected.

$ rsync -avi /home/asif/docs/ /backup/docs/
sending incremental file list
>f+++++++++ newfile.txt
>f..t...... unchanged-content.pdf
>f.st...... report.odt
cd+++++++++ archive/

Those eleven characters look like line noise until you know the layout. Reading left to right:

Position Meaning
1 > received, < sent, c created locally, . no transfer, * message follows
2 f file, d directory, L symlink, D device, S special
3 to 11 What differed: checksum, size, time, permissions, owner, group, then ACL and xattr

A row of plus signs means the file is brand new, so everything about it is a change. >f..t...... means only the timestamp moved, the content is identical. >f.st...... means size and time both changed, so the content genuinely differs.

That distinction is handy when a sync looks busier than it should. A screen full of ..t entries usually means something is rewriting timestamps without changing content, and you can go find out what.

Mirroring with –delete, carefully

--delete removes files at the destination that no longer exist at the source. It’s how you get a true mirror, and it’s the flag most likely to ruin your afternoon.

rsync -avn --delete /source/ /destination/     # look first
rsync -av  --delete /source/ /destination/     # then commit

The failure mode worth understanding: if the source path is wrong and happens to be empty, rsync mirrors that emptiness. It deletes everything at the destination, correctly, because that’s what you asked for. A typo in a path or a mount that didn’t come back after a reboot is all it takes.

Two guards worth knowing:

--max-delete=50           # bail out if more than 50 files would go
--delete-after            # transfer everything first, delete at the end

--max-delete is the cheap insurance. If a normal run deletes a handful of files and one day it wants to delete 4,000, something is wrong and you’d rather rsync stopped than finished.

Excluding what you don’t want

rsync -av --exclude "*.log" --exclude "node_modules/" /var/www/ /backup/www/

For anything more than a couple of patterns, put them in a file, one per line:

$ cat /etc/backup-excludes
*.log
*.tmp
node_modules/
.cache/
/var/www/uploads/thumbnails/

$ rsync -av --exclude-from=/etc/backup-excludes /var/www/ /backup/www/

A leading slash in a pattern anchors it to the transfer root rather than matching anywhere. So /var/www/uploads/thumbnails/ above only matches that one path, while .cache/ matches any directory of that name at any depth.

Big transfers over unreliable links

-P is --partial --progress together. Partial keeps whatever made it across when the connection drops, so the next run resumes instead of starting over.

rsync -avP /large/dataset/ asif@host:/backups/dataset/

For overall progress rather than per-file, --info=progress2 gives you one line for the whole transfer, which is much easier to read when there are thousands of files:

$ rsync -a --info=progress2 /large/dataset/ /backup/dataset/
     12.43G  67%   48.21MB/s    0:02:14 (xfr#8432, to-chk=2104/14229)

If the transfer is saturating a link other people are using, cap it. The value is in KiB per second:

rsync -av --bwlimit=5000 /large/dataset/ asif@host:/backups/

For anything that runs long enough to outlive your SSH session, start it inside screen so a dropped connection doesn’t kill the transfer.

Snapshot backups with –link-dest

This is the rsync feature that earns its keep and rarely gets covered. --link-dest points at a previous backup. Any file that hasn’t changed since then gets hard-linked instead of copied.

The result is a directory that looks like a complete full backup, but only consumes disk for the files that actually changed.

#!/bin/bash
set -euo pipefail

SRC=/home/asif/
DEST=/backup/snapshots
TODAY=$(date +%F)
LATEST=$DEST/latest

rsync -aAX --delete \
  --link-dest="$LATEST" \
  "$SRC" "$DEST/$TODAY/"

ln -sfn "$DEST/$TODAY" "$LATEST"

Run that daily and you get one directory per day. Each one browsable and restorable on its own, with unchanged files shared between them.

$ du -sh /backup/snapshots/*
4.2G    /backup/snapshots/2026-09-07
118M    /backup/snapshots/2026-09-08
94M     /backup/snapshots/2026-09-09

The first snapshot costs full size. After that you’re paying for the delta while still getting a full tree you can copy a single file out of. No restore chain, no incremental replay, no special tooling.

One caveat: hard links mean the copies share inodes on the same filesystem. It protects you from deleting or changing a file. It does not protect you from the disk dying. Snapshots are not offsite backups, and treating them as such is a mistake people make once.

Running it unattended

rsync behaves predictably and exits with a sane status code, which is what you want from something running at 3am. Check that code rather than assuming.

Exit code Meaning
0 Everything worked
23 Partial transfer, some files failed. Usually permissions.
24 Source files vanished mid-run. Common and often harmless on live directories.
30 Timeout waiting for data
255 SSH failed. Not an rsync problem.

A cron entry that logs and tells you when it fails:

0 3 * * * /usr/bin/rsync -aAX --delete /home/asif/ /backup/home/ \
  >> /var/log/backup.log 2>&1 || echo "backup failed" | mail -s "rsync failed" asif@example.com

Exit code 24 is worth calling out because it turns up constantly when backing up anything live, like a mail spool or a busy web root. A file existed when rsync built its list and was gone by the time it got there. Nothing is broken. If you treat 24 as a failure your alerts will cry wolf and you’ll start ignoring them, which is worse than not having them.

One more for remote backups where you need root at the far end but don’t want to permit root SSH logins:

rsync -aAX --rsync-path="sudo rsync" /var/www/ asif@host:/backups/www/

rsync, cp or scp?

Situation Use Why
One file, same machine cp rsync adds nothing here
Repeated sync of the same tree rsync Only moves what changed
One-off file to a server scp Fewer keystrokes, though rsync works fine
Backups of any kind rsync Resumable, verifiable, preserves attributes
Transfer that may get interrupted rsync --partial means you resume rather than restart

Worth knowing that scp is effectively deprecated. OpenSSH now routes it through the SFTP protocol by default, and upstream has been steering people away from it for years. If you’re picking one tool to get good at, rsync is the better investment.

Troubleshooting

What you see What it usually means
Files land in a nested duplicate directory Missing trailing slash on the source
Everything recopies every run No -t, so timestamps aren’t preserved
command not found on a remote sync rsync isn’t installed on the remote host
Permissions or owners wrong after restore Ran without root, or without -aAX
Exit code 23 Some files unreadable. Check the log for which.
Exit code 24 Source files changed mid-run. Usually fine.
Transfer slower with -z Already-compressed data. Drop the flag.
Destination emptied unexpectedly --delete with a wrong or unmounted source

Where to go next

The natural next step is making these run without a password prompt, which is covered in passwordless SSH, rsync and scp. If you’re newer to the shell generally, Linux commands and directory structure covers the ground this assumes.

rsync has been around for decades and hasn’t needed replacing, which tells you something. Learn the handful of flags above and you’ve covered most of what you’ll ever need. The rest of the man page is there when you hit an edge case, and you will.

If you take one habit from this, make it the dry run. Every destructive rsync you’ll ever regret would have shown you exactly what it was about to do, if you’d asked it first.

Avatar photo

Asif Khan

I have spent over 10 years working across IT systems, open source software, DevOps, Linux administration and cloud operations. Three things drive most of what I do: automation, security and resilience. Much of that work involves planning and building the platforms that sit behind services people rely on daily, which means designing for failure just as carefully as for load. Cloud computing held my attention early on, largely for its flexibility. Being able to scale up and then back down again means far less guessing about how much capacity you will need. Across projects I work with the full DevOps toolchain, from provisioning, orchestration and configuration management through to release management and microservices architecture.