SFTP has about forty commands and you will use eight of them: ls, cd, pwd, get, put, mkdir, rm, and bye. The one thing that trips up everyone new to it is that half the commands have a local twin prefixed with l — lls, lcd, lpwd — because you are driving two filesystems from one prompt.
SFTP is file transfer over SSH: same port, same keys, same host verification, encrypted throughout. It is not FTPS and it is not FTP with a certificate. If you can SSH to a machine, you can almost certainly SFTP to it with no extra server configuration.
Table of contents
- Connecting
- The local-versus-remote split
- Transferring files
- Housekeeping on the remote side
- Batch mode, for scripts and cron
- When to use something else
- How this fits the rest of the stack
- FAQ
Connecting
# Basic connection
sftp user@host
# Non-standard port -- capital P, unlike ssh's lowercase p
sftp -P 2222 user@host
# With a specific key
sftp -i ~/.ssh/deploy_key user@host
# Land directly in a directory
sftp user@host:/var/www/uploads
# Through a jump host
sftp -J bastion.example.com user@internal-host
-P for the port, not -p. In sftp, lowercase -p means preserve modification times, which is the opposite of what you meant and fails confusingly. scp shares this quirk; ssh does not.
SFTP reads ~/.ssh/config, so hosts, keys, ports, and jump configuration you have already set up for SSH apply without repeating them.
The local-versus-remote split
This is the concept that makes the rest obvious. Every command acts on the remote machine unless it starts with l, in which case it acts on your local one.
pwdremote working directory —lpwdlocal working directorylslist remote —llslist localcdchange remote directory —lcdchange local directorymkdircreate remote directory —lmkdircreate local directory
The practical consequence: get file.txt drops the file into whatever lpwd reports, which is wherever you launched sftp from. If downloads keep landing in your home directory, that is why.
sftp> lcd ~/Downloads # set the local destination first
sftp> cd /var/www/backups # then navigate the remote
sftp> lpwd
Local working directory: /home/you/Downloads
sftp> get dump.sql.gz # lands in ~/Downloads
Transferring files
# Download
get remote.txt # to local cwd, same name
get remote.txt local-name.txt # rename on arrival
get -r logs/ # recursive directory download
get *.log # globs work
get -a bigfile.iso # resume a partial transfer
# Upload
put local.txt
put local.txt /var/www/remote.txt
put -r dist/
put -P file.txt # preserve permissions and timestamps
# Resume an interrupted transfer either way
reget bigfile.iso
reput bigfile.iso
-r does not follow symlinks. SFTP copies the link itself, not the target. If your build output is full of symlinks, the transferred tree will not be what you expected — that is one of the cases where rsync is the right tool instead.
reget and reput are get -a and put -a under different names, and they are what you want on any transfer large enough to be interrupted. Starting a 4GB download from zero for the second time is avoidable.
Housekeeping on the remote side
mkdir releases/2026-08
rmdir old-release # must be empty
rm stale.log
rename current.txt previous.txt
ln -s releases/2026-08 current # symlink
chmod 755 deploy.sh
chown 1000 app.log # numeric UID only
chgrp 1000 app.log # numeric GID only
df -h # remote disk usage
Two things surprise people. chown and chgrp take numeric IDs, not names — the SFTP protocol has no way to resolve usernames on the remote system. And rm has no recursive flag; deleting a populated directory means clearing it first or opening an SSH session, which is a deliberate safety property rather than an oversight.
df only works if the server implements the [email protected] extension, which OpenSSH does. Against a non-OpenSSH SFTP server it may simply fail.
You can also escape to a local shell without leaving the session:
sftp> !ls -la # run ls locally
sftp> !gzip dump.sql # compress locally before uploading
sftp> ! # drop to a local shell; exit to return
Batch mode, for scripts and cron
Interactive SFTP does not belong in automation. Batch mode reads commands from a file and exits non-zero on failure, which is what a script needs.
cat > /tmp/upload.sftp <<'EOF'
cd /var/www/releases
put -r dist/
rename dist current
bye
EOF
# -b batch file, -o BatchMode=yes disables any password prompt
sftp -b /tmp/upload.sftp -o BatchMode=yes deploy@host
echo "exit: $?"
Batch mode aborts on the first failing command, which is usually what you want. To let a specific command fail without stopping the run, prefix it with -; to suppress echoing it, prefix with @.
-rm possibly-missing.txt # continue even if this fails
@put secret.env # do not echo this line
-@rm optional.txt # both
Batch mode requires key-based authentication. There is no interactive prompt, so a password-only account will fail immediately. That is a feature — automated transfers should be using keys.
When to use something else
SFTP is a good general-purpose tool and a poor synchronisation tool. Knowing where the line is saves time.
- Syncing a directory repeatedly →
rsync. It transfers only differences, handles symlinks properly, and can delete removed files.rsync -avz --delete dist/ user@host:/var/www/beats any SFTP equivalent. - One file, one time, in a script →
scp. Fewer moving parts when you do not need a session. - Deploying an application → neither. Pushing files over SFTP means no build log, no version history, and no rollback. Deploy from a repository instead.
- Mounting the remote filesystem →
sshfs. Works over the same SFTP subsystem and lets local tools operate on remote files directly.
That third point is the one worth pressing. SFTP deployment is still common on WordPress and PHP hosting, and it is the reason so many production sites have no reliable record of what is running on them. A file uploaded by hand exists in exactly one place with no history behind it.
The alternative is a repository as the source of truth, with the platform building and serving from it — a build log, a live route, and a previous deploy to roll back to. RunxBuild’s managed WordPress also exposes a dashboard file manager for the cases where you genuinely need to touch one file, which removes most of the day-to-day reasons to open an SFTP session at all.
How this fits the rest of the stack
Learn the eight core commands, internalise the l prefix for local operations, and use batch mode with keys for anything automated. Reach for rsync when you are syncing rather than transferring, and for a repository when you are deploying rather than copying. If you are moving off hand-uploaded files and want to see what a build-and-deploy setup costs by line item, the RunxBuild hosting calculator breaks it down.
Useful related references:
- Redis Pipeline: How to Batch Commands, When to Use It, and Why It Is Not the Same as a Transaction
- Redis Commands: The 25 You Actually Use, Grouped by What They Touch
- Check IP Address on Ubuntu: The 6 Commands That Always Work
- Services on RunxBuild
FAQ
What is the difference between SFTP and FTPS?
SFTP is a subsystem of SSH, using one encrypted connection on port 22 with SSH keys and host verification. FTPS is the old FTP protocol wrapped in TLS, using separate control and data connections and X.509 certificates. They share nothing but a similar name.
How do I download a whole directory over SFTP?
Use get -r directory/. Note that SFTP does not follow symlinks during recursive transfers, so a tree containing links will not arrive intact — use rsync if that matters.
Why do my downloads go to the wrong folder?
get writes to your local working directory, which is wherever you started sftp. Run lpwd to see it and lcd to change it before transferring.
Can I use SFTP in a shell script?
Yes, with batch mode: sftp -b commands.txt -o BatchMode=yes user@host. It exits non-zero on failure and requires key-based authentication, since there is no interactive password prompt.
Why does chown not accept a username in SFTP?
The SFTP protocol has no mechanism for resolving usernames on the remote system, so chown and chgrp take numeric UIDs and GIDs only. Look the ID up over SSH first if you need it.