SSH File Transfer for Developers: SCP, Rsync, and SFTP Compared

A practical guide to SSH file transfer methods — when to use scp, rsync, or sftp, with real command examples, performance tips, and common pitfalls.

If you're SSHing into servers regularly, you're probably moving files back and forth more often than you'd like to admit. But are you using the right tool? Most developers default to scp because it's familiar, but there's a good chance you're wasting time—or worse, accidentally overwriting work.

This guide breaks down the three main SSH file transfer methods: SCP, Rsync, and SFTP. By the end, you'll know exactly which tool to reach for and how to use it effectively.

The Quick Decision Tree

Before we dive deep, here's when to use each tool:

  • SCP: One-time transfers of single files or small directories. Fast and simple, but no resume capability.
  • Rsync: Syncing directories repeatedly, large file transfers, or when you need to resume interrupted transfers. The smart choice for most workflows.
  • SFTP: Interactive file browsing, corporate environments that block SCP/rsync, or when you need to confirm what you're downloading before grabbing it.

Now let's get into the details.

SCP: The Simple One-Shot Transfer

SCP (Secure Copy Protocol) is the SSH equivalent of cp. It's built into OpenSSH, requires no setup, and works exactly how you'd expect.

Basic Syntax

The pattern is simple: scp source destination. One of them will be a remote path in the format user@host:/path/to/file.

Copy a local file to a remote server:

scp /local/path/config.json user@server.com:/remote/path/

Copy a remote file to your local machine:

scp user@server.com:/var/log/app.log ./logs/

Copy between two remote servers (yes, this works):

scp user1@server1.com:/path/file.txt user2@server2.com:/destination/

That third example is underrated. The file transfer happens directly between the two remote servers, not through your local machine. Useful when you're orchestrating migrations.

Copying Directories

Add the -r flag (recursive) to copy entire directories:

scp -r ./dist/ user@server.com:/var/www/html/

This copies the dist directory and everything inside it. Note that SCP will follow symbolic links by default—if you have a symlink pointing outside your intended directory tree, SCP will copy the target. Use -r carefully.

Using SSH Config Hosts

If you've defined hosts in your ~/.ssh/config file, SCP respects them:

# In ~/.ssh/config:
Host production
    HostName prod.example.com
    User deploy
    IdentityFile ~/.ssh/prod_key

# Then in your terminal:
scp production:/app/config.json ./backup/

This is cleaner and lets you centralize SSH settings like ports, keys, and jump hosts.

SCP Limitations You Need to Know

  1. No resume capability: If a transfer fails halfway through a 10GB file, you start over from scratch.
  2. No delta sync: Every time you run SCP, it copies the entire file, even if only one line changed.
  3. Deprecated in newer OpenSSH: OpenSSH 8.0+ shows warnings that the SCP protocol is outdated. It still works, but it's being phased out in favor of SFTP under the hood.
  4. No progress by default: Add -v (verbose) to see what's happening, but it's noisy.

For one-time file transfers, SCP is fine. For anything else, reach for rsync.

Rsync: The Smart Sync Tool

Rsync is what you use when you care about efficiency. It only transfers the bytes that have changed, can resume interrupted transfers, and gives you granular control over what gets copied.

Why Rsync is Almost Always Better

Here's the key insight: rsync uses a delta-transfer algorithm. Instead of copying entire files, it compares the source and destination, then only sends the differences. If you're syncing a 1GB database dump and only 10MB changed, rsync transfers 10MB. SCP transfers 1GB.

Rsync also runs over SSH by default (when you use a remote path), so your transfers are encrypted just like SCP.

The Flags Every Developer Should Know

rsync -avz --progress source/ user@server.com:/destination/

Breaking down those flags:

  • -a (archive mode): Preserves permissions, timestamps, symbolic links, and recursively copies directories. This is the "make the destination look exactly like the source" flag.
  • -v (verbose): Shows which files are being transferred.
  • -z (compress): Compresses data during transfer. Saves bandwidth, especially over slow connections.
  • --progress: Shows a progress bar for each file. Critical for large transfers so you know the thing didn't hang.

