scp file.txt user@host:/path/ is the answer everyone gives, and for one small file it is fine. For anything else, use rsync. It resumes interrupted transfers, skips files that have not changed, shows you progress, and preserves permissions properly. The OpenSSH maintainers themselves consider the scp protocol outdated and have said so in the release notes - it now runs over SFTP under the hood precisely because the original protocol was a problem. If you are copying a directory, a large file, or anything you might have to copy again, rsync is simply the better tool.
Table of contents
- scp, for when it genuinely is one small file
- rsync, the one you should actually use
- The rsync flags worth knowing
- sftp, for browsing rather than scripting
- Transferring while already SSHed in
- How this fits the rest of the stack
- FAQ
scp, for when it genuinely is one small file
# local to remote
scp report.pdf [email protected]:/var/www/uploads/
# remote to local (note the . for current directory)
scp [email protected]:/var/log/app.log .
# a whole directory
scp -r ./dist [email protected]:/var/www/app/
# non-standard port - capital P, unlike ssh's lowercase
scp -P 2222 file.txt [email protected]:/tmp/
The -P for port is a classic trip-hazard: ssh uses lowercase -p, scp uses uppercase -P, and lowercase -p in scp means preserve timestamps. Everyone gets this wrong at least once.
What scp does not do, and this is the whole argument:
- No resume. The connection drops at 90% of a 4GB file and you start again from zero.
- No progress detail. You get a percentage and very little else.
- No skipping. Re-run it and it copies everything again, including the 400 files that did not change.
- Poor error handling on partial transfers - you can end up with a truncated file at the destination and a zero exit code that suggests everything went fine.
For a config file, scp is fine. For anything you care about, it is the wrong tool.
rsync, the one you should actually use
rsync -avz --progress ./dist/ [email protected]:/var/www/app/
The flags:
-a- archive mode: recursive, and preserves permissions, timestamps, symlinks, and ownership. This is what you want almost always.-v- verbose, so you can see what it decided to do.-z- compress in transit. Excellent for text and code, pointless for already-compressed media.--progress- real progress, per file.
The trailing slash on the source is load-bearing and it will bite you. ./dist/ copies the contents of dist into the target. ./dist (no slash) copies the directory itself, producing /var/www/app/dist/. This is the single most common rsync mistake, and it is why people end up with app/dist/dist/.
The reason to use it:
rsync -avz --partial --progress bigfile.tar.gz deploy@host:/backups/
--partial keeps what has already transferred if the connection dies, so re-running resumes rather than restarting. On a slow link with a large file, this is the difference between a transfer that eventually completes and one that never does.
And on re-runs, rsync compares the two sides and sends only what changed. Deploying a build directory where three files differ transfers three files, not four hundred. That property alone makes it the right default.
The rsync flags worth knowing
A few that turn it from a copy tool into a deployment tool.
--delete - remove files at the destination that no longer exist at the source. This makes the target a mirror rather than an accumulation. It is exactly what you want for deploying a build, and it is exactly what will destroy the wrong directory if you point it somewhere unexpected.
rsync -avz --delete ./dist/ deploy@host:/var/www/app/
--dry-run (or -n) - show what would happen without doing it. Always run this first when --delete is involved. It costs three seconds and it has saved a great many /var/www directories.
rsync -avzn --delete ./dist/ deploy@host:/var/www/app/
--exclude - skip things that should never ship.
rsync -avz --exclude '.git' --exclude 'node_modules' --exclude '.env' \
./ deploy@host:/var/www/app/
-e - specify the SSH command, for a non-standard port or a specific key.
rsync -avz -e "ssh -p 2222 -i ~/.ssh/deploy_key" ./dist/ deploy@host:/var/www/app/
Or configure the host in ~/.ssh/config once and never think about it again - rsync reads that file, which is one more reason to set it up.
sftp, for browsing rather than scripting
sftp gives you an interactive session, which is the right tool when you do not yet know what you want to move.
sftp [email protected]
sftp> cd /var/log
sftp> ls -la
sftp> get app.log
sftp> put config.yaml
sftp> bye
get pulls, put pushes, and the usual cd, ls, and pwd work on the remote side (lcd and lls operate locally).
This is also what FileZilla, Cyberduck, and the SFTP support in your editor are speaking underneath. If you want a GUI, that is a perfectly reasonable choice - and worth knowing that the protocol is the same one scp now uses internally.
When sftp is the right answer: exploring a remote filesystem, grabbing one log file you have just located, or handing a non-technical colleague a way to upload something. For anything scripted or repeated, use rsync.
Transferring while already SSHed in
A genuinely common confusion: you are logged into the server, you want to copy a file to your laptop, and you cannot - because scp from inside the session would need to connect back to your machine, which usually is not reachable.
The answers, in order of practicality:
1. Open a second terminal. Run the scp or rsync from your local machine. This is almost always the right answer and it takes five seconds.
2. Use SSH escape sequences. Inside a session, press Enter, then ~C to open the ssh command line, and set up port forwarding on the fly. Powerful, obscure, rarely worth it.
3. Pipe it through a new SSH connection. From your local machine:
ssh deploy@host "cat /var/log/app.log" > app.log
# or a whole directory, streamed and compressed
ssh deploy@host "tar czf - /var/log/app" > app-logs.tar.gz
That tar over SSH pattern is genuinely useful for large directories, and it is fast - no temporary files on either side, just a compressed stream over the wire.
The mental model that clears up the confusion: scp and rsync always run from the machine that initiates the connection. Being logged into the server does not change where the tool runs. Go back to your own terminal.
How this fits the rest of the stack
Whatever you decide here, the cost of the decision only shows up as a bill. The RunxBuild hosting calculator is the right place to model that before committing: the compute, the database, the storage, the bandwidth, the worker - each one is a separate line item, and the real cost of a platform is the sum, not the headline number. The RunxBuild dashboard is where the team sees the actual usage once it is running.
Useful related references:
- SSH Config File: ~/.ssh/config for Per-Host Settings, ProxyJump, and Identities
- Can’t Open the SSH Pub File? Here’s What’s Going On
- SSH Ports Beyond 22: When to Move, When to Stay
- Services on RunxBuild
FAQ
What is the best way to send a file over SSH?
rsync for anything other than a single small file. It resumes interrupted transfers, skips unchanged files on re-runs, shows real progress, and preserves permissions properly. scp is fine for one small file but has no resume and no skipping.
Is scp deprecated?
The scp protocol is considered outdated by the OpenSSH maintainers, and modern scp runs over SFTP internally as a result. The command still works and is not going away, but rsync or sftp is the better choice for anything non-trivial.
What does the trailing slash do in rsync?
Everything. A source of ./dist/ copies the contents of dist into the target; ./dist without the slash copies the directory itself, giving you target/dist/. This is the most common rsync mistake and the reason people end up with app/dist/dist.
How do I resume an interrupted file transfer?
rsync with —partial keeps what has already been transferred, so re-running resumes instead of starting over. scp cannot do this - a dropped connection at 90% of a large file means starting again from zero.
How do I copy a file to my laptop while I am SSHed into the server?
Open a second terminal and run scp or rsync from your local machine - the tool always runs on the side that initiates the connection, so being logged in does not help. Alternatively, from your local machine, ssh host “tar czf - /path” > archive.tar.gz streams a whole directory back in one command.