Networking and remote
rsync
Copy and synchronize files and directories, locally or over ssh, transferring only what changed.
rsync [OPTION...] SOURCE... DEST (either side may be [USER@]HOST:PATH)
rsync makes a destination match a source. On the first run it copies everything; on later runs
it compares sizes and modification times, skips files that are already identical, and for
changed files sends only the parts that differ. That makes it the standard tool for backups,
deployments and keeping two directories in step, whether both are local or one is on a remote
host reached over ssh (host:path).
Nearly every invocation starts with -a (archive): recurse, and keep permissions, timestamps,
symlinks, owners (when run as root) and device files. Add -v to see the file list, -n to
rehearse, --delete to remove files that no longer exist in the source, and -z to compress
over slow links.
The one rule to memorize is the trailing slash on the source: rsync -a src/ dest/ copies the
contents of src into dest, while rsync -a src dest/ copies the directory itself,
producing dest/src/. The slash on the destination does not matter.
In these examples the remote host server is the sandbox itself, reached over ssh, so
server:backup/ lands in /home/dev/backup.
Examples
Copy a directory, keeping its attributes
rsync -av src/ backup/
sending incremental file list created directory backup ./ Makefile app.py main.c util.sh lib/ lib/helpers.py sent 1,151 bytes received 155 bytes 2,612.00 bytes/sec total size is 745 speedup is 0.57
-a copies recursively and preserves modes and times; -v lists each file. The summary
shows bytes sent and received. speedup below 1 is normal on a first copy, which has to
send everything plus the file list.
A second run copies nothing
Setup run before this example
rsync -a src/ backup/echo '// tweak' >> src/main.c
rsync -av src/ backup/
sending incremental file list main.c sent 317 bytes received 36 bytes 706.00 bytes/sec total size is 754 speedup is 2.14
backup/ is already up to date except for the one file just changed, so only main.c
is transferred. Unchanged files are skipped by comparing size and modification time,
which is why -a (keeping times) matters for the next run.
The trailing slash on the source
rsync -a src with-dir/
rsync -a src/ contents-only/
find with-dir contents-only -maxdepth 2 | sort
contents-only contents-only/Makefile contents-only/app.py contents-only/lib contents-only/lib/helpers.py contents-only/main.c contents-only/util.sh with-dir with-dir/src with-dir/src/Makefile with-dir/src/app.py with-dir/src/lib with-dir/src/main.c with-dir/src/util.sh
Without a slash, src itself is copied, giving with-dir/src/.... With src/, its
contents go straight into contents-only/. The destination's trailing slash makes no
difference.
Rehearse a mirror with --delete and -n
Setup run before this example
rsync -a src/ mirror/
touch mirror/stale.txt
echo '// tweak' >> src/main.crsync -avn --delete src/ mirror/
sending incremental file list deleting stale.txt ./ main.c sent 185 bytes received 39 bytes 448.00 bytes/sec total size is 754 speedup is 3.37 (DRY RUN)
--delete removes files in the destination that are not in the source; -n (--dry-run)
only reports what would happen. stale.txt would be deleted and main.c updated, and the
summary ends in (DRY RUN). Nothing has changed yet.
Mirror for real and see what changed
Setup run before this example
rsync -a src/ mirror/
touch mirror/stale.txt
echo '// tweak' >> src/main.crsync -ai --delete src/ mirror/
ls mirror
*deleting stale.txt .d..t...... ./ >f.st...... main.c Makefile app.py lib main.c util.sh
-i (itemize changes) prints a code per change: *deleting for removals, and for
>f.st...... a file (f) received (>) because its size (s) and time (t) differ.
.d..t...... means only the directory's timestamp was updated.
Leave files out with --exclude
rsync -av --exclude='*.py' --exclude=lib/ src/ backup/
sending incremental file list created directory backup ./ Makefile main.c util.sh sent 532 bytes received 105 bytes 1,274.00 bytes/sec total size is 294 speedup is 0.46
Patterns are matched against paths relative to the source. A trailing slash (lib/)
matches only directories. Excluded files are also protected from --delete on the
receiving side.
Copy to a remote host over ssh
rsync -av data logs server:backup/
ssh server 'ls backup'
sending incremental file list created directory backup data/ data/config.json data/fruits.txt data/hosts.txt data/inventory.tsv data/numbers.txt data/poem.txt data/sales.csv data/servers.json data/settings.conf data/users.csv data/windows.txt logs/ logs/app.log logs/app.log.1 logs/error.log sent 4,028 bytes received 319 bytes 2,898.00 bytes/sec total size is 3,043 speedup is 0.70 data logs
server:backup/ means the backup directory in your home on the remote host; rsync starts
itself there over ssh and creates the last path component if missing. Because data and
logs have no trailing slash, they arrive as directories.
Compress and show progress with -z and -P
rsync -azP logs/ server:backup-logs/
sending incremental file list ./ app.log 729 100% 0.00kB/s 0:00:00 729 100% 0.00kB/s 0:00:00 (xfr#1, to-chk=2/4) app.log.1 150 100% 146.48kB/s 0:00:00 150 100% 146.48kB/s 0:00:00 (xfr#2, to-chk=1/4) error.log 112 100% 109.38kB/s 0:00:00 112 100% 109.38kB/s 0:00:00 (xfr#3, to-chk=0/4)
-z compresses data in transit, which helps on slow links and wastes CPU on fast ones. -P
combines --progress (a per-file progress line) and --partial (keep half-sent files so
an interrupted transfer can resume).
Pull from a remote host
Setup run before this example
ssh server 'mkdir -p reports && cp demo/data/sales.csv demo/data/users.csv reports/'rsync -av server:reports/ ./reports/
ls reports
receiving incremental file list created directory ./reports ./ sales.csv users.csv sent 65 bytes received 1,029 bytes 2,188.00 bytes/sec total size is 841 speedup is 0.77 sales.csv users.csv
The remote side can be the source just as well. With the trailing slash, the files inside
reports/ on the remote host land directly in the local reports/.
Summarize a transfer with --stats
rsync -ah --stats data/ backup/ | head -n 9
Number of files: 12 (reg: 11, dir: 1) Number of created files: 12 (reg: 11, dir: 1) Number of deleted files: 0 Number of regular files transferred: 11 Total file size: 2.05K bytes Total transferred file size: 2.05K bytes Literal data: 2.05K bytes Matched data: 0 bytes
--stats prints counts of created, deleted and transferred files and the bytes moved;
-h makes the sizes human-readable. head keeps the most useful lines.
Missing parent directories on the destination
rsync -a data/ server:archive/2026/data/
rsync -a --mkpath data/ server:archive/2026/data/ && echo created
rsync: [Receiver] mkdir "/home/dev/archive/2026/data" failed: No such file or directory (2) rsync error: error in file IO (code 11) at main.c(808) [Receiver=3.5.0] created
rsync creates only the last directory of the destination. The first command fails because
archive/2026 does not exist (exit code 11). --mkpath (rsync 3.2.3 and later) creates
the whole path.
A source that does not exist
rsync -a nope/ backup/
rsync: [sender] change_dir "/home/dev/demo/nope" failed: No such file or directory (2) rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1394) [sender=3.5.0]
rsync reports the missing directory, adds some files/attrs were not transferred and exits
with 23.
In scripts, treat any non-zero status from rsync as a failed backup.
Common pitfalls
rsync -a src destandrsync -a src/ destdiffer: the first createsdest/src. Decide before you add--delete, or a mistaken slash can delete a whole tree on the destination.Always run
--deletewith-nfirst and read thedeletinglines.Without
-a(or-t), times are not preserved, so the next run cannot skip unchanged files and copies everything again.rsync needs to be installed on both ends for remote copies. The error
rsync: command not foundfrom the remote side means it is missing there.Owners and groups are only preserved when the receiving side runs as root; otherwise files belong to the user who received them.
GNU vs BSD and macOS
The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).
macOS shipped the old rsync 2.6.9 for many years, and recent macOS versions use openrsync, a separate implementation. Neither supports every option of current rsync (
--mkpath, for instance, arrived in 3.2.3); checkrsync --versionand install a current rsync from Homebrew if you need them.
Options worth knowing
A selection, described in our own words. For the full list, see rsync(1) or rsync --help.
| Option | What it does |
|---|---|
-a, --archive | Archive mode: recurse and preserve permissions, times, symlinks, owner, group and devices (-rlptgoD). |
-v, --verbose | List transferred files; repeat for more detail. |
-n, --dry-run | Show what would be done without changing anything. |
-r, --recursive | Recurse into directories. |
-z, --compress | Compress data during the transfer. |
-P | Same as --partial --progress. |
--progress | Show progress for each file. |
--partial | Keep partially transferred files so the transfer can resume. |
-h, --human-readable | Print sizes in human-readable units. |
-i, --itemize-changes | Print a change code for every updated item. |
--delete | Delete destination files that do not exist in the source. |
--exclude PATTERN | Skip files matching PATTERN. |
--include PATTERN | Do not exclude files matching PATTERN (order matters). |
--exclude-from FILE | Read exclude patterns from FILE. |
--stats | Print transfer statistics at the end. |
-e, --rsh COMMAND | Remote shell to use, for example 'ssh -p 2222'. |
-u, --update | Skip files that are newer on the receiving side. |
-c, --checksum | Compare files by checksum instead of size and time. |
-l, --links | Copy symlinks as symlinks. |
-L, --copy-links | Copy the files symlinks point to. |
-H, --hard-links | Preserve hard links. |
-A, --acls | Preserve ACLs. |
-X, --xattrs | Preserve extended attributes. |
-t, --times | Preserve modification times. |
-p, --perms | Preserve permissions. |
--mkpath | Create missing parent directories of the destination. |
--bwlimit RATE | Limit bandwidth, in KiB per second unless a suffix is given. |
--remove-source-files | Delete source files after they are transferred (not directories). |
--backup-dir DIR | With -b, put replaced and deleted files in DIR. |
-b, --backup | Keep a backup of files that are replaced or deleted. |
--info FLAGS | Fine-grained output control, for example --info=progress2 for one overall progress line. |
Related
scpCopy files between hosts over ssh, with cp-like syntax where a remote path is written HOST:PATH.sshLog in to a remote machine or run a command on it over an encrypted connection; also forwards ports and jumps through hosts.cpCopy files and directories, optionally keeping their timestamps, permissions and links.tarBundle files and directories into one archive, list it, or extract it; optionally compressed with gzip, bzip2, xz or zstd.