This single command handles 90% of file sync needs.

Practical Example: Syncing a Project Directory

You're deploying a web app. You want to sync your build output to the server, but skip node_modules, .git, and .env files:

rsync -avz --progress \
  --exclude 'node_modules' \
  --exclude '.git' \
  --exclude '.env' \
  ./project/ user@server.com:/var/www/app/

The --exclude flag can be repeated as many times as needed. You can also use wildcards:

rsync -avz --exclude '*.log' --exclude '*.tmp' ./data/ backup:/data/

The Trailing Slash Gotcha

Pay attention to trailing slashes on the source directory:

# Copies the CONTENTS of project/ into /var/www/
rsync -avz ./project/ user@server:/var/www/

# Copies the project DIRECTORY into /var/www/, creating /var/www/project/
rsync -avz ./project user@server:/var/www/

The slash makes a huge difference. Messing this up is how you end up with nested directories you didn't intend.

Deleting Files That Don't Exist in Source

By default, rsync only adds or updates files on the destination. If you delete a file from the source, it stays on the destination. To make the destination an exact mirror:

rsync -avz --delete ./source/ user@server:/destination/

The --delete flag removes files on the destination that don't exist in the source. Be careful with this—it's powerful but destructive.

Dry Run Before You Execute

Before running a sync with --delete or on a critical server, do a dry run:

rsync -avz --delete --dry-run ./source/ user@server:/destination/

The --dry-run flag (or -n) shows you exactly what would happen without actually doing it. You'll see output like:

deleting old-file.txt
sending incremental file list
new-file.txt

This has saved me from disastrous mistakes more times than I'd like to admit.

Resuming Interrupted Transfers

Rsync is smart enough to resume where it left off. If your connection drops during a large transfer, just run the same command again. Rsync will checksum what's already there and only transfer what's missing or changed.

For extremely large files, add the --partial flag to keep partially transferred files:

rsync -avz --partial --progress large-file.tar.gz user@server:/backups/

Without --partial, rsync deletes incomplete files and starts over. With it, rsync resumes from where it stopped.

SFTP: The Interactive Browser

SFTP (SSH File Transfer Protocol) is the protocol that replaced the old SCP protocol. It's more robust, supports resuming, and has an interactive mode for exploring remote filesystems.

When SFTP Makes Sense

  • You're not sure what file you need: SFTP lets you ls and cd around the remote server before deciding what to download.
  • Corporate environments: Some sysadmins lock down servers to only allow SFTP, blocking SCP and rsync.
  • Graphical tools: FileZilla, Cyberduck, and other GUI clients use SFTP under the hood.

Interactive Mode

Just run sftp user@server.com to drop into an interactive session:

sftp production

sftp> ls
config.json    app.log    data/

sftp> cd data
sftp> ls
users.db    sessions.db

sftp> get users.db ./backup/
Fetching /data/users.db to ./backup/users.db
users.db                      100%  45MB   5.2MB/s   00:08

sftp> put ./new-config.json /etc/app/
Uploading ./new-config.json to /etc/app/new-config.json
new-config.json               100%  2048    1.1MB/s   00:00

sftp> mkdir /backups/2026-02-24
sftp> quit

Key commands:

  • ls, cd, pwd: Navigate the remote filesystem
  • lls, lcd, lpwd: Navigate your local filesystem (note the leading "l")
  • get remote-file [local-path]: Download a file
  • put local-file [remote-path]: Upload a file
  • mkdir, rm, rmdir: Manage remote directories
  • quit or exit: Close the session

Batch Mode for Scripts

You can also script SFTP with a batch file:

# commands.txt
cd /var/log
get app.log
get error.log
quit

Then run:

sftp -b commands.txt user@server.com

This is useful in cron jobs or CI/CD pipelines where you can't use interactive mode.

