·12 min read

Rsync Over SSH: Incremental Backups and File Transfers That Actually Work

scp copies everything. sftp copies everything. Even when only three files changed since yesterday. Rsync over SSH computes the difference and transfers only the bytes that moved — and it runs over the same encrypted channel you already trust. Here's the practical guide.

RsyncSSHSFTPBackupLinuxDevOpsFile Transfer

You have a directory of 40 GB on a remote server. Yesterday, you downloaded a copy to your laptop. Today, you need the updated version.

If you reach for scp or sftp, you'll move all 40 GB again. If you use rsync over SSH, you'll move the few megabytes that actually changed.

That's not a small difference. It's the difference between a ten-second sync and a forty-minute coffee break.

The Algorithm That Makes It Fast

Rsync's magic is the delta-transfer algorithm. Here's how it works in plain terms:

  1. Rsync on the sender and receiver break files into blocks.
  2. The receiver sends checksums for each block of its existing copy.
  3. The sender compares those checksums against its own file.
  4. Blocks that match are skipped. Blocks that differ are transferred.

The result: even within a single large file, only the changed pieces move. A 500 MB log file where only the last 2 MB is new costs you 2 MB of transfer, not 500 MB.

And because rsync runs over SSH by default, every byte is encrypted on the wire. You don't need to set up a separate rsync daemon, open a new port, or manage a second set of credentials. Your SSH key is the only auth you need.

The Basic Command, Decoded

The simplest useful rsync command looks like this:

rsync -avz ./local-folder/ user@server:/remote/folder/

Let's break down what each flag means, because the flags are where rsync's power lives:

  • -a (archive) — recursive copy, preserve permissions, ownership, timestamps, and symlinks. This is the flag you want 90% of the time.
  • -v (verbose) — show what's being transferred.
  • -z (compress) — compress data during transfer. Huge help on slow links; unnecessary on fast LANs.

The trailing slash matters. This is the single most common rsync mistake:

# Copies the CONTENTS of local-folder/ into /remote/folder/
rsync -avz ./local-folder/ user@server:/remote/folder/

# Copies local-folder ITSELF into /remote/folder/local-folder/
rsync -avz ./local-folder user@server:/remote/folder/

The first one puts files where you expected. The second one creates a nested directory you didn't want. When something looks wrong after a sync, check the slashes first.

Dry Runs: Never Sync Blindly

Before you run a real rsync, add --dry-run (or -n):

rsync -avzn --dry-run ./local-folder/ user@server:/remote/folder/

Rsync prints exactly what it would transfer, then exits without touching anything. This is how you catch mistakes — a path that's wrong, a directory you forgot to exclude, a file you're about to overwrite on the remote side — before they cause damage.

Make --dry-run a habit on any command you haven't run before.

The Common Scenarios

Backup a remote directory to your laptop

rsync -avz user@server:/var/www/data/ ~/Backups/server-data/

Run this once and it copies everything. Run it again tomorrow and it only transfers the changes. That's the backup workflow rsync was built for.

Deploy a build directory to a server

rsync -avz --delete ./dist/ user@server:/var/www/app/

The --delete flag removes files on the remote side that no longer exist in the source. Use it when you want the remote directory to be an exact mirror of the local one — for example, after a frontend build. Be careful: --delete will happily erase anything that isn't in the source.

Exclude files you never want to transfer

rsync -avz \
  --exclude 'node_modules/' \
  --exclude '.git/' \
  --exclude '*.log' \
  ./project/ user@server:/srv/project/

You can pass --exclude multiple times, or list them in a file with --exclude-from=exclude.txt. This is how you keep .env files, build artifacts, and local configs from leaking to production.

Show progress during large transfers

rsync -avz --progress --partial ./large-dataset/ user@server:/data/
  • --progress shows per-file transfer speed and ETA.
  • --partial keeps partially transferred files if the connection drops, so the next run resumes instead of starting over.

Rsync vs SCP vs SFTP: When to Use Which

Tool Best for
scp One-off, single-file transfers where you don't care about efficiency
SFTP Interactive browsing of a remote filesystem, drag-and-drop transfers
rsync Repeated syncs, backups, deployments, anything where files have changed before

FoxTerm's SFTP file manager gives you the interactive browsing experience. When you need to synchronize directories or back up data, drop to the terminal and use rsync — FoxTerm's SSH terminal supports it natively, with the same connection you're already authenticated on.

A Production-Ready Backup One-Liner

Here's a command you can adapt for your own servers. It backs up the home directory, excludes caches, compresses, shows progress, and keeps logs:

rsync -avz \
  --exclude '.cache/' \
  --exclude '.local/share/Trash/' \
  --exclude 'node_modules/' \
  --delete \
  --partial \
  --progress \
  -e "ssh -i ~/.ssh/id_ed25519" \
  user@server:/home/user/ \
  ~/Backups/server-home/

The -e "ssh ..." part lets you specify a custom SSH identity file, port, or any other SSH option without writing a separate config. If you use a non-standard SSH port:

rsync -avz -e "ssh -p 2222" ./local/ user@server:/remote/

Watch Out For These Gotchas

  • Permissions matter. If you're syncing to a directory your remote user can't write to, rsync fails silently partway through. Use --dry-run first.
  • Disk-space mismatches. rsync doesn't check available space on the destination. It'll fill the disk and then error out mid-transfer.
  • Symlinks. By default (-a), rsync copies symlinks as symlinks. Use -L if you want to follow them and copy the actual files.
  • Trailing dots and spaces. If your source directory has files with unusual whitespace, quote paths carefully.

Put It On a Schedule

Once you trust the command, add it to cron:

# Daily incremental backup at 2 AM
0 2 * * * rsync -az --delete /home/user/ backup@nas:/backups/laptop/ >> /var/log/rsync.log 2>&1

The first backup does the heavy lifting. Every run after that costs seconds. That's the whole point.