| Tested on | Red Hat Enterprise Linux 10.2 (client) RHEL 10.2 peer host 192.168.56.156 |
|---|---|
| Package | openssh-clients 9.9p1-25.el10_2sshpass 1.09-9.el10_0expect 5.45.4-25.el10 |
| Applies to | RHEL, Rocky Linux, AlmaLinux, Fedora, Ubuntu, Debian, and other Linux systems with OpenSSH |
| Privilege | Normal user for key-based SFTP; sudo to install sshpass or expect |
| Scope | Automate SFTP uploads and downloads in shell scripts with batch mode, SSH keys, sshpass, or expect. Does not cover SFTP server setup or chroot jails. |
| Related guides | SSH key authentication test SSH connection SFTP chroot jail SSH command use SFTP with IPv6 |
You automate SFTP in a shell script by feeding cd, put, and get commands through batch mode (sftp -b). The authentication layer is separate: SSH keys let the script run unattended, while password logins need sshpass or expect because OpenSSH refuses to read passwords from a pipe.
Quick reference: pick an automation method
| Method | When to use | Needs | Jump to |
|---|---|---|---|
sftp -b + SSH keys |
Cron jobs, CI uploads, production transfers | Key pair, BatchMode=yes |
Method 1 |
sshpass + sftp -b |
Legacy host that only allows passwords | sshpass package, BatchMode=no |
Method 2 |
expect + sftp |
Custom prompts or multi-step interactive flows | expect package |
Method 3 |
All three paths use the same SFTP batch commands. Only the login step changes.
How SFTP batch mode works
sftp -b batchfile reads SFTP commands from a file instead of your keyboard. Each line is one command (cd, put, get, ls, bye, and so on). OpenSSH runs them in order and exits when the batch ends.
Useful batch-mode details:
- Pass
-as the batch file name to read commands from standard input (a here document works). - Batch mode normally stops when an important command fails, including
put,get, andcd. Prefix a command with-to let that specific failure be ignored, for example-rm oldfile. - Without SSH keys, set
BatchMode=nososftpmay prompt for a password (or letsshpass/expectanswer it). - With keys, set
BatchMode=yesso a missing key fails immediately instead of hanging on a password prompt.
Lab layout used in this guide
The examples use two RHEL 10 hosts on a host-only network:
| Role | Host | Address |
|---|---|---|
| SFTP client (where the script runs) | vm1.lab.example |
local |
| SFTP server | vm2.lab.example |
192.168.56.156 |
Export the target once so every command block stays copy-friendly:
export SFTP_HOST=192.168.56.156
export SFTP_USER=rootFor the password-based examples later, use a normal user account on the server (not root) and set SFTP_USER to that name.
Method 1: SFTP batch file with SSH keys (recommended)
This is the pattern you want for cron and CI: keys handle authentication, batch mode handles file commands.
Test SSH keys before scripted SFTP
Before scripting SFTP, verify SSH login does not ask for a password:
ssh -o BatchMode=yes -o ConnectTimeout=5 "${SFTP_USER}@${SFTP_HOST}" 'hostname'Sample output:
vm2.lab.exampleA silent failure or Permission denied means keys are not configured yet. See SSH key authentication to copy a public key to the server's ~/.ssh/authorized_keys.
Create an SFTP batch file for uploads
Create a small source directory and files to upload:
LAB_SRC=~/sftp-lab-src
mkdir -p "${LAB_SRC}"
printf 'batch-%s\n' "$(date +%s)" > "${LAB_SRC}/upload1.txt"
printf 'batch-%s\n' "$(date +%s)" > "${LAB_SRC}/upload2.txt"On the server, ensure the remote directory exists:
ssh "${SFTP_USER}@${SFTP_HOST}" 'mkdir -p /tmp/sftp-lab'Write the SFTP batch commands to a temporary file:
cat > /tmp/sftp-batch.cmd <<'EOF'
cd /tmp/sftp-lab
put upload1.txt
put upload2.txt
ls -l
quit
EOFThe put lines use filenames only because you will run sftp from inside LAB_SRC.
Upload files with sftp -b batch mode
Run sftp from the source directory with batch mode and non-interactive SSH:
cd "${LAB_SRC}"
sftp -o BatchMode=yes -b /tmp/sftp-batch.cmd "${SFTP_USER}@${SFTP_HOST}"Sample output:
sftp> cd /tmp/sftp-lab
sftp> put upload1.txt
sftp> put upload2.txt
sftp> ls -l
-rw-r--r-- ? root root 22 Aug 16 11:37 upload1.txt
-rw-r--r-- ? root root 22 Aug 16 11:37 upload2.txt
sftp> quitConfirm the files landed on the server:
ssh "${SFTP_USER}@${SFTP_HOST}" 'ls -l /tmp/sftp-lab/'total 8
-rw-r--r--. 1 root root 22 Aug 16 11:37 upload1.txt
-rw-r--r--. 1 root root 22 Aug 16 11:37 upload2.txtAutomate SFTP with a here document in bash
You can skip the temporary batch file and pipe commands on standard input:
cd "${LAB_SRC}"
sftp -o BatchMode=yes -b - "${SFTP_USER}@${SFTP_HOST}" <<'EOF'
cd /tmp/sftp-lab
put upload1.txt
put upload2.txt
bye
EOFBoth styles are equivalent. Pick a file when a scheduler reuses fixed commands; pick a here document when the paths are generated inside the script.
Download files with SFTP get in batch mode
Batch mode works the same way for downloads. Create a local directory for incoming files, then pull a remote file into the current local directory:
mkdir -p ~/sftp-downloads
cd ~/sftp-downloads
sftp -o BatchMode=yes -b - "${SFTP_USER}@${SFTP_HOST}" <<'EOF'
cd /tmp/sftp-lab
get upload1.txt
bye
EOFThe get command saves upload1.txt into ~/sftp-downloads because that is your local working directory when sftp starts. For a fixed remote export path, the batch lines look the same:
sftp -o BatchMode=yes -b - "${SFTP_USER}@${SFTP_HOST}" <<'EOF'
cd /remote/export
get report.csv
bye
EOFCheck SFTP exit status in shell scripts
A wrapper script should treat a non-zero sftp exit code as a failed transfer:
if sftp -o BatchMode=yes -b /tmp/sftp-batch.cmd \
"${SFTP_USER}@${SFTP_HOST}"; then
echo "Transfer completed"
else
echo "Transfer failed" >&2
exit 1
fiWhen put, get, or cd fails, batch mode stops and sftp returns a non-zero status unless you prefixed that command with -. Use the prefix only when a missing remote file or directory should not abort the whole run.
Method 2: Automate SFTP with sshpass and a password
When the remote host only accepts password authentication, install sshpass and disable batch mode for SSH so the password handshake can complete.
Install sshpass
On RHEL 10, sshpass ships in AppStream:
sudo dnf install sshpassOn Ubuntu or Debian:
sudo apt update
sudo apt install sshpassRun SFTP with SSHPASS
Store the password in SSHPASS rather than passing -p on the command line. sshpass -e reads the password from the SSHPASS environment variable instead of placing it directly in the command line.
Create a test file to upload:
printf 'sshpass-test\n' > /tmp/sshpass-upload.txtRun batch SFTP with password authentication (BatchMode=no is required):
export SSHPASS='your_password_here'
export SFTP_USER=sftpuser
sshpass -e sftp -oBatchMode=no -o StrictHostKeyChecking=accept-new -b - "${SFTP_USER}@${SFTP_HOST}" <<'EOF'
cd upload
put /tmp/sshpass-upload.txt
ls -l
bye
EOFSample output:
sftp> cd upload
sftp> put /tmp/sshpass-upload.txt
Uploading /tmp/sshpass-upload.txt to /home/sftpuser/upload/sshpass-upload.txt
sshpass-upload.txt 100% 13 4.1KB/s 00:00
sftp> ls -l
-rw-r--r-- ? sftpuser sftpuser 13 Aug 16 11:38 sshpass-upload.txt
sftp> byeThe SFTP server must allow password-based authentication for the account. If it accepts only public-key authentication, sshpass or expect cannot supply a password instead.
Method 3: Automate SFTP with expect
expect watches for the password: prompt and sends a reply. It is more verbose than sshpass but handles uneven prompt text and mixed interactive steps.
Install expect
On RHEL:
sudo dnf install expectOn Ubuntu or Debian:
sudo apt install expectRun a one-shot expect SFTP upload
Prepare a local file:
printf 'expect-test\n' > /tmp/expect-upload.txtRun expect inline (replace the password and username with your values, or read the password from a restricted file):
export SFTP_USER=sftpuser
expect <<'EOF'
set timeout 30
set password "your_password_here"
set host "192.168.56.156"
set user "sftpuser"
spawn sftp -o StrictHostKeyChecking=accept-new -oBatchMode=no $user@$host
expect {
-nocase "*password*" { send "$password\r" }
timeout { exit 1 }
}
expect "sftp>"
send "cd upload\r"
expect "sftp>"
send "put /tmp/expect-upload.txt\r"
expect "sftp>"
send "bye\r"
expect eof
EOFSample transcript:
spawn sftp -o StrictHostKeyChecking=accept-new -oBatchMode=no sftpuser@192.168.56.156
sftpuser@192.168.56.156's password:
Connected to 192.168.56.156.
sftp> cd upload
sftp> put /tmp/expect-upload.txt
Uploading /tmp/expect-upload.txt to /home/sftpuser/upload/expect-upload.txt
expect-upload.txt 100% 12 3.0KB/s 00:00
sftp> byeFor production, move the expect block into its own file with mode 700 and read the password from a root-only secret file instead of embedding it in the script.
Wrap batch SFTP in a shell script
The pattern below uploads every *.csv file from a local directory when new files appear. It uses SSH keys and the same batch-file approach as Method 1:
cat > ~/bin/sftp-upload-csv.sh <<'SCRIPT'
#!/bin/bash
set -euo pipefail
SFTP_USER=${SFTP_USER:?set SFTP_USER}
SFTP_HOST=${SFTP_HOST:?set SFTP_HOST}
SRC_DIR=${1:?usage: sftp-upload-csv.sh /path/to/csv}
REMOTE_DIR=${2:?usage: sftp-upload-csv.sh /path/to/csv remote/dir}
batch=$(mktemp)
trap 'rm -f "$batch"' EXIT
shopt -s nullglob
files=("${SRC_DIR}"/*.csv)
if ((${#files[@]} == 0)); then
echo "No CSV files in ${SRC_DIR}" >&2
exit 0
fi
{
echo "cd ${REMOTE_DIR}"
for f in "${files[@]}"; do
echo "put $(basename "$f")"
done
echo "bye"
} > "$batch"
(cd "${SRC_DIR}" && sftp -o BatchMode=yes -b "$batch" "${SFTP_USER}@${SFTP_HOST}")
SCRIPT
chmod +x ~/bin/sftp-upload-csv.shRun it after exporting SFTP_USER, SFTP_HOST, and passing local and remote directory arguments. The script exits early when there is nothing to upload. The resulting script can be called from cron, systemd timers, CI, or another scheduler.
This example assumes the local filenames and remote directory do not contain spaces or special SFTP command characters.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Permission denied (publickey,password) with BatchMode=yes |
No usable SSH key | Install a key with SSH key authentication or switch to password method with BatchMode=no |
| Script hangs at password prompt | BatchMode=yes while using a password |
Set BatchMode=no and use sshpass or expect, or move to keys |
sftp: command not found |
OpenSSH clients not installed | Install openssh-clients (RHEL) or openssh-client (Debian) |
Couldn't read batchfile |
Wrong path to -b file |
Use an absolute path or cd to the directory that contains local put sources |
dest open ... Failure on put |
Remote directory missing or permissions | ssh user@host mkdir -p /remote/path and check ownership |
sshpass: command not found |
Package not installed | dnf install sshpass or apt install sshpass |
| expect times out at password | Prompt text differs | Run sftp user@host manually and match the prompt string in expect -nocase |
| Host key verification failed | Unknown server key | Connect once interactively or configure the correct host key before running the automated transfer |
References
Summary
Scripted SFTP comes down to two layers: batch commands (sftp -b) and authentication. For production, configure SSH keys and run sftp -o BatchMode=yes -b batchfile user@host from cron or a deploy script. That path ran end to end in this lab without passwords or helper tools.
When the remote side only accepts passwords, sshpass -e with the SSHPASS variable is the shortest batch-mode option, and expect fits prompts that do not match a fixed pattern. Both require BatchMode=no and both expose credentials more than keys do.
Start new automation with keys and a small batch file. Reach for sshpass or expect only on hosts you cannot change, and keep passwords out of the script body when you can load them from a restricted file at runtime.

