How to copy files between servers with scp and rsync
For one file, scp file.tar.gz user@server:/path/ is enough. For directories, large files or anything you will copy again, use rsync -a --info=progress2 /src/ user@server:/dest/: it keeps permissions and times, resumes with --partial, and later runs send only what changed. To copy from one server to another, give one of them its own restricted key for the other instead of forwarding your agent, then check the result with checksums.
Pick the tool
| Tool | Use it for | Keeps |
|---|---|---|
| scp | One file or a few, once. | Times and modes with -p; never owners. Follows symlinks. |
| rsync | Directories, large files, repeated copies. | Everything, with -aHAX --numeric-ids as root. Resumes; sends only changes. |
| tar over ssh | A first copy of a huge tree, or a server without rsync. | Owners, modes and links, when extracted as root. |
| sftp | Browsing and fetching by hand. | Times and modes with -p. Resumes with reget. |
All four use the same SSH logins, keys and ports as ssh. The examples copy between 203.0.113.10 and 198.51.100.20 as deploy.
Copy files with scp
scp works like cp with user@host: in front of one path. Upload a file:
scp backup.tar.gz [email protected]:/var/backups/Download one into the current directory:
scp [email protected]:/var/log/nginx/access.log .Copy a directory, keeping times and modes, to a server whose SSH listens on port 2222:
scp -r -p -P 2222 /var/www/html [email protected]:/srv/| Flag | What it does |
|---|---|
-r | Copy directories. scp follows symlinks, so a link to a large directory arrives as a full copy of it. |
-p | Keep modification times, access times and modes. Owners are never kept: files belong to the account you log in as. |
-P 2222 | The SSH port. Capital P, because -p was taken; ssh itself uses lowercase -p. |
-i ~/.ssh/key | The private key to use. |
-l 80000 | Bandwidth cap in Kbit/s (80000 is about 10 MB/s). rsync's --bwlimit counts KiB/s instead. |
scp overwrites existing files without asking and cannot resume: an interrupted copy starts again from zero.
scp uses SFTP since OpenSSH 9.0
Since OpenSSH 9.0 (April 2022), scp uses the SFTP protocol instead of the old scp/rcp protocol. Ubuntu 22.10 and later have it; Ubuntu 22.04 ships OpenSSH 8.9 and still uses the old protocol by default. Two things changed:
- Remote file names are no longer expanded by the remote shell, so the extra layer of quoting old scp needed for spaces and special characters is gone. Old commands that still add it can fail.
~user/paths need the SFTP server from OpenSSH 8.7 or later on the other end.
For a server without an SFTP server (some appliances and minimal systems), -O uses the legacy protocol:
scp -O backup.tar.gz [email protected]:/var/backups/Copy with rsync over SSH
rsync runs over SSH whenever one path starts with host:, and it must be installed on both machines. A second run sends only what changed, so it is the right tool for directories and repeated copies:
rsync -a --info=progress2 /var/www/ [email protected]:/var/www/| Flag | What it does |
|---|---|
-a | Archive mode: recurse and keep symlinks, permissions, times, group, owner (only when the receiving side runs as root) and devices. |
--info=progress2 | One progress line for the whole transfer instead of one per file (leave out -v). |
-P | Same as --partial --progress: keep a partly sent file so the next run resumes it. |
-e "ssh -p 2222 -i ~/.ssh/key" | The ssh command to use, for another port or key. |
-n, --dry-run | Show what would happen and change nothing. Add -i to see why each file would be sent. |
--delete | Remove receiver files the sender does not have. A wrong or empty source empties the destination: dry-run first. |
-H -A -X --numeric-ids | Keep hard links, ACLs, extended attributes, and owners by number: what you want when moving a whole tree. |
--rsync-path="sudo rsync" | Run rsync as root on the far side, for files only root can read or write. Needs passwordless sudo for rsync. |
--bwlimit=20m | Cap the rate at 20 MiB/s. A plain number is KiB/s. |
Mind the trailing slash: /var/www/ copies the directory's contents and /var/www the directory itself, so rsync -a /var/www host:/srv/www/ gives you /srv/www/www. How to back up a server with rsync covers this, --delete and dated copies in depth.
-z compresses data on the wire only. It pays off for text on slow links: in our test a 13.4 MB text file crossed as 2.5 MB. It is wasted on already compressed data (archives, images, video, gzipped dumps): a 50 MB file of random data crossed as 50 MB either way and took twice as long with -z. rsync's --skip-compress list currently has no effect, so leave -z off for such data and on fast networks.
Copy directly from one server to another
rsync handles one remote end at a time; with two remote paths it stops with The source and destination cannot both be remote. Three ways around that:
- A key on one server for the other. Best for anything large or repeated; steps below.
- Through your own computer.
scp [email protected]:/var/backups/site.tar.gz [email protected]:/var/backups/connects to both and relays the data, scp's default since OpenSSH 8.7 (-3asks for it explicitly). No credentials land on either server, but every byte crosses your connection twice.-Rmakes the first server connect to the second instead, which needs a key there anyway. - Agent forwarding (
ssh -A): your local agent signs the first server's login to the second. While you are connected, anyone with root on the first server can use your agent to log in wherever your keys work, without ever copying the key. Avoid it.
Say server B (198.51.100.20) should pull from server A (203.0.113.10). Pulling from the destination means A is only read. On B, as the user who will run the copy, create a key without a passphrase (scripts will use it; the restrictions on A protect it):
ssh-keygen -t ed25519 -N "" -C "server-b pull from server-a" -f ~/.ssh/pull_from_aOn A, add B's ~/.ssh/pull_from_a.pub as one line in the authorized_keys of an account that can read the files. from= accepts the key only from B's address; restrict turns off forwarding and terminals, which rsync and scp do not need:
restrict,from="198.51.100.20" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILnq6CNYp0H1cWbX462YZmSVjeDHtuvEIt6GtZRPN7VJ server-b pull from server-aFor a read-only, rsync-only key, add command="/usr/bin/rrsync -ro /var/www" to those options (rrsync comes with rsync). Paths are then inside /var/www, so pull [email protected]:/. In our test, rrsync let pulls through and refused a push with sending to read-only server is not allowed. Now run the copy on B:
rsync -a --info=progress2 -e "ssh -i ~/.ssh/pull_from_a" [email protected]:/var/www/ /var/www/The first run shows A's host key (ED25519 key fingerprint is SHA256:...) and asks whether to continue. Compare it with the real one, printed on A:
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pubPaste that fingerprint at the prompt instead of typing yes, and ssh compares them for you. When the job is finished, delete the line from A's authorized_keys. Moving a whole server takes more (matching users, a final pass, databases, DNS): see moving a server to a new provider.
Copy many small files with tar over SSH
tar packs a tree into one stream that ssh carries to another tar on the far side: nothing is written to disk in between, there is no file list to exchange, and the other server needs only tar. Run it on the source, with the destination directory already created:
tar -C /var/www -cf - . | ssh [email protected] 'tar -C /var/www -xf -'-C /var/wwwchanges into the directory first, so paths in the stream are relative.-f -writes the archive to standard output, and on the far side reads it from standard input. The single quotes pass the second command to the other server unchanged.- Owners and modes are restored only when the receiving tar runs as root (GNU tar's default for root). Add
--numeric-owneron both sides to keep owners by number, and--acls --xattrsif you use them. Hard links arrive as hard links. - On a slow link with compressible data, add
zon both sides (-czf -and-xzf -).
To pull instead, put ssh first:
ssh [email protected] 'tar -C /var/www -cf - .' | tar -C /var/www -xf -Whether tar beats rsync depends on your disks and network: in our test with 50,000 small files, times varied more between runs than between the tools, so measure first. A common pattern is tar for the first copy, then rsync to pick up later changes and check the result. More on tar in tar backups.
Resume an interrupted transfer
Interrupted without --partial, rsync deletes the half-sent file. With it, the next run uses the partial file as its starting point. In our test, we stopped a 50 MB transfer after 15.6 MB; the rerun matched those 15,630,160 bytes and sent only the remaining 34.4 MB:
rsync -a -P -e "ssh -p 2222" [email protected]:/var/backups/site.tar.gz /var/backups/Rerun it until it completes. rsync checks every transferred file against a whole-file checksum, so a resumed file is still verified. sftp's reget and reput also resume, but assume the partial file matches the original, so check the result. scp cannot resume.
Check that the copy is complete
If both sides have rsync, a checksum dry run lists every file whose content differs and every extra file --delete would remove. No output means the trees match:
rsync -anci --delete /var/www/ [email protected]:/var/www/<fc........ html/index.php< means the file would be sent to the remote side, c that its checksum differs. In our test that file had one changed byte with the same size and time: the dry run without -c printed nothing, and both runs exited 0, so scripts must check for output. Without rsync, list checksums on the source:
cd /var/www && find . -type f -print0 | sort -z | xargs -0 sha256sum > /tmp/www.sha256scp /tmp/www.sha256 [email protected]:/tmp/Then check it on the destination:
cd /var/www && sha256sum -c --quiet /tmp/www.sha256--quiet prints only failures, so silence and exit status 0 mean every file matches. A mismatch looks like this, with exit status 1:
./html/index.php: FAILED
sha256sum: WARNING: 1 computed checksum did NOT matchThe list cannot notice extra files on the destination; compare find /var/www -type f | wc -l on both sides for that.
sftp in brief
sftp opens an interactive session over the same SSH login:
sftp -P 2222 [email protected]Inside, ls and cd act on the server and lls and lcd on your machine; get -R dir and put -R dir copy directories (without following symlinks), and bye exits. sftp -b commands.txt [email protected] runs a list of commands from a script and stops at the first one that fails; it needs key login.
Common errors
| Error | Cause and fix |
|---|---|
The source and destination cannot both be remote. | rsync copies between the machine it runs on and one remote. Run it on one of the servers, as above. |
rsync: connection unexpectedly closed (0 bytes received so far) [sender] then rsync error: remote command not found (code 127) | rsync is not installed on the other server. Install it there (sudo apt install rsync), or point --rsync-path at it. |
protocol version mismatch -- is your shell clean? | A shell startup file on the other server (.bashrc, .profile) prints text for non-interactive logins, and rsync reads it as data. ssh [email protected] /bin/true > out.dat must leave out.dat empty; make those lines print only in interactive shells. |
Host key verification failed. | ssh could not ask you to accept the host key (a cron job, -o BatchMode=yes), or the key changed. Connect once by hand as the same user and check the fingerprint. |
Permission denied (publickey). | That account does not accept the key ssh offered. Name the key with -i (inside -e for rsync) and check the account's authorized_keys. |
| scp fails against an old server or appliance, ssh works | The server has no SFTP server. Use scp -O. |
Frequently asked questions
- Is rsync faster than scp?
- For a first copy, both send everything over SSH, so the network usually sets the pace. On every later copy rsync sends only what changed, and it can resume an interrupted file; scp sends everything again.
- How do I copy files between two remote servers?
- Log in to one of them and run rsync or scp there, with a key the other server accepts, ideally a dedicated key limited with
from=andrestrict.scp host1:/path host2:/pathfrom your own computer also works; the data then passes through your machine. - Does scp keep file permissions and ownership?
- With
-pit keeps modes and times. It never keeps owners: files belong to the account you log in as. To keep owners, usersync -awith root on the receiving side, or tar extracted as root. - How do I resume an interrupted scp transfer?
- scp cannot resume. Run the same copy with
rsync -Pinstead: it keeps the partial file and sends only the rest. - How do I use rsync with a different SSH port?
- Pass the ssh command with
-e:rsync -a -e "ssh -p 2222" /src/ user@host:/dest/.
How this was checked
The commands were run on Ubuntu 24.04 LTS, OpenSSH 9.6p1, rsync 3.2.7, GNU tar 1.35 on October 4, 2026. Any that need something this test server does not have, such as a second server, a cloud account or another database engine, were checked against the official pages below instead.
Sources, on October 4, 2026:
- OpenSSH scp(1) manual
- scp(1) manual (Ubuntu 22.04)
- OpenSSH sftp(1) manual
- OpenSSH ssh(1) manual
- OpenSSH ssh-keygen(1) manual
- OpenSSH sshd(8) manual: AUTHORIZED_KEYS FILE FORMAT
- OpenSSH release notes (8.7 and 9.0)
- Ubuntu packages: openssh-client (22.04)
- rsync(1) manual
- rrsync(1) manual (Ubuntu 24.04)
- tar(1) manual (Ubuntu 24.04)
- sha256sum(1) manual (Ubuntu 24.04)