Copying the same files repeatedly?
Use rsync instead: Rsync Remote Backup has the script, and the Rsync Command Builder builds the command. Setting up the key scp uses is SSH Key Setup.
scp is the copy command everyone already knows, which is why its sharp edges survive in scripts for years. Since OpenSSH 9.0 it speaks SFTP by default, which quietly changed how remote paths are quoted, and -O brings the old rules back. -P is the port and -p is something else. And exit 0 means the transfer finished, not that the file on the other end is complete and correct, which matters for the backup that a cron job copies off the box every night.
What Are the scp Commands Worth Knowing?
Reproduced here on 2026-10-03 (Kali, OpenSSH 10.5p1) against a throwaway sshd listening on 127.0.0.1 port 2222, so nothing real was touched. demo is a ~/.ssh/config alias for it (below), and the remote directory, a scratch directory, is shown as /srv/drop.
Upload, download, and a directory with -r. A trailing / on the remote side means "into this directory". -q (not used here) hides the progress bar, which you want in cron logs.
-p keeps the modification time, which backups and make-style "is it newer?" checks depend on:
Without -p the copy is stamped with the time of the copy. Lowercase -p is "preserve"; the port is capital -P. ssh uses lowercase -p for the port, so the two commands disagree, which is the best argument for the config alias.
The demo's alias, with its scratch paths shortened:
With that in place, scp, ssh, sftp and rsync all reach the server with the same short name and no flags to get wrong.
How Do I Copy Remote Paths With Spaces?
In the default SFTP mode, quote once for your own shell:
With -O, the legacy SCP protocol, the remote shell parses the path too, and splits it at the space:
Double quoting (local quotes around remote quotes) is the old rule, and most scp tutorials written before 2022 teach it. It is now only for -O, which you need just for servers without an SFTP subsystem. Remote wildcards work in SFTP mode too; quote them so your local shell does not expand them first:
What Exit Codes Does scp Return?
1 is a file or permission problem; 255 is ssh failing to connect or authenticate. In a script that runs unattended, add -o BatchMode=yes so a missing key fails with 255 instead of waiting forever at a password prompt, and -o ConnectTimeout=10 so a dead host fails fast. Both codes, and what 255 means for ssh in general, are in the Bash Exit Code Lookup.
The Script
Exit 0 says the bytes were sent. It does not say the remote file matches, and a transfer killed halfway leaves a partial file under its real name for the next job to pick up. Save as scp-verified.sh: it uploads to a hidden .name.part, compares SHA-256 on both ends, and only then renames it into place.
Prerequisites
OpenSSH client on your side; on the remote side, sshd plus sha256sum and mv (coreutils, present on every Linux server). Key-based login set up, ideally through a ~/.ssh/config alias.
How Does the Script Work?
.name.partis where the bytes land first. A transfer that dies leaves only a hidden temp file, never a truncated file under the real name.sha256sumon both ends checks what actually arrived. scp's exit status cannot tell you that.printf '%q'quotes paths for the remote shell, because thessh … "command"calls run through it. That is whymy file.txtbelow works without any manual double quoting.ssh -nkeeps those calls from reading the script's stdin, the same trap as an ssh call inside a while loop.mv -fin the same directory is an atomic rename, so anything watching the drop directory sees a complete file appear.FAILEDcounts problems without stopping the batch, and the exit code still reports them.
What Does the Script Print?
Three files, one with a space in its name, plus one that does not exist:
No .part files left behind. (SSH_OPTS points at the demo's own config file; with the alias in ~/.ssh/config you would leave it out.)
When Should I Use rsync Instead?
Every scp run copies every byte again. rsync compares first and sends only what changed, resumes with --partial, checksums each file as part of the transfer, and mirrors deletions with --delete. For a nightly copy of a backup directory that is the difference between minutes and seconds, and between a broken half-copy and a resumed one. Rsync Remote Backup is the script for that; Run Commands on a Remote Server over SSH covers what to do once the file is there. The Production Bash Toolkit ships its scripts ShellCheck-clean.
Frequently Asked Questions
How do I scp a file with spaces in the name?
With OpenSSH 9.0 or later in its default SFTP mode, quote the whole argument once for your local shell: scp "my file.txt" "host:/srv/drop/my file.txt". With scp -O, the legacy protocol, the remote path is also parsed by the remote shell, so it needs a second set of quotes inside: "host:'/srv/drop/my file.txt'". Without them, -O fails with scp: ambiguous target.
What port option does scp use?
Capital -P: scp -P 2222 file host:/path/. ssh uses lowercase -p for the port, and scp's lowercase -p means preserve times and modes, so mixing them up silently changes what the command does. Putting Port 2222 under a Host entry in ~/.ssh/config avoids the difference entirely, because scp, ssh, sftp and rsync all read it.
What does scp -O do?
-O forces the legacy SCP protocol instead of SFTP, which OpenSSH scp uses by default since version 9.0. You need it only for servers without an SFTP subsystem, such as some embedded devices and old appliances. Its main side effect is that remote paths are interpreted by the remote shell again, so spaces and wildcards need remote-side quoting, and file names are expanded by that shell.
How do I check if scp succeeded in a bash script?
Test its exit status: if scp -q file host:/path/; then … else … fi, or capture rc=$? straight after. scp exits 0 on success, 1 on errors like a missing source or a permission problem on the remote side, and 255 when ssh cannot connect or authenticate. Exit 0 means the transfer completed, not that the remote copy is identical; for that, compare checksums on both ends as the script on this page does.
Should I use scp or rsync?
Use scp for a one-off copy of a few files. Use rsync for anything repeated or large: it sends only files and blocks that changed, can resume an interrupted transfer with --partial, verifies each file with a checksum as part of the transfer, and can delete files that disappeared from the source with --delete. Both run over ssh and read the same ~/.ssh/config, so switching is usually a one-word change.
Part of the bash snippets collection
Related Scripts
- Rsync Remote Backup — repeated or large copies, only the changes sent
- SSH Key Setup — the key scp and the script authenticate with
- Run Commands on a Remote Server over SSH — what to run once the file has arrived
- Bash Heredoc — sending a whole script to the remote side