The sftp Command-Line Client, Mastered
If you learn exactly one command-line transfer tool deeply, make it sftp. It ships wherever OpenSSH does — every Linux distribution, every BSD, macOS, and current Windows builds too. That means it is already installed on nearly every machine you will ever script against. It encrypts everything, authenticates with SSH keys, runs happily unattended, and, unlike the ancient ftp clients it replaces, tells the truth about whether your transfer worked.
Most administrators use a tenth of it. They know get and put, fumble through the rest interactively, and never touch the batch mode that turns sftp into a real automation client. This article closes that gap. It starts with interactive fluency, then the flags that matter, then key and host-key handling for jobs with no human present. Finally, it covers batch mode with the exit-code discipline that makes scripts trustworthy. It is part of our command-line client mastery series. It pairs with batch modes, heredocs, and command files, which goes deeper on unattended patterns.
sftp Rides on SSH — and That Is Its Superpower
First, a mental model that explains almost everything about how sftp behaves. SFTP is not FTP with encryption added. It is a file-transfer protocol that runs inside an SSH connection, as a subsystem of the same secure channel you use for remote shells. The sftp program is a thin front end: it asks the ssh client to open the connection, then speaks the file-transfer protocol through it. The full protocol story is in how SFTP works.
The practical consequence: everything you have configured for ssh works for sftp automatically. Your SSH keys authenticate it. Your ~/.ssh/known_hosts file verifies the servers it talks to. Your ~/.ssh/config aliases, ports, usernames, and identity files all apply. Connect to a host you have an alias for and sftp inherits every setting. We explore that trick in ssh config for transfers. One firewall port, port 22, carries the whole session, so there is no active/passive data-connection drama at all.
It also means sftp's security posture is SSH's security posture: single well-understood port, strong encryption, and key-based login that removes passwords from the picture entirely. When this article talks about credentials, it will push you toward keys every time.
Interactive Fluency in One Session
Connect with any of these forms:
sftp deploy@sftp.example.com # simplest form sftp -P 8022 deploy@sftp.example.com # nonstandard port (capital P) sftp deploy@sftp.example.com:/outgoing # start in a remote directory sftp deploy@sftp.example.com:/outgoing/report.csv . # fetch one file and exit
That last form deserves a highlight. Give sftp a remote file path and a local destination and it downloads the file and exits. That is a complete non-interactive transfer with no session at all, ideal for quick pulls in scripts.
Inside a session, the prompt changes to sftp> and you type commands. A realistic exchange:
$ sftp -i ~/.ssh/transfer_key deploy@sftp.example.com Connected to sftp.example.com. sftp> pwd Remote working directory: /home/deploy sftp> ls -lt -rw-r--r-- 1 deploy deploy 4523 Mar 14 02:10 report.csv drwxr-xr-x 2 deploy deploy 512 Mar 12 23:41 archive sftp> lcd /data/inbox sftp> get report.csv Fetching /home/deploy/report.csv to report.csv sftp> put results.csv archive/results.csv Uploading results.csv to /home/deploy/archive/results.csv sftp> bye
Wildcards work where you expect them: get *.csv downloads every matching file in the remote directory, and put report_*.txt uploads a matching local set. The patterns are expanded by sftp itself, so quote them if your shell would otherwise grab them first. Unlike the old ftp clients, there is no per-file confirmation prompt to switch off — multi-file transfers just run. That is one less thing to remember when a script grows out of an interactive session.
Two small habits make interactive work faster. First, help (or ?) lists every command with a one-line summary, so you never need to leave the session to check syntax. Second, the ! escape runs a local shell command in place. !sha256sum report.csv verifies a download's checksum without dropping the connection. !ls -l /data/inbox confirms where a file actually landed.
The command set is small enough to actually memorize. The key insight for beginners: most commands come in a remote version and a local version prefixed with l. ls lists the remote directory, and lls lists the local one. cd and lcd, pwd and lpwd, mkdir and lmkdir pair up the same way. You are always standing in two directories at once, one on each machine, and transfers happen between them.
| Command | What it does | Worth knowing |
|---|---|---|
get remote [local] |
Download a file | Accepts wildcards; get -R dir copies a whole tree; get -a resumes a partial download |
put local [remote] |
Upload a file | Same options as get; reput resumes an interrupted upload |
ls / lls |
List remote / local files | ls -lt sorts newest first — handy for "grab the latest" checks |
cd / lcd |
Change remote / local directory | Set both once at the top of a script; relative paths follow |
rename old new |
Rename a remote file | The key to atomic publishing; some servers refuse to overwrite an existing target |
rm / mkdir / rmdir / chmod |
Remote housekeeping | What your account may do is set by the server's permissions |
df -h |
Remote free space | Needs server support; not every server answers |
!command |
Run a local shell command | Check a checksum or unpack an archive without leaving the session |
For the full semantics of the operations themselves — what rename can promise, how permissions map — see SFTP file operations.
The Flags That Matter
sftp accepts many options; six do almost all the work in practice:
-b batchfile— batch mode: read commands from a file instead of a keyboard. The heart of automation, covered fully below.-b -reads commands from standard input, which lets a script pipe commands in without a separate file.-i identityfile— use this private key for authentication instead of the defaults. Unattended jobs should always name their key explicitly.-P port— connect to a nonstandard port. Capital P, unlike ssh's lowercase-p— a trap everyone falls into exactly once.-o option— pass any ssh configuration option by name. The three that matter for jobs:-o BatchMode=yes(never prompt — fail instead),-o ConnectTimeout=15(give up on a dead host in seconds, not minutes), and the host-key options discussed below. Repeat-ofor each option.-p(lowercase) — preserve modification times and permissions on transferred files. Without it, every file arrives timestamped "now," which confuses newest-file logic downstream.-r— recursive transfers of whole directories from the command line.
Two more are worth a nod. -q quiets the progress meter and diagnostic chatter, which keeps job logs readable. -v does the opposite. It raises the underlying ssh logging so you can watch authentication and connection setup in detail. When a job misbehaves, rerunning it once with -v usually names the culprit in the first screen of output.
A fully specified unattended invocation, the shape you will see again in the batch section:
sftp -b push_daily.sftp \
-i /etc/transfer/keys/transfer_key \
-o BatchMode=yes \
-o ConnectTimeout=15 \
deploy@sftp.partner.example
Key Authentication: The Unattended Login
A scheduled job cannot type a password, and embedding one in a script trades one problem for a worse one. The SSH answer is key authentication: a private key file on the client, a matching public key registered on the server, and no secret ever typed or transmitted. Generate a dedicated key for the job — not your personal key. Use no passphrase, or a passphrase supplied by an agent if your environment provides one:
ssh-keygen -t ed25519 -f /etc/transfer/keys/transfer_key -C "nightly push job" chmod 600 /etc/transfer/keys/transfer_key
The public half (transfer_key.pub) goes to the server administrator, who adds it to the account's authorized keys. From then on, -i /etc/transfer/keys/transfer_key logs in silently. Two refinements keep it robust. Protect the private key file so only the job's service account can read it. Add -o IdentitiesOnly=yes so sftp offers exactly the key you named rather than everything an agent happens to hold. Some servers disconnect clients that try too many keys. Key generation, storage, and rotation discipline are covered in generating and storing SSH keys, and the server-side view in SFTP authentication.
Host Keys Without Prompts
The first time you connect to any SSH server, you get the famous question: The authenticity of host ... can't be established. Are you sure you want to continue connecting? That prompt is host-key verification — the client asking you to vouch for the server's identity fingerprint before it will trust the connection. Interactively you answer once and the fingerprint lands in ~/.ssh/known_hosts. An unattended job, though, cannot answer — with BatchMode=yes it will simply fail. A job that fails at its very first production run at 2 a.m. is a rite of passage this paragraph exists to spare you.
The clean solution is to settle the host key before the job ever runs, in one of two ways:
- Connect interactively once from the account the job will run as, verify the fingerprint against what the server's administrator tells you out-of-band, and accept it. The entry is now in that account's
known_hosts. - Pre-load the key with
ssh-keyscan sftp.partner.example >> ~/.ssh/known_hosts— convenient for fleets, but understand what it does: it trusts whatever answered the scan. Verify the fetched fingerprint against an out-of-band source before you bless it.
Then leave StrictHostKeyChecking at yes for the job, so a changed or unknown key is a hard failure. That is what you want. A surprise host-key change is either a server rebuild nobody mentioned or an interception attempt. An unattended job should refuse to proceed in both cases. What you should never do is disable checking to make the prompt go away — that quietly removes the protection SFTP's encryption depends on. The full reasoning lives in host keys and known_hosts.
Remember: the three-line incantation for every unattended sftp job is -o BatchMode=yes, -o ConnectTimeout=15, and a pre-populated known_hosts with strict checking left on. Prompts become failures, failures become exit codes, and exit codes are something a scheduler can see.
Batch Mode Done Right
Batch mode is where sftp becomes an automation client. Put the session's commands in a file, one per line. Blank lines and # comments are allowed. Hand the file to -b:
# push_daily.sftp — upload, then publish atomically cd /incoming put /data/out/daily_export.csv daily_export.csv.part rename daily_export.csv.part daily_export.csv -mkdir archive bye
Three behaviors define batch mode, and all three exist to make failures loud:
- It aborts at the first failed command. If the
putfails, therenamenever runs — no half-finished choreography. The commands that abort on failure includeget,put,rename,rm,mkdir,cd, andls. - A leading
-makes one command tolerant.-mkdir archiveabove succeeds the first night and "fails" harmlessly every night after, without aborting the run. Use it for genuinely optional steps only — a tolerated failure is invisible, which is exactly what you do not want on the steps that matter. - Commands are echoed as they execute. The output interleaves each command with its result, which turns a captured log into a readable narrative of the run.
Notice the .part-then-rename pattern in the example. Upload under a temporary name, then rename to the final name only when the bytes are complete. That way, a consumer on the far side can never read a half-written file. That pattern is worth adopting everywhere; the why is in our partial-file safety series.
And the payoff for all this discipline: the exit code is honest. sftp exits 0 only when every command succeeded. A failed batch command exits 1; connection-level failures — unreachable host, refused key, rejected host key — typically surface as 255, the SSH convention. In scripts, treat any nonzero status as failure and you will never be lied to. The classic ftp clients never extended that courtesy (see the built-in ftp clients for that story).
A Production-Shaped Job
Here is the whole thing assembled: a wrapper script a scheduler can run, with logging and an exit code it can act on.
#!/bin/sh
# push_daily.sh — runs from the scheduler; nonzero exit means failure
LOG=/var/log/transfers/push_daily.log
sftp -b /etc/transfer/push_daily.sftp \
-i /etc/transfer/keys/transfer_key \
-o BatchMode=yes -o ConnectTimeout=15 \
deploy@sftp.partner.example >> "$LOG" 2>&1
rc=$?
if [ "$rc" -ne 0 ]; then
echo "push_daily FAILED exit=$rc" >> "$LOG"
exit "$rc"
fi
echo "push_daily OK" >> "$LOG"
One more discipline before the schedule goes live: test the job as the account that will run it, not as yourself. SSH state is per-account. The service account has its own ~/.ssh directory, which means its own known_hosts and its own key permissions. A job that works perfectly from your login can still fail from the scheduler because the service account never accepted the host key or cannot read the identity file. On Unix, sudo -u svc-transfer sh /etc/transfer/push_daily.sh rehearses the real conditions in one line. On Windows, run the task once manually from Task Scheduler under its configured account and read the log it leaves behind.
Every piece earns its place. The log captures the echoed batch commands and server responses (2>&1 folds error output in). The exit code is checked and propagated. The scheduler — cron or Task Scheduler — can alert on a nonzero result. What this skeleton still lacks is retry logic for transient network blips and alerting a human actually sees. Those belong one layer up, in the patterns covered by our bash and cron automation and retry and error handling series.
What sftp Will Not Do for You
Mastery includes knowing the edges. The sftp client deliberately stays simple, and four gaps matter for real jobs:
- No retries. A dropped connection is a failed run; sftp will not try again on its own. Your wrapper or scheduler owns retry policy.
- No mirroring.
get -Rcopies an entire tree every time — there is no "only what changed." For true synchronization you want lftp's mirror or rsync where both ends allow it. - No parallelism. One file at a time, one connection. Usually fine; occasionally the reason a nightly window gets tight.
- Fresh timestamps unless you ask. Remember
-pif downstream logic depends on modification times.
There is also a graduation point to be honest about. When a flow accumulates schedules, retries, notifications, folder watching, and encryption steps, the wrapper script around sftp grows into a small application you now maintain. That is the moment purpose-built tooling pays for itself. Sysax FTP Automation builds scheduled SFTP, FTPS, and FTP transfer tasks through a wizard. It watches folders for arriving files and sends email notifications when a run fails. That is the operational shell this article's shell script only sketches. And for practicing everything here safely, a trial install of Sysax Multi Server gives you an SFTP server of your own on Windows. The trial is free from the download page. That way, your first batch-mode experiments happen against a machine where mistakes cost nothing.
Where to Go Next
You now have the full arc. sftp inherits SSH's transport and trust model. A small command set moves files in both directions, and six flags cover automation. Keys and pre-loaded host keys remove every prompt. Batch mode plus honest exit codes make jobs a scheduler can supervise. That is 90 percent of scripted transfer work on SSH-based servers.
For the remaining 10 percent, keep reading the series. batch modes, heredocs, and command files deepens the unattended patterns and their quoting traps. lftp adds mirroring and automatic retries when sftp's simplicity runs out. curl for file transfers covers the one-shot alternative. If you are choosing between sftp and its cousins for a given job, scp vs sftp vs rsync settles it.
Frequently Asked Questions
What is the difference between sftp -P and -p?
-P sets the port to connect to; lowercase -p preserves file timestamps and permissions on transfer. sftp differs from ssh here (ssh uses lowercase -p for port), which is why the mix-up is so common.How do I run sftp in a script without it asking for a password?
-i. Add -o BatchMode=yes so that if authentication ever breaks, the job fails immediately instead of hanging at a prompt.What exit code does sftp return when a batch command fails?
-b batch run exits 1, and connection-level problems typically exit 255, following SSH convention. Scripts should simply treat any nonzero exit as failure.Can sftp resume an interrupted transfer?
reget (or get -a) resumes a partial download, and reput resumes an upload. Resume continues from the current partial size, so pair it with a size or checksum verification once the transfer completes.Is the sftp client the same thing as FTPS?
Why did my unattended sftp job fail the first time it ran in production?
From the Sysax team: we build secure file transfer software for Windows. Sysax Multi Server is an FTP, FTPS, SFTP, and HTTPS server. Sysax FTP Automation handles scheduled, scripted transfers. Free trials are on the download page.
