How to Copy Files Over SSH Safely (2026) with scp and rsync

To copy files over SSH you use scp for one-off transfers and rsync when you need incremental, resumable directory syncs. Both ride the encrypted SSH connection you already use for a shell, so there is nothing extra to install and no unencrypted FTP step in the middle. A single file takes seconds to move once you know the syntax.

Last updated October 2026. The commands below run on Linux, macOS and Windows 10 or newer, where OpenSSH ships in the box.

Table of Contents

What You Need

Three things, and only three: an SSH client on the machine you are copying from, an SSH server on the machine you are copying to, and permission to write where the files land.

  • On the sending side: an OpenSSH client. macOS has it in Terminal, Windows 10 and 11 have it in PowerShell, and Linux ships it by default.
  • On the receiving side: an SSH server (usually sshd) running on the target, and an account you can log into with.
  • Destination write access: your login user needs write permission in the target directory. Copying into somewhere like /etc usually means copying to your home directory and then moving the file with sudo on the remote side.
  • Room on the destination: check free space before a large transfer with df -h /path/to/destination on the remote host. A copy that dies at 99 percent is usually a full disk, not a broken network.
  • For rsync: it must be installed on both machines. Most Linux distributions have it, macOS ships it, Windows needs WSL or a recent build.

The scp versus rsync split is simple. Use scp for a handful of files, a one-time deployment, or grabbing a log off a server. Use rsync for anything you will repeat, anything large, or anything where a dropped connection would hurt, because it transfers only the differences and can pick up where it stopped.

Step-by-Step

Choose the right command for the transfer

These six commands cover almost every transfer people need:

  • scp report.pdf user@server:/home/user/ — upload one file
  • scp user@server:/var/log/app.log ./ — download one file
  • scp -r project user@server:/home/user/ — upload a folder and everything in it
  • scp -P 2222 config.yml user@server:/tmp/ — same upload on a non-default SSH port
  • scp -3 source-server:/data/dump.sql target-server:/backups/ — copy between two servers
  • rsync -avz --partial project/ user@server:/home/user/project/ — sync a folder, skipping what has not changed

That colon after server is what makes a path remote. Leave it out and scp quietly creates a local file with a name like user@server in your current directory instead of transferring anything.

Copy a single file over SSH with scp

Start by proving the SSH connection itself works with ssh user@server. If that logs you in, the transfer will too, and you have separated connection problems from copy problems.

Upload from your machine into the remote account:

scp report.pdf user@server:/home/user/

Change the filename while you are at it by giving a full destination path instead of a directory:

scp report.pdf user@server:/home/user/report-final.pdf

Pull a file back down the other way, where . means the current local directory:

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

On a non-default port, remember the capital letter: -P 2222 sets the port for scp, while lowercase -p preserves timestamps. Getting that backwards is the single most common flag mistake, and it fails silently.

scp -P 2222 deploy.sh user@server:/home/user/

You know the copy worked when the command exits with status 0 and prints nothing. Add -v to watch the negotiated cipher and per-file progress, or check the destination with ssh user@server 'ls -lh /home/user/'.

Copy a directory over SSH with scp -r

Directories need the recursive flag, and the destination semantics catch people: scp -r project user@server:/home/user/ creates /home/user/project, while scp -r project/ user@server:/home/user/project/ copies the contents into that folder.

scp -r -P 2222 ./assets user@server:/srv/www/

Downloading a directory works the same way with the remote path first:

scp -r user@server:/etc/nginx/sites-available ./nginx-backup/

Quote paths that contain spaces, because your own shell will eat them otherwise. Wildcards need quoting too, so the remote shell expands them instead of your local one:

scp user@server:'/var/log/app/*.log.gz' ./logs/

Copying several files into one directory in a single command also works, and saves handshakes:

scp id_rsa.pub authorized_keys user@server:/home/user/.ssh/

For thousands of small files, scp is the wrong tool because it opens a fresh session per file and the overhead adds up fast. Stream a tar archive through SSH instead:

ssh user@server 'tar czf - -C /var/log app' > app-logs.tar.gz

The mirror image compresses and sends a local directory up in one stream:

tar czf - -C /srv app | ssh user@server 'tar xzf - -C /home/user'

Use rsync for reliable directory transfers

rsync works over SSH by default, so the connection syntax is the same and nothing needs to be enabled on the server for most modern setups:

rsync -avz --partial project/ user@server:/home/user/project/

The flags: -a is archive mode, which preserves permissions, timestamps, symlinks and ownership, -v makes it verbose, and -z compresses in transit, which helps on slow links and costs CPU on a fast local network.

Run it without -e when SSH just works. When the port is non-standard, pass SSH options through:

rsync -avz -e 'ssh -p 2222 -i ~/.ssh/deploy_key' --partial project/ user@server:/home/user/project/

The trailing slash on the source matters more than anything else here. project/ copies the contents of the folder, while project copies the folder itself. Getting it wrong nests a directory inside another one, and rsync will happily do it without complaining.

To mirror a backup and delete files that no longer exist on the source, add --delete — and only after a dry run:

rsync -avzn --delete project/ user@server:/backups/project/

The -n flag makes it a dry run that lists what it would do without touching a single file. Once the list looks right, run the same command without the n. Add --exclude 'node_modules' style patterns to skip caches, and --partial keeps the finished portion of a large file so an interrupted transfer resumes instead of restarting.

Verify and troubleshoot the transfer

Exit status tells you whether the command worked before your eyes do. In a script, check it:

scp backup.tar user@server:/backups/ || echo "transfer failed"