Performance Comparison

Here's when each tool is fastest:

SCP wins for: Single small files over fast connections. The overhead is minimal, and the transfer is straightforward. If you're copying a 5KB config file once, SCP is fine.

Rsync wins for:

  • Large files (can resume)
  • Repeated syncs (delta transfer means only changes are sent)
  • Directory trees (efficient at handling many files)
  • Anything over a slow or unreliable connection (compression and resume)

SFTP wins for: Almost never, performance-wise. It's more robust than old SCP, but rsync is faster for bulk transfers. SFTP's strength is the interactive browsing, not speed.

In practice, if you're not sure, use rsync. The delta-transfer algorithm means it's often faster even for first-time copies because of the compression.

Common Pitfalls

Permission Issues

All three tools will faithfully copy your local permissions to the remote server. If your local file is chmod 600, it'll be 600 on the remote side too. This trips people up when they copy files to a web server and get "403 Forbidden" errors because the web server can't read the files.

With rsync, you can override this:

rsync -avz --chmod=644 ./files/ user@server:/var/www/

This sets all files to 644 on the remote side, regardless of local permissions.

Symbolic Links

SCP follows symlinks by default, copying the target. Rsync preserves symlinks as symlinks (because of -a). If you want rsync to follow symlinks:

rsync -avzL ./source/ user@server:/destination/

The -L flag (or --copy-links) dereferences symlinks.

Large File Handling

For multi-gigabyte files, always use rsync with --partial and --progress. SCP will make you start over if anything goes wrong, and SFTP doesn't have rsync's delta-transfer smarts.

Interrupted Transfers

SCP and basic SFTP: Start over. Rsync: Just run the command again. It'll pick up where it left off.

This alone is reason enough to default to rsync.

SSH Config Tricks for Faster Transfers

Your SSH configuration can dramatically affect transfer speeds.

Enable Compression

For slow connections, add compression to your SSH config:

# ~/.ssh/config
Host slowserver
    HostName remote.example.com
    Compression yes

This is equivalent to ssh -C or rsync's -z flag. For fast local networks, compression actually slows things down (CPU overhead > bandwidth savings), so only enable it when bandwidth is the bottleneck.

Connection Multiplexing

SSH can reuse a single connection for multiple sessions. This eliminates the handshake overhead when you're running multiple rsync or scp commands in succession:

# ~/.ssh/config
Host *
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 600

Make sure the ~/.ssh/sockets/ directory exists:

mkdir -p ~/.ssh/sockets

Now when you SSH into a server, that connection stays open for 10 minutes (ControlPersist 600). Any subsequent SSH/SCP/rsync commands reuse it, eliminating the handshake delay. This can shave seconds off each transfer, which adds up fast in deployment scripts.

The Modern Approach: Agents UI

While command-line tools are powerful, they're not always the fastest way to move files. Agents UI includes built-in drag-and-drop file transfer over SSH. You can drag files from Finder directly into a remote directory in your terminal session, or drag files out of the terminal to download them.

For developers who spend hours a day in SSH sessions, this kind of workflow integration saves more time than any command-line optimization. But when you do need to script transfers or sync large directory trees, the knowledge above will serve you well.

Conclusion

Here's what you should take away:

  1. SCP is fine for one-off small files, but it's been deprecated and has no resume capability.
  2. Rsync is the workhorse: It's smarter, faster for repeated transfers, and can resume. Make -avz --progress your default.
  3. SFTP is for interactive exploration or restricted environments. It's not faster, but it's more robust than old SCP.
  4. Use --dry-run before destructive operations. Always.
  5. Configure SSH multiplexing to speed up repeated connections.

Next time you reach for scp, ask yourself: should this be rsync instead? You'll save time and bandwidth, and your future self (dealing with an interrupted 5GB transfer) will thank you.

Try Agents UI

A native terminal for AI coding agents with persistent sessions, SSH workflows, and built-in editing.