The first remote copy usually feels magical: one command, a password prompt, and the file appears on another machine. The second copy is where the questions begin—did the trailing slash create an extra directory, will an interrupted 80 GB transfer restart, did ownership survive, and was that really the intended server? This article treats the path and verification details as part of the transfer, not cleanup afterward.
Prerequisites and trust checks
The source can reach the destination’s SSH service and the account is authorized to write the target directory.
SSH host identity is verified from a trusted fingerprint/source; never silence host-key checking to make automation “work.”
Enough destination space and inodes exist, and quotas/read-only mounts are understood.
Source data is stable or snapshot-consistent; copying live databases, VM images, mail stores, or changing application state can produce an unusable point-in-time result.
Required tools exist on the correct side: modern
scpuses SFTP over SSH; remote-shell rsync normally requires compatible rsync programs on both hosts.Ownership, ACL, xattr, hard-link, sparse-file, device, and security-label requirements are written down before selecting flags.
Test SSH before moving data
ssh -v deploy@files.example.com 'printf "connected as %s\n" "$(id -un)"'... authenticated ...
connected as deployAuthentication success is only the first check
-vexposes connection, host-key, and authentication decisions for diagnosis; it can reveal infrastructure details, so sanitize logs.The quoted command runs remotely and confirms the effective account.
Verify the displayed host fingerprint through a trusted channel on first connection.
Prefer scoped keys or certificates, an agent with controlled forwarding, and server-side least privilege over passwords embedded in commands.
Test target write permissions separately without overwriting production data.
Put connection details in SSH configuration
Host archive-host
HostName files.example.com
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519_archive
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 3One reviewed alias reduces command drift
The alias
archive-hostcan be used byssh,scp,sftp, and rsync’s SSH transport.IdentitiesOnly yeslimits authentication attempts to configured identities and explicit agent identities.Keep private keys readable only by their owner and never commit them.
Alive messages detect a dead connection; they do not make an interrupted transfer resumable.
Do not put
StrictHostKeyChecking noor a disposable known-hosts file into routine automation.
Copy one file with scp
scp -- ./report.csv archive-host:/srv/incoming/report.csvreport.csv 100% 18MB 42.0MB/s 00:00Both sides of the colon matter
The local
./report.csvis sent to the explicit remote path afterarchive-host:.--ends local option parsing, useful for filenames beginning with a hyphen.Quote paths containing spaces or shell metacharacters; remote path interpretation depends on protocol/version and should be tested.
A successful exit means the client completed its work, not that an application can read or semantically use the file.
Modern OpenSSH
scpuses SFTP by default;-Orequests the legacy SCP protocol only for compatibility and reintroduces its quirks.
Copy a file back from remote to local
scp archive-host:/srv/exports/report.csv ./downloads/report.csv
stat ./downloads/report.csvreport.csv 100% 18MB 38.1MB/s 00:00
File: ./downloads/report.csv
Size: ...Direction is determined by source and destination
The first operand is remote because it contains a recognized host prefix and colon; the second is local.
Create and permission the local parent directory before transfer.
A local filename containing a colon can be ambiguous; prefix it with
./or use an absolute path.statconfirms a local object exists but does not establish content identity; use checksums or application validation when required.
Copy a directory recursively with scp
scp -r -- ./project archive-host:/srv/incoming/... files transferred ...Recursive scp is convenient, not a synchronizer
-rdescends into the directory and copies it under the destination.An interrupted recursive scp generally lacks rsync’s efficient resume/change-selection workflow.
It does not delete obsolete destination files or provide a reliable dry-run mirror plan.
For repeatable deployments or large trees, use rsync and stage releases atomically rather than copying into a live application directory.
Use rsync for repeatable directory transfers
rsync -a --info=progress2 --human-readable ./project/ archive-host:/srv/incoming/project/sending incremental file list
...
1.24G 100% ...
sent ... received ... total size ... speedup ...Archive mode is broad, but not everything
-aexpands to recursive copying plus preservation of links, permissions, times, group, owner, and devices/specials where privileges allow.Archive mode does not by itself include ACLs (
-A), extended attributes (-X), hard-link relationships (-H), access times, or every platform-specific attribute.The source trailing slash means “copy the contents of
project”; without it, rsync normally creates aprojectlevel beneath the destination.Both endpoints need suitable rsync versions when using remote-shell mode.
Transport is SSH for this host:path syntax unless configured otherwise; no standalone rsync daemon is required.
Preview changes before a consequential sync
rsync -a --dry-run --itemize-changes ./project/ archive-host:/srv/incoming/project/>f+++++++++ assets/new-logo.svg
>f.st...... index.html
cd+++++++++ docs/A dry run is a plan based on current state
--dry-runperforms selection without transferring file data;--itemize-changesexplains proposed updates compactly.State can change between preview and execution, so production automation should control writers or use snapshots/releases.
Review source/destination spelling and the trailing slash before trusting the itemized list.
Permissions, exclusions, mount boundaries, symlink rules, and remote-shell expansion can change the transfer set.
Resume an interrupted large transfer
rsync -a --partial --partial-dir=.rsync-partial --info=progress2 ./dataset/ archive-host:/srv/archive/dataset/... transfer progress ...Keep partial data separate from final names
--partialretains interrupted work;--partial-dirstores it in a separate destination-side directory for reuse.Rerunning the same command lets rsync evaluate and continue efficiently where its algorithm/version/file state permits.
Ensure the partial directory is excluded from application consumption, backup recursion, publication, and untrusted access.
For files being actively appended or modified, snapshot/stop writers rather than assuming resume flags create consistency.
-Pis shorthand for--partial --progress, but an explicit partial directory is often operationally clearer.
Compression and bandwidth limits
rsync -a --compress --bwlimit=20M ./logs/ archive-host:/srv/archive/logs/... transfer limited near the configured rate ...Compression can save bytes or waste CPU
--compressreduces compressible data in transit but often adds little for JPEG, video, ZIP, encrypted, or already compressed files.--bwlimitlimits rsync socket I/O approximately according to its documented units/averaging; validate on the installed version.SSH may also compress if configured; avoid redundant compression decisions.
Measure CPU, elapsed time, network contention, and destination disk performance on representative data.
Mirroring with delete is destructive
rsync -a --delete --dry-run --itemize-changes ./site/ archive-host:/srv/mirror/site/
# Run only after reviewing the complete dry-run output:
rsync -a --delete --itemize-changes ./site/ archive-host:/srv/mirror/site/*deleting obsolete.html
...Risk level: destructive. Review the command before running it.
Delete makes the destination resemble the source
--deleteremoves destination entries absent from the transfer set; a reversed path or empty/wrong source can destroy data.The dry run must use the same include/exclude, mount, symlink, permission, and delete options as the real operation.
Take and test a recoverable backup or snapshot, and confirm the exact destination through a guard.
Review rsync’s delete timing and excluded-file rules if using related options.
Do not mirror directly into a live release when a staged directory plus atomic switch provides safer rollback.
Transfer selected files and exclusions
rsync -a --dry-run --itemize-changes \
--exclude=.git/ \
--exclude=.env \
--exclude='*.tmp' \
./ archive-host:/srv/incoming/project/... proposed files, excluding matched paths ...Quote patterns so the local shell does not consume them
The quoted
*.tmpreaches rsync as a filter instead of expanding against only the current directory.Filter rules are evaluated relative to the transfer root with documented include/exclude semantics.
Excluding
.envreduces one secret-copy risk but is not a substitute for inventorying credentials and generated artifacts.Add the identical filters to preview and execution; store reviewed filter files for complex policies.
Remember that a pattern containing internal
*characters is ordinary rsync syntax, not formatting markup.
Verify content after transfer
sha256sum ./release.tar.zst
ssh archive-host 'sha256sum /srv/incoming/release.tar.zst'91b... ./release.tar.zst
91b... /srv/incoming/release.tar.zstMatching digests answer a narrow but valuable question
The same SHA-256 digest strongly indicates the two regular-file byte streams match.
Use an authenticated channel and trusted remote execution; a compromised endpoint can lie about files and hashes.
A digest does not verify filenames, permissions, owners, ACLs, xattrs, link structure, database consistency, or application semantics.
Rsync already verifies reconstruction of transferred files internally;
--checksumchanges pre-transfer change detection and can add heavy disk I/O.For releases/backups, validate manifest signatures, extraction, startup/read tests, counts, metadata, and restoration as appropriate.
Stream a directory with tar over SSH
tar -C ./source -cf - . | ssh archive-host 'mkdir -p /srv/incoming/tree && tar -C /srv/incoming/tree -xf -'No output on success; both pipeline stages should exit successfully.Risk level: caution. Review the command before running it.
A stream avoids a temporary archive but raises the stakes
The local tar writes an archive to stdout; SSH carries it; remote tar extracts from stdin.
-Cand relative member names avoid embedding an absolute source path.Archive extraction can overwrite files and materialize symlinks/special entries; never extract an untrusted stream into a sensitive destination.
A plain shell pipeline can hide an earlier command failure unless the invoking shell uses/inspects pipeline status correctly.
GNU tar cannot use its ordinary post-write archive verification on a non-seekable pipe; perform independent destination checks.
SFTP for interactive and application workflows
sftp archive-hostConnected to archive-host.
sftp> pwd
sftp> lpwd
sftp> put report.csv /srv/incoming/report.csv
sftp> get /srv/exports/result.csv ./result.csv
sftp> byeAlways distinguish local and remote working directories
pwdand remote path commands refer to the server;lpwdandl-prefixed commands refer to local state.SFTP uses SSH authentication/encryption but exposes file operations rather than an interactive remote shell.
Batch mode is useful for automation only with explicit error handling, host verification, logging, and idempotency.
Server policy may allow SFTP while denying shell commands, which prevents rsync/tar-over-SSH even though SFTP works.
Metadata and filesystem boundaries
Ordinary users cannot recreate arbitrary owners, device nodes, capabilities, or security labels.
ACLs and xattrs need explicit rsync options plus support/permissions on both filesystems and rsync builds.
Hard links require preservation logic; otherwise linked names may become independent copies.
Sparse files can expand dramatically unless the chosen tool/options/filesystem preserve holes.
Symlink following versus preservation changes both data copied and escape risk; audit links that point outside the source tree.
Do not cross mounted filesystems accidentally; decide whether mount points, bind mounts, proc/sys/dev, containers, and network filesystems belong.
Filenames can contain spaces, newlines, leading hyphens, glob characters, and non-UTF-8 bytes; prefer null-safe manifests/tool-native selection over shell loops.
Live data needs application consistency
Filesystem-level copying does not create a consistent database backup while transactions continue.
Use database-native backup/snapshot procedures and test restoration.
For VMs, containers, mail stores, repositories, and object indexes, follow application quiesce/snapshot/export guidance.
Rsync’s second pass can narrow changes but does not create an atomic multi-file point in time.
Stage received data, validate it, then publish through an atomic rename/symlink/release mechanism when possible.
Troubleshooting map
Permission denied (publickey): inspect selected identity, agent, username, server authorization, permissions, and verbose SSH output.
Host key changed: stop and verify whether the server was rebuilt, DNS changed, or a man-in-the-middle attack is possible; never delete the warning blindly.
Connection timed out/refused: verify route, firewall/security group, hostname, port, SSH service, and bastion/VPN requirements.
No space left: check bytes, inodes, quotas, snapshots, and the actual destination filesystem.
Extra directory level: compare rsync source paths with and without the trailing slash.
Files recopied every run: compare timestamps/resolution, clock, size, metadata, generated files, filesystem behavior, and rsync versions.
Remote rsync not found: install an approved compatible package or use SFTP/scp/tar with understood limitations.
Transfer completed but app fails: validate ownership, mode, ACL/xattr, SELinux/AppArmor context, links, completeness, application format, and atomic publication.
Production transfer checklist
Exact source, destination, direction, trailing-slash meaning, and remote account are reviewed.
Host key is verified; credentials are scoped, protected, and not exposed in arguments/logs.
Free space, inodes, quotas, mounts, permissions, and tool versions are checked.
The data is quiesced/snapshotted or application-exported for consistency.
Metadata requirements determine scp/rsync/SFTP/tar options.
Destructive delete/overwrite operations have an equivalent dry run and recoverable backup.
Interrupted transfer behavior and retry/idempotency are understood.
Counts, hashes/manifests, metadata, and application/restoration checks prove the result.
Logs are retained without secrets, and temporary/partial data is cleaned through a safe policy.
Primary references
The OpenBSD/OpenSSH `scp(1)` manual documents modern SFTP transport, legacy compatibility, options, paths, and exit behavior.
The upstream `rsync(1)` manual defines archive mode, trailing slashes, filters, partial files, delete behavior, checksums, and metadata limits.
The GNU tar manual documents archive creation/extraction, member selection, absolute paths, comparison, and why streamed archives need separate verification.
Use the installed
ssh(1),ssh_config(5),sftp(1),rsync(1), andtar(1)manuals because distribution versions and supported algorithms/options differ.
Comments and corrections