Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

The scp Command: Syntax, Traps, and When to Use Something Else

Sean

Platform Writer

Sep 05, 2026
8 min read

scp copies files between machines over an SSH connection, using the syntax scp source destination where either side can be remote. That is the whole idea, and almost every problem people have with it comes from three specific details rather than the concept.

The scp Command: Syntax, Traps, and When to Use Something Else

scp has been the reflex answer to get this file onto that server for decades. It is still a reasonable reflex for one-off transfers. It is a bad reflex for anything you do twice, and there are a handful of details — an uppercase flag, a colon in a Windows path, a protocol change in OpenSSH 9 — that turn a thirty-second job into an afternoon.

Table of contents

The syntax, and the mental model that makes it stick

scp reads left to right: source first, destination second, exactly like cp. The only addition is that either side can be prefixed with user@host: to make it remote.

# Local file -> remote directory
scp report.pdf [email protected]:/var/www/uploads/

# Remote file -> local directory
scp [email protected]:/var/log/app.log ./

# A whole directory, recursively
scp -r ./dist [email protected]:/var/www/site/

# Remote to remote, routed through your machine
scp deploy@host-a:/data/dump.sql deploy@host-b:/tmp/

The colon is the part that separates host from path, and it is the part people forget. Without it, scp treats the whole thing as a local filename and you get a file literally named [email protected] in your current directory. If a transfer silently produced a strangely named local file, that is what happened.

A trailing slash on the destination directory is good practice. Without one, if the destination does not exist, scp will happily create a file with that name instead of failing, and you end up with a file called uploads containing your PDF.

The capital-P trap

This is the single most common scp mistake and it deserves its own section. To specify a non-standard SSH port, scp uses -P. Uppercase. The ssh command uses -p, lowercase, for the same thing.

ssh -p 2222 [email protected]          # lowercase p
scp -P 2222 file.txt [email protected]:~/ # uppercase P

In scp, lowercase -p means preserve modification times, access times, and modes — a completely different and perfectly valid flag, so you get no error. You get a connection attempt to port 22 that times out or is refused, while the flag you typed quietly does something else. The error message never mentions ports.

The reason for the collision is historical: scp inherited -p from cp, where it has always meant preserve, so the port flag had to go somewhere else. Knowing why does not stop you making the mistake, but it does make it the first thing you check.

Flags that earn their place

  • -r copies directories recursively. Without it, scp refuses and tells you the source is a directory.
  • -i /path/to/key uses a specific private key rather than whatever your agent offers. Essential when you have several keys and the server rejects you after too many attempts.
  • -C enables compression. Worth it for text, logs, and source; pointless or slightly harmful for already-compressed archives, images, and video.
  • -l 8000 limits bandwidth in kilobits per second. Useful when copying something large over a link you share with a live service.
  • -v prints the SSH handshake. When authentication fails, this is where the reason is.
  • -q suppresses the progress meter, which matters in scripts and CI logs.
  • -p preserves timestamps and permissions — and is not the port flag.

One combination worth remembering for debugging: scp -v tells you exactly which key files were offered and in what order, which resolves nearly every permission denied publickey message without further investigation.

What changed in OpenSSH 9

In OpenSSH 9.0, scp stopped using the legacy SCP protocol by default and started using SFTP underneath instead. The command is the same; the wire protocol is not. This was a security decision, and it is a good one — the old protocol had a design where the remote side could influence which files got written locally.

Most transfers are unaffected. A few edge cases changed behaviour, and if you have a script that used to work and now does not, this is a likely cause:

  • Shell glob expansion on the remote side behaves differently, because the remote shell is no longer involved in the same way.
  • Paths containing characters the old protocol passed through a shell may now be interpreted literally.
  • Some restricted or embedded servers offer the SCP protocol but no SFTP subsystem, so the transfer fails outright.

For that last case, scp -O forces the legacy protocol. Reach for it only when you have confirmed the server has no SFTP subsystem, not as a general fix for a transfer you have not diagnosed.

When scp is the wrong tool

scp copies everything, every time, with no idea what is already there. That is fine for a single file and wasteful for anything else.

For a directory you sync repeatedly, rsync is strictly better. It transfers only what changed, can resume an interrupted transfer, and can delete files on the destination that no longer exist on the source. It also runs over SSH, so the authentication you already have keeps working.

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

For anything interactive — browsing a remote tree, checking what is there before you overwrite it — sftp is the tool, and any graphical SFTP client is easier than guessing paths.

And for deploying an application, none of these is the right answer. Copying build output onto a server by hand means no record of what is running, no way to roll back beyond a directory you hopefully renamed, and a deploy that only works when the person who knows the command is awake. A pipeline that builds from a commit and keeps the previous release available is not more complicated; it is the same work done once instead of every time.

Permission denied, and the other usual failures

Permission denied (publickey) means authentication, not file permissions. Run with -v and look at which keys were offered. Common causes: the key is not loaded into your agent, the private key file has permissions wider than 600 so SSH refuses to use it, or the server offered too many keys before the right one and hit MaxAuthTries.

Permission denied on the write, after a successful login, is different — that is the remote user lacking write access to the destination directory. Copy to a directory the user owns, such as their home, then move it with sudo in a separate step. Do not make /var/www world-writable to make an scp work.

No such file or directory pointing at the destination usually means an intermediate directory does not exist. scp will not create a path for you. And on Windows, a source path like C:\data\file.txt confuses the parser because the colon looks like a host separator; use a forward-slash path or run it from a shell where the path has no drive letter.

How this fits the rest of the stack

scp is the right tool for a file you will copy once, and the wrong one for a deploy you will run a hundred times. If the ad-hoc copying is really a deployment step in disguise, the version worth building instead is a repository push that produces a build log, a live route, and a previous release you can return to. The RunxBuild hosting calculator shows what that path costs across the service, the storage, and the bandwidth, which is a more honest comparison than a free command against a paid platform.

Useful related references:

FAQ

Why does scp use -P for the port instead of -p?

Because scp inherited -p from cp, where it means preserve modification times and permissions. The port flag had to go somewhere, so it became uppercase -P. The ssh command has no such conflict, which is why it uses lowercase -p. Typing the wrong case gives no error, just a connection to the default port.

How do I copy an entire folder with scp?

Add -r for recursive: scp -r ./localdir user@host:/remote/path/. Watch the trailing slashes — scp -r ./dist user@host:/var/www/ creates /var/www/dist, which is usually what you want. For folders you sync more than once, rsync is a better fit because it only transfers what changed.

Is scp deprecated?

The scp command is not deprecated, but the protocol it originally used is. Since OpenSSH 9.0 the command runs over SFTP underneath by default. The syntax is unchanged. A few scripts that relied on remote shell glob behaviour needed adjusting, and scp -O forces the old protocol for servers that lack an SFTP subsystem.

What causes permission denied publickey with scp?

It is an authentication failure, not a file-permission one. Usually the key is not in your agent, the private key file is readable by others so SSH refuses it, or the server hit its authentication attempt limit after being offered several wrong keys first. Run scp -v to see exactly which keys were tried.

Should I use scp to deploy my application?

For a one-off it is fine. As a deployment process it leaves you with no record of what is running and no reliable way back to the previous version. A build triggered from a commit, producing a log and a rollback target, removes the class of incident where nobody is sure which files are on the server.

#scp command#ssh#file transfer#linux#rsync