Transfer Files with SFTP, SCP, and rsync over SSH
Choose the transfer tool based on the job and the trust boundary. SFTP provides an interactive file protocol over SSH. Modern OpenSSH scp uses the SFTP protocol by default, while rsync uses SSH as a transport for its own delta-transfer protocol. All three still depend on host-key verification, account authorization, file permissions, and a private management path. None requires publishing SSH to the internet.
Prepare a narrow transfer account and destination
Decide whether the account needs an interactive shell, a directory tree, or only a forced transfer operation. A deployment account should not automatically be a login shell or a root account. Apply an authorized_keys source restriction and, where appropriate, restrict, command=, and no-pty/no-agent-forwarding/no-port-forwarding options. Use a separate article’s user and sudo guidance for ownership and revocation.
On the client, use a reviewed alias that selects one account and one key:
Host files-stage
HostName 192.168.60.10
User transfer
IdentityFile ~/.ssh/id_ed25519_transfer
IdentitiesOnly yes
ForwardAgent no
Check the effective destination before every new integration:
ssh -G files-stage | grep -E '^(hostname|user|port|identityfile|identitiesonly|forwardagent) '
Use SFTP for an explicit file session
# Start an SFTP session over the approved alias.
sftp files-stage
# Inside SFTP, inspect and transfer deliberately.
sftp> pwd
sftp> lpwd
sftp> ls -la
sftp> put ./release.tar.gz /srv/staging/release.tar.gz
sftp> get /srv/staging/checksum.txt ./checksum.txt
sftp> bye
SFTP operations are server-controlled: the account may be chrooted, restricted, or limited to a particular subsystem. Prefer an explicit destination path and inspect the remote working directory before uploading. Use SFTP’s resume support only after understanding whether a partial destination is safe; for important releases, upload to a temporary name, verify a checksum, then have an authorized deployment step perform the rename.
Do not use an SFTP session to browse directories that the account does not need. A filesystem permission failure is a policy signal, not a reason to make the destination world-writable.
Understand modern scp behavior
In OpenSSH 9.0 and later, scp uses the SFTP protocol by default. This improves handling of remote path names and avoids the historical remote-shell protocol’s parsing hazards. Older servers or unusual appliances may require the legacy protocol for compatibility; scp -O explicitly requests that protocol and should be used only for a reviewed, trusted exception.
# Copy one file to an explicit staging path.
scp -o IdentitiesOnly=yes ./release.tar.gz files-stage:/srv/staging/release.tar.gz
# Copy a directory tree when the destination policy permits it.
scp -r -o IdentitiesOnly=yes ./public/ files-stage:/srv/staging/public/
Quote paths that contain shell metacharacters and verify the local and remote side of every command. Never use -O as a generic fix for a failed transfer; first identify whether the server’s SFTP subsystem or path policy is the issue. Do not use -3 or a third-party relay casually: a client-side third-party copy can require both authorizations and can move data through a host you did not intend.
Use rsync for repeatable trees and partial transfers
rsync compares metadata and file contents, transferring only changed blocks where possible. Over SSH it starts a remote rsync process, so the remote account must have the approved rsync executable and a shell or forced command that permits this protocol. It is not SFTP and will not work with an account limited to the SFTP subsystem alone.
# Dry-run the direction and exclusions first.
rsync -aHn --itemize-changes \
--exclude='.git/' --exclude='*.tmp' \
./public/ files-stage:/srv/staging/public/
# Transfer with progress and resumable partial files.
rsync -aH --info=progress2 --partial --partial-dir=.rsync-partial \
--exclude='.git/' --exclude='*.tmp' \
./public/ files-stage:/srv/staging/public/
The trailing slash on ./public/ means “contents of public”; without it, rsync may create a nested public directory. Review the dry run, destination ownership, excludes, and available space before transferring. --partial retains an interrupted destination; --partial-dir places incomplete files in a separate directory so readers are less likely to consume them. Secure that directory and remove it through the approved maintenance process after a successful verified transfer.
For automation, use --protect-args when supported, fixed source and destination paths, a forced command or dedicated account, and an explicit exit-status check. Do not add --delete to a production command unless a reviewed mirror policy, backup, dry run, and recovery test exist; this article intentionally does not provide a destructive mirror command.
Verify integrity and permissions
# On the sender, calculate a published checksum for the staged file.
sha256sum ./release.tar.gz
# On the receiver, compare the checksum through the approved session.
ssh files-stage 'sha256sum /srv/staging/release.tar.gz'
# Review ownership and modes without changing them.
ssh files-stage 'stat -c "%U %G %a %n" /srv/staging/release.tar.gz'
Compare values through a trusted channel and ensure the file is not being modified while you measure it. A checksum confirms bytes, not provenance, authorization, malware safety, or correct deployment. Keep secrets, private keys, backup archives, and configuration databases out of a broadly readable staging directory.
Diagnose without weakening security
- Use
ssh -vvor rsync’s-e 'ssh -vv'temporarily to distinguish routing, host-key, authentication, subsystem, and permission errors; redact paths and usernames from shared logs. - Check
ssh -G files-stage, server authentication logs, SFTP subsystem configuration, and the account’s filesystem permissions. - Do not turn off host-key checking, use a root account, add world-writable permissions, or expose port 22 publicly to fix a transfer.
- For a failed partial transfer, inspect the partial directory and destination before retrying. Resume only when the source and destination are known to be the same intended object.
Continue with Use Local, Remote, and Dynamic SSH Port Forwarding to understand tunnels separately from file transport.
Sequence navigation
Previous: Manage SSH Users, authorized_keys, and sudo · Next: Use Local, Remote, and Dynamic SSH Port Forwarding
dispelled