2 min read

rsync & scp — Copy files between servers with metadata, logging & resume support ---

rsync — recommended for most use cases

rsync is the go-to tool for server-to-server transfers. It supports incremental sync, preserves metadata, logs output, and can resume interrupted transfers.

Basic syntax

rsync [options] [user@]source:/path/ [user@]dest:/path/

Common option flags

Flag Meaning
-a Archive mode — preserves permissions, timestamps, symlinks, owner, group
-v Verbose — shows files being transferred
-z Compress data during transfer
-P Combines --progress + --partial — shows progress and keeps partial files for resume
--numeric-ids Don't map UID/GID to names — safer across different systems
--rsync-path="sudo rsync" Run rsync as sudo on the remote side
--log-file=/path/to/log Write output to a log file
--checksum Skip based on checksum instead of file size/time (slower, more accurate)
--bwlimit=KBPS Throttle bandwidth (e.g. --bwlimit=50000 for ~50 MB/s)
--exclude='pattern' Exclude files or directories matching a pattern
--delete Delete files on destination that no longer exist on source
-n / --dry-run Simulate transfer without making changes — always test first

A note on trailing slashes

The trailing slash on a source path is significant in rsync and a common source of mistakes.

# Copies the CONTENTS of /data/myapp/ into /mnt/backup/
rsync -a user@10.0.0.10:/data/myapp/ /mnt/backup/

# Copies the DIRECTORY /data/myapp itself into /mnt/backup/
# Result: /mnt/backup/myapp/
rsync -a user@10.0.0.10:/data/myapp /mnt/backup/

Always double-check your trailing slashes before running, especially with --delete.


Example 1 — Remote to local, full metadata, with resume & log

rsync -avzP \
  --numeric-ids \
  --rsync-path="sudo rsync" \
  user@10.0.0.10:/data/myapp/ \
  /mnt/backup/myapp/ \
  --log-file=/var/log/rsync_myapp.log

Example 2 — Local to remote

rsync -avzP \
  --numeric-ids \
  /mnt/backup/myapp/ \
  user@10.0.0.10:/data/myapp/ \
  --log-file=/var/log/rsync_push.log

Example 3 — Add checksum verification + bandwidth limit + exclude pattern

rsync -avzP \
  --numeric-ids \
  --checksum \
  --bwlimit=50000 \
  --exclude='*.tmp' \
  --exclude='.cache/' \
  user@10.0.0.10:/data/myapp/ \
  /mnt/backup/myapp/ \
  --log-file=/var/log/rsync_myapp.log

rsync -avzPn \
  --numeric-ids \
  user@10.0.0.10:/data/myapp/ \
  /mnt/backup/myapp/ \
  --log-file=/var/log/rsync_dryrun.log

Remove the -n flag once you're happy with the output.


Example 5 — Delete files on destination that no longer exist on source

rsync -avzP \
  --numeric-ids \
  --delete \
  user@10.0.0.10:/data/myapp/ \
  /mnt/backup/myapp/ \
  --log-file=/var/log/rsync_myapp.log
⚠️ Use --delete with caution. Always do a dry run first.

scp — simple alternative for one-off copies

scp is simpler but has no resume support and fewer options for metadata control. Use it for quick one-off file copies where rsync is overkill.

# Copy a file from remote to local
scp user@10.0.0.10:/data/myapp/archive.tar.gz /mnt/backup/

# Copy an entire directory recursively
scp -rp user@10.0.0.10:/data/myapp/ /mnt/backup/myapp/
Flag Meaning
-r Recursive (directories)
-p Preserve timestamps and permissions
-C Enable compression
-P 2222 Specify a custom SSH port
-i ~/.ssh/id_rsa Use a specific SSH key
Note: scp does not support resume. If the connection drops mid-transfer, you start over. Prefer rsync for large transfers.

Re-running rsync after an interruption

Because of --partial (included in -P), rsync saves partially transferred files and picks up where it left off. Simply re-run the exact same command — rsync will skip already-completed files and resume any partial ones.


Tailing the log in real time

tail -f /var/log/rsync_myapp.log