Compare checksums on both ends when the data matters. Run sha256sum report.pdf locally, run ssh user@server 'sha256sum /home/user/report.pdf' remotely, and the two hashes should match character for character. A matching size is a decent signal, a matching hash is proof.

For a whole tree, diff -r local_dir remote_dir over SSH gives you a line-by-line report of anything that differs.

When something fails, these are the errors I see most:

  • Permission denied (publickey) — plain ssh works but scp does not, so the key being offered differs. Add -i /path/to/key, or check that your agent has the key loaded with ssh-add -l.
  • Connection refused — nothing is listening on that host and port. Check the port number, then confirm sshd is running on the server with systemctl status sshd.
  • Host key verification failed — the server was rebuilt or re-imaged and its key changed. Remove the stale entry with ssh-keygen -R server and reconnect to accept the new key.
  • No such file or directory — a typo in the remote path, or your user cannot write to that directory. Confirm the path by listing it over ssh first.
  • scp: Connection Closed — often a banner or motd script printing to stdout on the server, which corrupts the data stream. Move those echo lines to stderr in the login scripts.
  • The transfer crawls near the end — usually the remote side flushing to disk or a slow disk, not the network. rsync with --partial makes the next attempt cheap.

Common Mistakes

Using lowercase -p for the port. -p preserves modification times. The port flag is capital -P. There is no error message when you get this wrong.

Unquoted paths and wildcards. A local shell expands *.log before scp ever sees it, and it cannot expand anything on a remote machine. Quote remote paths that contain spaces or globs.

Forgetting the trailing slash. On rsync, the slash on the source decides whether you copy a folder or its contents. Read it twice before running a --delete against a backup.

Assuming a failed scp resumed. Plain scp has no resume. A dropped WAN connection means starting that file over, which is why rsync with --partial is the standard answer on long transfers.

Silently overwriting the destination. scp replaces files with no confirmation, and people have lost working configuration files that way. Test with -n style dry runs on rsync, or copy into a fresh timestamped directory rather than over a live one.

Assuming an old server has the modern protocol. Since OpenSSH 9.0, scp uses the SFTP subsystem by default instead of the old rcp-style protocol. Servers that have not upgraded, or that restrict the sftp subsystem in Subsystem lines in sshd_config, can fail oddly. scp -O forces the legacy protocol as a diagnostic, and rsync is usually the better answer on those hosts.

Transfer files safely in everyday use

Switch to key authentication early. Generate a key with ssh-keygen -t ed25519, push it over with ssh-copy-id user@server, then confirm a passwordless login works before you automate anything. Once keys are in place, scp and rsync become non-interactive and cron-friendly.

Never put a password on the command line. It ends up in shell history and in the process list where every other user on that machine can read it.

Use absolute remote paths and check them with ssh user@server 'ls -ld /target/dir' first. Write into a staging directory with tight permissions, then move files into place, so a half-finished upload is never something a web server can read.

For repetitive work, put an alias in ~/.ssh/config so you stop retyping hosts, users and ports:

Host backup
    HostName server
    User deploy
    Port 2222
    IdentityFile ~/.ssh/deploy_key

After that, scp dump.sql backup:/backups/ does the right thing, and rsync -avz -e ssh ./site backup:/var/www/ becomes short enough to run without reading it twice.

Frequently Asked Questions

Does SSH allow file transfer?

Yes. SSH ships two subsystems for moving data: sftp, an interactive file transfer session you drive with put and get, and scp, a one-command copy tool that takes a source and a destination. Both run inside the encrypted SSH channel, so your password, keys and the file contents are protected in transit. You do not need a separate FTP server or any extra port.

How do I copy a folder using SSH?

Use the recursive flag: scp -r folder user@server:/home/user/ uploads the folder and everything inside it, and scp -r user@server:/home/user/folder ./ downloads it. For rsync, put a slash after the source folder to copy its contents rather than the folder itself: rsync -avz folder/ user@server:/home/user/folder/. Quote any path with spaces in it.

Why is rsync faster than scp?

rsync sends only the blocks that differ between the two versions, so a repeat sync of a mostly-unchanged directory moves very little data, while scp resends whole files every time. rsync also handles metadata in one pass and can resume a partial file after a dropped connection with u002du002dpartial. On a first run with identical data and no exclusions, the two are much closer in speed.

What does the scp connection refused error mean?

It means nothing accepted a TCP connection on the host and port you asked for, so the transfer never started. Usual causes are a wrong port number, sshd not running on the target, or a firewall dropping the connection. Test with ssh -p PORT user@server, and on the server check systemctl status sshd. If the port is not 22, remember scp needs capital -P.

How do I copy files over SSH on Windows?

Windows 10 and 11 include an OpenSSH client, so scp and rsync run in PowerShell with the same syntax as Linux, using backslash or forward slash paths. For a graphical workflow, WinSCP and MobaXterm both speak SFTP over the same SSH keys and hosts file. Running rsync on Windows usually means using WSL with Ubuntu and treating its filesystem as local.

How do I copy files between two remote servers?

Copy the file to your machine first, then push it to the second server: scp source-server:/data/dump.sql ./ followed by scp dump.sql target-server:/backups/. If the servers can reach each other, the relay is automatic. When they cannot, scp -3 forces the transfer through your local machine, and ProxyJump in your ssh config routes the connection through a host they both trust.

Start with the plain command: scp -r folder user@server:/path/, run from a terminal you have already proved works with ssh. Once you need a second run, a resume, or anything larger than a handful of files, switch that same job to rsync -avz --partial with a dry run first.

If this is your first time setting up a server of your own, the ~/.ssh/config alias above removes most of the typing that follows, and it is the same file that VS Code Remote SSH reads.

Leave a Comment