How to Automate SFTP in Shell Scripts on Linux

Deepak Prasad
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_2
sshpass 1.09-9.el10_0
expect 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.

IMPORTANT
Prefer SSH keys for scripted SFTP. Password automation stores secrets in scripts, environment variables, or process lists. Use sshpass or expect only when the remote side cannot accept keys.

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, and cd. Prefix a command with - to let that specific failure be ignored, for example -rm oldfile.
  • Without SSH keys, set BatchMode=no so sftp may prompt for a password (or let sshpass/expect answer it).
  • With keys, set BatchMode=yes so 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:

bash
export SFTP_HOST=192.168.56.156
export SFTP_USER=root

For the password-based examples later, use a normal user account on the server (not root) and set SFTP_USER to that name.


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:

bash
ssh -o BatchMode=yes -o ConnectTimeout=5 "${SFTP_USER}@${SFTP_HOST}" 'hostname'

Sample output:

output
vm2.lab.example

A 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:

bash
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:

bash
ssh "${SFTP_USER}@${SFTP_HOST}" 'mkdir -p /tmp/sftp-lab'

Write the SFTP batch commands to a temporary file:

bash
cat > /tmp/sftp-batch.cmd <<'EOF'
cd /tmp/sftp-lab
put upload1.txt
put upload2.txt
ls -l
quit
EOF

The 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:

bash
cd "${LAB_SRC}"
sftp -o BatchMode=yes -b /tmp/sftp-batch.cmd "${SFTP_USER}@${SFTP_HOST}"

Sample output:

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> quit

Confirm the files landed on the server:

bash
ssh "${SFTP_USER}@${SFTP_HOST}" 'ls -l /tmp/sftp-lab/'
output
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.txt

Automate SFTP with a here document in bash

You can skip the temporary batch file and pipe commands on standard input:

bash
cd "${LAB_SRC}"
sftp -o BatchMode=yes -b - "${SFTP_USER}@${SFTP_HOST}" <<'EOF'
cd /tmp/sftp-lab
put upload1.txt
put upload2.txt
bye
EOF

Both 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:

bash
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
EOF

The 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:

bash
sftp -o BatchMode=yes -b - "${SFTP_USER}@${SFTP_HOST}" <<'EOF'
cd /remote/export
get report.csv
bye
EOF

Check SFTP exit status in shell scripts

A wrapper script should treat a non-zero sftp exit code as a failed transfer:

bash
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
fi

When 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:

bash
sudo dnf install sshpass

On Ubuntu or Debian:

bash
sudo apt update
sudo apt install sshpass

Run 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:

bash
printf 'sshpass-test\n' > /tmp/sshpass-upload.txt

Run batch SFTP with password authentication (BatchMode=no is required):

bash
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
EOF

Sample output:

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> bye

The 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:

bash
sudo dnf install expect

On Ubuntu or Debian:

bash
sudo apt install expect

Run a one-shot expect SFTP upload

Prepare a local file:

bash
printf 'expect-test\n' > /tmp/expect-upload.txt

Run expect inline (replace the password and username with your values, or read the password from a restricted file):

bash
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
EOF

Sample transcript:

output
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> bye

For 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:

bash
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.sh

Run 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.


Frequently Asked Questions

1. Can I run sftp in a script without typing a password?

Yes. Set up SSH key authentication between the client and server, then run sftp with -b for batch mode and BatchMode=yes so OpenSSH never prompts interactively. This is the approach most production cron jobs use.

2. Why does sftp -b fail with a password prompt in a script?

Batch mode expects non-interactive authentication. Password prompts need BatchMode=no plus a helper such as sshpass or expect, or you must switch to SSH keys. With keys configured, use BatchMode=yes instead.

3. Is sshpass safe for SFTP automation?

sshpass stores or passes a password non-interactively, which exposes it in process lists, logs, and shell history. It is acceptable only for legacy systems that cannot use keys. Prefer SSH keys or a secrets manager for scheduled transfers.

4. What is the difference between sftp -b and a here document?

The -b option reads commands from a file. A here document fed to sftp -b - does the same thing from stdin. Both are batch mode; authentication still follows your SSH settings.
Omer Cakmak

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration