Skip to content
shellmap

Copy files over SSH

Move one or more files between your local machine and a remote host over an SSH-encrypted channel — the standard `scp` / `rsync` / `pscp` operation. The canonical "deploy this build artifact" or "pull these logs back for inspection" gesture.

How to copy files over ssh in each shell

Bashunix
scp ./build.tar.gz user@host:/srv/releases/

Canonical `scp` syntax: `scp <src> <dst>`. Remote side uses `user@host:/path`. Recursive: `-r`. Preserve modes/times: `-p`. Limit bandwidth: `-l 8000` (8000 Kbit/s). Non-default port: `-P 2222` (capital P — NOT lowercase like ssh). For LARGE / resumable / many-file transfers, prefer `rsync -avz --partial --progress -e ssh ./dir/ user@host:/path/` — rsync resumes interrupted transfers (scp re-copies from byte 0). CAVEAT since OpenSSH 9.0 (Apr 2022): `scp` defaults to the SFTP protocol internally; this rejects some legacy server-side filename patterns. Use `-O` to force the old SCP protocol if you hit "subsystem request failed".

Zshunix
scp ./build.tar.gz user@host:/srv/releases/
Fishunix
scp ./build.tar.gz user@host:/srv/releases/

No fish-side wrinkle — `scp` is a separate binary, fish does not parse the `user@host:/path` syntax. CAVEAT: fish autocompletes the `:` in `user@host:` as a path SEPARATOR in some versions, which can leave you typing `user@host\\:/path`. Workaround: type the colon manually after the host, then tab-complete the remote path. fish-shell/fish-shell#7152 tracks the upstream fix.

PowerShellwindows
scp .\build.tar.gz user@host:/srv/releases/

pwsh 7+ on Windows 10 1809+ ships OpenSSH built-in — same `scp`/`ssh` binaries as Linux/macOS. Recursive: `scp -r .\dist user@host:/var/www/`. Path-style: pwsh accepts both forward-slash and backslash on local paths; remote path must be `/forward/slashes` (Linux remote convention). On older Windows or pwsh 5.1: `scp` may not be on PATH — install OpenSSH client via `Add-WindowsCapability -Online -Name OpenSSH.Client*` (one-time, admin). For modern PowerShell-native: the `Posh-SSH` module gives `Get-SCPFile`/`Set-SCPFile`/`Set-SCPFolder` with PSObject pipelines — useful when you want exit-code-as-rich-object instead of plain text.

cmd.exewindows
scp build.tar.gz user@host:/srv/releases/

Same `scp.exe` shipped with Windows OpenSSH. For legacy systems without OpenSSH: `pscp.exe -i C:\keys\id.ppk build.tar.gz user@host:/srv/releases/` (PuTTY suite; note `-i` accepts PuTTY's own `.ppk` key format, NOT OpenSSH PEM — use `puttygen` to convert). Recursive: `pscp -r dist user@host:/var/www/`. The `pscp` ergonomics differ slightly: no `-P` port flag — use `-P <port>` (same form, but check `pscp -h` because old versions used `-p` for port). For automated CI on legacy Windows servers, the WinSCP CLI (`winscp.com /command "open …" "put …"`) is the heavier-weight scriptable alternative.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **`scp` vs `rsync` vs `sftp` — three tools, one channel.** All three transport over SSH but have different ergonomics. `scp` is one-shot: re-copies every file every time, no resume on interruption, no diff-against-remote. Simplest syntax. `rsync -avz -e ssh src/ host:dst/` (note the trailing `/` on src — copies CONTENTS not the directory itself; with no trailing slash you get an extra nested dir on the remote — this is the #1 rsync mistake) does an incremental sync: checksums to skip unchanged files, partial-file resume with `--partial`, deletion-sync with `--delete`, exclusion patterns with `--exclude=*.log`. `sftp` is interactive (`sftp user@host`) but also batch-mode (`sftp -b commands.txt user@host`) — useful when you need a session-style flow (mkdir, cd, put, ls). For most cases: scp for one-shot small files, rsync for everything else, sftp when scripting an interactive-looking session.
  • **SSH agent and key-based auth eliminate password prompts.** `scp`/`rsync` invoke `ssh` internally for the transport. If your key is loaded in `ssh-agent` (`ssh-add ~/.ssh/id_ed25519` once per session) it is used automatically. Without an agent: `scp -i ~/.ssh/id_ed25519 src host:dst` (`-i` for identity file). For rsync: `rsync -e "ssh -i ~/.ssh/id_ed25519" src host:dst` (the `-e` flag overrides the entire SSH invocation). Windows pwsh: the OpenSSH agent service must be running — `Get-Service ssh-agent` then `Start-Service ssh-agent; Set-Service ssh-agent -StartupType Automatic`. With agent forwarding (`ssh -A`), the remote can use your local key to ssh further — convenient but a security risk on untrusted hosts (root on the remote can use your forwarded key while you're connected). Don't forward to boxes you don't fully control.
  • **Non-default port, jump host, and complex destinations.** Non-default port: `scp -P 2222 src host:dst` (capital P — unlike `ssh -p` lowercase, a frequent gotcha). Jump host: `scp -J jumpuser@bastion src host:dst` (OpenSSH 7.3+); equivalent for rsync: `rsync -e "ssh -J jumpuser@bastion" src host:dst`. Better than `-J`: configure `~/.ssh/config` once: `Host prod`/`HostName prod.internal`/`ProxyJump bastion`/`User deploy` — then `scp src prod:dst` just works. For different ports/users per host, the config file is the only sane long-term approach. CAVEAT: on Windows pwsh, `~/.ssh/config` is `%USERPROFILE%\.ssh\config` — the `~` expands but `~/.ssh` may not exist by default (create it: `mkdir $env:USERPROFILE\.ssh`).
  • **OpenSSH 9.0+ changed scp under the hood — `-O` is the legacy escape hatch.** Since OpenSSH 9.0 (released 2022-04-08), `scp` internally uses the SFTP protocol instead of the legacy SCP protocol. This breaks some edge cases: remote-glob expansion (`scp host:"/var/log/*.log" .` — the legacy SCP protocol expanded the glob server-side via the remote shell; SFTP does NOT expand globs), and a few servers (legacy embedded SSH, OpenSSH < 5.0) that don't support SFTP. Symptom: `subsystem request failed on channel 0` or `lost connection` errors. Fix: `scp -O src host:dst` (`-O` forces the legacy SCP protocol — capital O). Alternatively, `sftp` for the modern case; `rsync` for glob-on-remote workflows (`rsync host:"/var/log/*.log" .` works because rsync uses its own protocol, not SCP).
  • **Pull from remote vs push to remote — same syntax, swap order.** `scp local-file user@host:/remote/path` PUSHES to remote. `scp user@host:/remote/path local-file` PULLS to local. Recursive directory pull: `scp -r user@host:/var/log/ ./logs/`. For "copy file BETWEEN two remotes" (third-party copy): `scp -3 user1@host1:/path1 user2@host2:/path2` (the `-3` routes through the local machine; without it scp tries a direct host1→host2 connection which usually fails behind NAT/firewalls). Bandwidth angle: scp uses one TCP connection — slow on lossy links. rsync over a fast WAN: `rsync -avz --compress-level=6 -e "ssh -c [email protected]" …` (compression `-z` + faster cipher = throughput boost on CPU-bound paths). For TB-scale transfers, `mbuffer` between rsync and ssh smooths bursty I/O; for one-shot multi-TB, `tar` over `ssh` (`tar c src | ssh host "tar x -C /dst/"`) often beats both because it avoids per-file overhead.

Related tasks