rsync Tutorial: Copy, Sync and Back Up Files over SSH with Incremental Snapshots
What is rsync #
rsync copies files and sends only the parts that changed. The first run copies everything and the next ones take seconds, which makes it ideal for backups, mirroring a website or moving data between servers. It works locally and over SSH, with no server software other than rsync itself and an SSH login.
sudo apt install -y rsync # macOS: brew install rsync
rsync --version
Create some data to practice:
mkdir -p demo/src/docs && cd demo
echo one > src/a.txt && echo two > src/docs/b.txt && echo skip > src/debug.log
The basic command #
rsync -avh src/ dest/
-a(archive): recursive, and keeps permissions, owners, times and symlinks. You almost always want it.-v: list the files.-h: human readable sizes.
Run it again and nothing is transferred. Change a.txt and run it once more: only that file is sent.
The trailing slash #
This is the most common mistake:
rsync -a src/ dest/ # copies the CONTENTS of src into dest: dest/a.txt
rsync -a src dest/ # copies the folder itself: dest/src/a.txt
The slash after the source decides it. The slash after the destination does not matter.
Preview first with --dry-run #
rsync -avhn --delete src/ dest/
-n (--dry-run) prints what would happen without touching anything. Make it a habit before you use --delete.
Mirror with --delete #
By default rsync never removes files from the destination. To make dest an exact copy of src:
rsync -avh --delete src/ dest/
Exclude and include #
rsync -avh --exclude '*.log' --exclude 'node_modules/' --exclude '.git/' src/ dest/
rsync -avh --exclude-from=exclude.txt src/ dest/
rsync -avh --include '*.conf' --exclude '*' src/ dest/ # only .conf files
Patterns are checked in order, so put --include before the --exclude '*' that follows it.
Copy over SSH #
# push to a server
rsync -avzh src/ ubuntu@203.0.113.10:/backups/web/
# pull from a server
rsync -avzh ubuntu@203.0.113.10:/var/www/ ./www-copy/
# with a key and a different port
rsync -avzh -e "ssh -i ~/.ssh/backup_key -p 2222" src/ ubuntu@203.0.113.10:/backups/web/
-z compresses data on the wire, which helps on slow links and wastes CPU on already compressed files such as images and videos. The login uses public key authentication. Between two remote servers, run rsync on one of them.
Useful options #
| Option | Use |
|---|---|
-P |
--partial --progress: keep partly transferred files and show progress |
--info=progress2 |
One progress line for the whole transfer |
--bwlimit=5000 |
Limit to about 5000 KiB/s |
-c |
Compare checksums instead of size and time (slow, thorough) |
-i |
Itemized list: explains why each file is copied |
--remove-source-files |
Move instead of copy |
--max-size=100m |
Skip big files |
-A -X |
Also copy ACLs and extended attributes |
--rsync-path="sudo rsync" |
Read root-owned files on the remote side |
For a large transfer over a bad connection, use rsync -avP and run it again after any failure: it continues where it stopped.
Incremental backups with hard links #
--link-dest creates a new folder for each backup in which unchanged files are hard links to the previous backup. Each snapshot looks like a full copy, but only the changed files use extra disk space.
#!/usr/bin/env bash
# /usr/local/bin/backup.sh
set -euo pipefail
SRC="/var/www/"
DEST="/backups/web"
NOW="$(date +%F_%H%M)"
mkdir -p "$DEST"
rsync -a --delete --link-dest="$DEST/latest" "$SRC" "$DEST/$NOW/"
ln -sfn "$DEST/$NOW" "$DEST/latest"
# keep the last 14 snapshots
ls -1dt "$DEST"/20* | tail -n +15 | xargs -r rm -rf
Deleting an old snapshot is safe: a file stays on disk while any snapshot still links to it. To store the backups on another machine, run the script there and pull from the source over SSH.
Schedule it with cron #
30 2 * * * /usr/bin/flock -n /tmp/backup.lock /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
This runs every night at 02:30 and never twice at the same time. See cron expressions.
Restore #
Restoring is the same command in the other direction:
rsync -avh /backups/web/latest/ /var/www/
rsync -avh /backups/web/2026-10-01_0230/index.html /var/www/index.html
Test a restore regularly: a backup that was never restored is only a hope.
rsync or something else #
- Databases need a consistent dump first (see PostgreSQL and MariaDB); copying live data files may give a corrupted copy.
- S3 has its own tool,
aws s3 sync(S3). - Versioned and deduplicated backups with encryption: tools such as restic or borg. rsync is simple and fast for plain copies.
- Servers as code: automate the copy with the Ansible
synchronizemodule, which wraps rsync (Ansible).