Skip to content

scp and rsync

Everything covered so far in this module has been about interactive login — connecting to a server and running commands there. A large part of real administrative work, though, is moving files: uploading a configuration file, pulling a log file down for analysis, copying a backup archive to another server. This article covers two tools built on top of the SSH infrastructure already in place — scp and rsync — for moving files securely.

scp: Simple, Direct Copying

scp (secure copy) is a tool built on top of an SSH connection, with syntax similar to cp. File locations are written as [user@]host:path.

From the local machine to a server:

scp app.conf [email protected]:/etc/myapp/app.conf

From a server to the local machine:

scp [email protected]:/var/log/myapp/error.log ./error.log

Copying an entire directory requires the -r (recursive) flag:

scp -r ./dist [email protected]:/var/www/myapp/

Other useful flags:

  • -P <port> — a port other than the default 22 (note: scp uses a capital -P, while ssh uses a lowercase -p — mixing these up is a common mistake);
  • -i <file> — use a specific private key file;
  • -p — preserve file timestamps and permissions.
scp -P 2222 -i ~/.ssh/id_ed25519 backup.tar.gz [email protected]:~/backups/

The Limits of scp

scp is convenient for simple, one-off transfers, but it has a few significant limitations:

  • no resume support — if the connection drops, the transfer has to start over from the beginning, even if the file was 99% done;
  • no awareness of differences — copying the same directory a second time re-sends every file in full, even unchanged ones;
  • copying, not synchronization — a file deleted from the source stays behind at the destination.

These limitations become a real problem with large volumes of data, recurring backups, or an unstable network — exactly the situations rsync was built for.

Note

Recent versions of OpenSSH are gradually moving scp's internal protocol to SFTP (the legacy SCP protocol has known security limitations). This doesn't meaningfully change the outward syntax of the scp command, but it does require the SFTP subsystem to be enabled on the server — which is already the case in a standard OpenSSH install.

rsync: a Tool That Copies the Difference

rsync compares files at the source and destination and transfers only the difference (or files not yet copied at all). This gives two advantages: less network traffic, and no re-sending of data already transferred if a connection drops and the command is run again.

The basic syntax resembles cp/scp, but a trailing / on the source directory changes its meaning significantly:

rsync -avz ./dist/ [email protected]:/var/www/myapp/

The Most Important Flags

  • -a (archive) — recursive copy, preserving permissions, ownership, timestamps, and symbolic links; the base flag used in almost every invocation;
  • -v — verbose, shows which files are being transferred;
  • -z — compress data over the network (useful on a slow link, makes little difference on a fast local network);
  • --delete — removes files at the destination that no longer exist at the source (for true synchronization);
  • --exclude=<pattern> — excludes a specific file or directory from the transfer;
  • --dry-run — shows what would happen without actually transferring anything;
  • -P — a combination of --progress and --partial: shows transfer progress as a percentage, and keeps a partially transferred file when resuming an interrupted transfer instead of discarding it.

Reaching a Non-Default Port or Specific Key: -e

Unlike scp, rsync has no -P/-i of its own for SSH options — it hands the whole SSH command to the -e flag instead. This is how you sync to a server on a hardened, non-standard port or with a specific key:

rsync -avz -e "ssh -p 2222 -i ~/.ssh/id_ed25519" ./dist/ [email protected]:/var/www/myapp/

Everything inside the quotes is the literal ssh invocation rsync will use to open the connection. Note that rsync's own -p does not set a port — it means "preserve permissions" — so a non-default port must always go through -e "ssh -p ...", a mix-up worth watching for.

Why the Trailing / Matters

rsync -av ./dist alice@server:/var/www/

copies the dist directory itself as /var/www/dist/, whereas

rsync -av ./dist/ alice@server:/var/www/

copies the contents of dist directly into /var/www/ (not the directory itself). Not knowing this difference is the single most common cause of files ending up in the wrong location.

Testing First: --dry-run

Especially in combination with --delete, it's worth seeing what would happen before running the transfer for real:

rsync -avz --delete --dry-run ./dist/ [email protected]:/var/www/myapp/
sending incremental file list
deleting old-bundle.js
index.html
assets/style.css
assets/app.js

sent 1,204 bytes  received 96 bytes  866.67 bytes/sec
total size is 245,120  speedup is 188.60 (DRY RUN)

The (DRY RUN) marker confirms nothing was actually changed. If the result looks correct, dropping --dry-run and re-running the command is all that's needed.

Excluding Files

rsync -avz --exclude='.git/' --exclude='node_modules/' ./project/ alice@server:/srv/project/

This is the standard way to skip a version control directory or a heavy, regeneratable dependency folder during a transfer.

Choosing Between scp and rsync

Situation Recommended tool
A single file, quick one-off copy scp
Regularly syncing a large directory (deploy, backup) rsync
An unstable or slow network rsync (-P allows resuming)
Keeping two sides in sync, sending only what changed rsync
The absolute minimum syntax needed in a script scp

In practice, recurring production deploy or backup scripts almost always rely on rsync — this can be seen in a more applied context in Backup Automation.

Practical Scenario: Resuming an Interrupted Transfer

Problem: copying a 2 GB backup archive to a remote server with scp gets interrupted by a network drop, stopping at 60%.

Step 1 — understand the problem: restarting with scp means the file is copied again from zero — wasting time on a slow connection.

Step 2 — switch to rsync:

rsync -avzP backup-2026-07-25.tar.gz [email protected]:~/backups/

Step 3 — watch the transfer progress:

backup-2026-07-25.tar.gz
  1,258,291,200  61%   14.2MB/s    0:00:34

If the network drops again, simply re-running the command is enough — thanks to -P (--partial), rsync keeps the portion already transferred and only continues from there, instead of starting over.

Step 4 — verify once complete:

ssh [email protected] "sha256sum ~/backups/backup-2026-07-25.tar.gz"
sha256sum backup-2026-07-25.tar.gz

Matching checksums confirm the file arrived complete and intact.

Step 5 — result and documentation: for large files worth resuming, rsync -P is now adopted as the standard practice in place of scp.

Common Mistakes

Ignoring the Trailing / on the Source Directory in rsync

As explained above, this leads to files ending up one level deeper or shallower than intended. Checking with --dry-run before the real transfer catches this ahead of time.

Running --delete Without Checking First

--delete irreversibly removes files at the destination that no longer exist at the source. Always check what would be deleted with --dry-run first.

Confusing scp -P with ssh -p

scp uses an uppercase -P for the port, ssh uses lowercase -p. Using the wrong case usually produces an "unknown option" error.

Always Reaching for scp on Large Transfers

This isn't an issue for a small, one-off file, but for regular or large transfers, scp's inability to resume or detect differences wastes meaningful time.

Exercises

  1. Copy a small file with scp, then copy the same file a second time with rsync -avz, and observe that rsync sends nothing the second time since the file hasn't changed.
  2. Sync a project directory to a server, excluding at least two patterns with --exclude.
  3. Compare a directory with --dry-run --delete to see which files would be removed, then run the sync for real without --dry-run.
  4. Simulate the scenario above: interrupt a large file transfer with Ctrl+C, then resume it with rsync -P and confirm only the remaining portion is transferred.

Verification criterion: for exercise 4, the resumed transfer's reported data size must be noticeably smaller than the full file size — if rsync re-sends the entire file, -P (--partial) was not actually in effect.

References