How to Pass an SSH Password in a Script with sshpass and Expect

Deepak Prasad
Tested on RHEL 10.2 with openssh-clients 9.9p1-25.el10_2.x86_64, sshpass 1.09-9.el10_0 from AppStream, expect 5.45.4
Package sshpass, expect, openssh-clients
Applies to Linux and Unix hosts with OpenSSH client; examples use bash on RHEL and Ubuntu
Privilege Normal user on the SSH client; password belongs to the remote account you automate
Scope Non-interactive password authentication with SSH and SCP using sshpass, Expect, and OpenSSH SSH_ASKPASS. Explains why piping a password to ssh fails, password versus keyboard-interactive authentication, and when SSH keys are the better design. Does not cover PSSH inventory runs or full ssh-agent scripting.
Related guides OpenSSH tutorial
SSH through a jump host
Short on time?Beta Get the gist of this 15-minute read in seconds, then ask follow-up questions

OpenSSH does not provide a --password option, and echo "password" | ssh ... does not satisfy the login prompt because the ssh command reads authentication input from a terminal rather than ordinary stdin.

Pick a tool by how interactive the session is:

  • one password prompt — sshpass is the simplest helper
  • several or changing prompts — Expect
  • repeated production automation — SSH public-key authentication avoids storing account passwords in scripts

Quick answer

The shortest sshpass example people search for is:

bash
sshpass -p 'secret' ssh user@server.example.com

That works in a one-off lab, but sshpass -p is the least safe password-source option:

  • places the password on the command line at process startup
  • can end up in shell history, scripts, and audit logs
  • should stay in demos, not persistent automation

For a script, prefer a protected runtime secret with -d, -f, or -e rather than embedding the password after -p:

bash
export SSHPASS='secret'
sshpass -e ssh user@server.example.com
unset SSHPASS
Method Best use
sshpass Simple password-based SSH/SCP automation
expect Multiple or varying interactive prompts
SSH_ASKPASS OpenSSH-native askpass helper when you already manage one
SSH key + ssh-agent Preferred long-term automation

Why echo password | ssh does not work

OpenSSH deliberately collects sensitive prompts, including the login password, through the controlling TTY instead of stdin. Piping text into ssh does not feed that prompt.

This pattern fails for password login:

bash
echo "$PASSWORD" | ssh user@server.example.com

The lab capture below forces the password authentication path so the failure is obvious. Those PreferredAuthentications and PubkeyAuthentication options are not required for normal sshpass usage:

bash
echo 'secret' | ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no sshlab@127.0.0.1 hostname
output
Permission denied, please try again.
Permission denied, please try again.
sshlab@127.0.0.1: Permission denied (publickey,password).

sshpass and Expect work because they watch the pseudo-terminal for the password prompt and send the secret there. They do not rely on ssh reading the password from a pipe.

┌──────────┐      ┌─────────────────────┐      ┌──────────────┐      ┌──────────┐
│ sshpass  │ ───► │ watches PTY for     │ ───► │ ssh prompts: │ ───► │ sshd     │
│ wrapper  │      │ Password:           │      │ Password:    │      │ checks   │
└──────────┘      └─────────────────────┘      └──────────────┘      └──────────┘

sshpass makes SSH behave as if a human typed the password at the terminal prompt. That mental model matters more than trying to push the secret through stdin.

The stdin distinction matters:

  • ssh — does not read login passwords from a pipe
  • sshpass — reads a password from stdin when you omit -p, -e, -f, and -d
bash
printf '%s\n' "$SSH_PASSWORD" | sshpass ssh user@server.example.com hostname

For scripts, -d is generally clearer when a process already has the secret available.


Method 1: Use sshpass

sshpass wraps ssh or scp, supplies the password when OpenSSH asks for it, and can return sshpass-specific exit codes when authentication or host-key checks fail.

Install sshpass

Package names match across distros, but the repository differs:

  • Ubuntu / Debian — main archive (1.09 on current releases); install with the apt command: sudo apt install sshpass
  • Fedora — standard repos; install with dnf: sudo dnf install sshpass
  • RHEL / Rocky / AlmaLinux — often AppStream; if dnf install sshpass returns No match for argument, enable AppStream or use Ubuntu/Fedora for the lab

On the RHEL 10.2 host used for this article, sshpass 1.09-9.el10_0 came from AppStream:

bash
dnf info sshpass
output
Name         : sshpass
Version      : 1.09
Release      : 9.el10_0
Repository   : rhel-10-for-x86_64-appstream-rpms
Summary      : Non-interactive SSH authentication utility

Confirm the binary after install:

bash
sshpass -V
output
sshpass 1.09
(C) 2006-2011 Lingnu Open Source Consulting Ltd.
(C) 2015-2016, 2021 Shachar Shemesh
This program is free software, and can be distributed under the terms of the GPL
See the COPYING file for more information.

Using "assword" as the default password prompt indicator.

Basic password login

For everyday use, export the password once and call sshpass -e:

bash
export SSHPASS='secret'
sshpass -e ssh user@server.example.com hostname
unset SSHPASS

sshpass forwards remaining arguments to ssh. You do not need extra -o flags unless you are deliberately forcing a specific authentication method.

The quick -p form is common in search results but should stay in demos:

bash
sshpass -p 'secret' ssh user@server.example.com
WARNING
sshpass -p places the password on the command line at process startup and is the least safe password-source option. The upstream project warns about command-line exposure and race conditions. Some implementations try to obscure the argument in ps after startup, but the secret can still appear in shell history, scripts, and audit logs. Do not use -p for persistent automation.

Choose a password source: -d, -f, -e, -p

sshpass accepts the password from four built-in sources. A practical security-oriented order is:

Rank Option When to use
1 -d file descriptor Often best when a runtime process feeds the secret without argv exposure
2 -f file Persistent secret on disk with tight permissions
3 -e environment Convenient, but visible to the same user and child processes
4 -p argument Lab or demo only

A root-owned 0600 secret file can be preferable to an environment variable in some automation environments, so treat the table as guidance rather than a fixed rule.

Avoid putting the password on the command line: use a file descriptor

The sshpass manual recommends an anonymous pipe or file descriptor instead of -p. This keeps the password out of sshpass command arguments, but the secret still exists in:

  • the shell variable from read
  • process memory for the duration of the command
  • the here-string behind file descriptor 3 until you close it
bash
read -rsp "SSH password: " SSH_PASSWORD
echo

exec 3<<<"$SSH_PASSWORD"

sshpass -d 3 ssh user@server.example.com hostname

exec 3<&-
unset SSH_PASSWORD

For unattended jobs, replace the interactive read with your approved secret source.

Read the password from a protected file

Restrict the password file before sshpass reads it:

bash
chmod 600 ~/.ssh/ssh-password

sshpass -f ~/.ssh/ssh-password ssh user@server.example.com hostname

The file becomes the secret. Restrict ownership and mode, and keep it out of backups you do not control.

Use the SSHPASS environment variable

After the first export SSHPASS='secret' example above, later sections assume SSHPASS is already set in the shell unless noted otherwise.

An environment variable is still a secret-bearing process resource. It is not a substitute for a vault or configuration-management secret store.

Run remote commands and SCP

Assuming SSHPASS is already set for this shell, run one remote command:

bash
sshpass -e ssh user@server.example.com 'hostname'

Run several commands in one remote shell session:

bash
sshpass -e ssh user@server.example.com 'hostname; uptime; id'

The same mechanism applies to scp because scp uses SSH authentication. Modern OpenSSH may prefer SFTP mode for scp, but password prompting still follows the SSH authentication path.

Assuming SSHPASS is already set:

  • Upload — sshpass -e scp file.txt user@server.example.com:/tmp/
  • Download — sshpass -e scp user@server.example.com:/var/log/app.log .
  • Rsync over SSH — sshpass -e rsync -av -e ssh ./data/ user@server.example.com:/backup/ (see rsync command)

Lab verification on 127.0.0.1 adds client options only to force the password path for a reproducible capture:

bash
sshpass -e scp /tmp/local.txt sshlab@127.0.0.1:/tmp/remote.txt
sshpass -e ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no sshlab@127.0.0.1 cat /tmp/remote.txt
output
lab scp test

Do not copy those PreferredAuthentications and PubkeyAuthentication=no flags into production unless you intentionally want to disable public-key fallback.

Keep host-key verification enabled

Password automation should not disable server identity checks by default.

Avoid making this the normal pattern:

bash
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null

That accepts any host key on every connection and removes MITM protection.

Host-key handling depends on how long you keep the server:

  • Persistent infrastructure — verify the server host key out of band and populate ~/.ssh/known_hosts before automation runs
  • Disposable lab hosts — trust-on-first-use may be acceptable:
bash
sshpass -e ssh -o StrictHostKeyChecking=accept-new user@server.example.com hostname

accept-new adds a previously unknown host key but still refuses a host whose key has changed since you stored it. It helps only with new hosts; a key rotation on an existing host still stops the connection.

Common sshpass exit codes and troubleshooting

Before you blame sshpass, confirm the host is reachable and port 22 accepts connections with the layered checks in test an SSH connection.

sshpass defines its own exit statuses for password and host-key problems. Failures from the wrapped ssh or scp command may surface as SSH's own status, commonly 255 for a generic client failure. The table below is a quick subset, not an exhaustive list.

Exit Typical meaning
0 Command succeeded
1 Invalid sshpass command-line argument
3 sshpass runtime error
5 Invalid password
6 Host public key is unknown
7 Host public key changed
255 Propagated SSH failure (network, config, or rejected connection)

Wrong password on the lab host:

bash
sshpass -p 'wrong' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no sshlab@127.0.0.1 hostname
output
Permission denied, please try again.

Check the exit status immediately:

bash
echo $?
output
5

Status 5 means sshpass detected bad credentials.

Unknown host key with strict checking:

text
No ED25519 host key is known for 127.0.0.1 and you have requested strict checking.
Host key verification failed.

Network failure looks different:

text
Connection timed out during banner exchange
Connection to 192.168.99.99 port 22 timed out

A non-zero exit status only tells you that something failed. Read the code and stderr together to separate:

  • authentication (5 from sshpass, or Permission denied)
  • host-key verification (exit 6/7, or Host key verification failed)
  • networking (Connection timed out, Connection refused, or SSH exit 255)

Use verbose mode when a manual login works but automation stops:

bash
sshpass -v -e ssh sshlab@127.0.0.1 hostname
output
SSHPASS: searching for password prompt using match "assword"
SSHPASS: read: 
sshlab@127.0.0.1's password: 
SSHPASS: detected prompt. Sending password.
SSHPASS: read: 

vm1.lab.example

Custom password prompts with sshpass -P

By default, sshpass searches for the substring assword so both Password: and password: match. When verbose mode shows a different prompt string, override the pattern with -P:

bash
sshpass -e -P 'Enter passcode:' ssh user@server.example.com

Use -P only when authentication still involves one predictable prompt but the server uses text different from the normal Password: prompt. If authentication requires multiple prompts, use Expect instead.

Situation Tool
One standard password prompt sshpass
One custom prompt text sshpass -P
Multiple prompts, OTP, or MFA Expect

Password versus keyboard-interactive authentication

OpenSSH can authenticate through either the password method or keyboard-interactive. They are separate SSH authentication methods, but sshpass can handle a simple password-style prompt from either mechanism because it watches the pseudo-terminal for a matching pattern.

The problem appears when keyboard-interactive authentication produces multiple or unusual prompts, such as:

  • Password:
  • Verification code:
  • OTP:
  • Enter token:

In that situation, sshpass may not be enough because it normally waits for a single password-prompt pattern. Use sshpass -P when only the prompt text differs, or Expect when the session needs responses to several different prompts.

Check what the server advertises:

bash
ssh -vvv user@server.example.com

Look for a line such as one of these:

  • Authentications that can continue: publickey,password
  • Authentications that can continue: publickey,keyboard-interactive

PasswordAuthentication and KbdInteractiveAuthentication are configured independently in sshd. Either line can still work with sshpass when the session ends in one normal password-style prompt. See OpenSSH authentication methods in sshd_config for how those directives interact on the server.

On the server, inspect the effective settings with sudo:

bash
sudo sshd -T | grep -E 'passwordauthentication|kbdinteractiveauthentication'
output
passwordauthentication yes
kbdinteractiveauthentication yes

When passwordauthentication is no, the client cannot use the password method, but keyboard-interactive may still present a single password prompt that sshpass can answer.


Method 2: Use Expect

Use Expect when the session has more than one interactive prompt, or when prompts vary between hosts. For a single SSH password prompt, sshpass (or sshpass -P for custom prompt text) is usually enough.

Install Expect from the same package managers as sshpass:

  • RHEL / Fedora / Rocky / AlmaLinux — sudo dnf install expect
  • Ubuntu / Debian — sudo apt install expect

Handle multiple prompts

You only need four concepts for this example:

  • spawn — start ssh
  • expect — wait for output from the remote session
  • send — type a response at the prompt
  • eof — the remote process has finished

The script below expects SSH_USER, SSH_HOST, and SSH_PASSWORD to be set in the environment before it starts. If any variable is missing, Tcl throws an error immediately.

tcl
#!/usr/bin/expect -f

set timeout 15

if {![info exists env(SSH_USER)] || ![info exists env(SSH_HOST)] || ![info exists env(SSH_PASSWORD)]} {
    puts stderr "Set SSH_USER, SSH_HOST, and SSH_PASSWORD before running this script."
    exit 3
}

set user $env(SSH_USER)
set host $env(SSH_HOST)
set password $env(SSH_PASSWORD)

spawn ssh "$user@$host" hostname

expect {
    -re "(?i)password:" {
        send -- "$password\r"
        exp_continue
    }
    -re "Permission denied" {
        exit 1
    }
    -re "Connection refused" {
        exit 2
    }
    -re "Connection timed out" {
        exit 2
    }
    -re "Host key verification failed" {
        exit 4
    }
    timeout {
        exit 2
    }
    eof
}

Run it after exporting the connection details and reading the password once:

bash
export SSH_USER=sshlab
export SSH_HOST=127.0.0.1
read -rsp "Password: " SSH_PASSWORD
export SSH_PASSWORD

./ssh-login.exp

unset SSH_PASSWORD SSH_USER SSH_HOST
output
spawn ssh sshlab@127.0.0.1 hostname

sshlab@127.0.0.1's password: 
vm1.lab.example

Do not pass the password as a third script argument. Command-line arguments are visible in ps output.

Handle authentication errors

The expect block above stops on explicit failure text instead of looping on repeated password prompts until eof:

  • Permission denied — bad credentials or account policy
  • Connection refused / Connection timed out — network or sshd not listening
  • Host key verification failed — strict host-key checking blocked the session
  • timeout — no prompt arrived within 15 seconds

That is still a teaching script, not production-grade error reporting, but it shows the control-flow model Expect expects: match prompts, send input, and exit on known failure strings.

Use Expect with SCP

Reuse the same password-prompt and error-handling block for scp:

tcl
spawn scp file.txt "$user@$host:/tmp/"

Match the same -re "(?i)password:" branch and let exp_continue run until eof or an explicit failure pattern matches.


Method 3: Use OpenSSH SSH_ASKPASS

OpenSSH can call an external askpass program when it needs a password. This answers searches for automating SSH without sshpass or Expect, but it takes more setup than sshpass -e.

OpenSSH selects an askpass helper in two main ways:

  • Classic — no controlling terminal and DISPLAY is set
  • Modern — SSH_ASKPASS_REQUIRE=force selects the helper even when a terminal exists
┌─────────────┐      ┌──────────────────┐      ┌─────────────────┐
│ ssh client  │ ───► │ SSH_ASKPASS      │ ───► │ helper prints   │
│ needs pass  │      │ helper program   │      │ password line   │
└─────────────┘      └──────────────────┘      └─────────────────┘
WARNING
The helper below is intentionally simplified for demonstration. Do not store a real account password in a script file for production use.

Create a helper that reads the secret from the environment:

bash
cat > ~/bin/ssh-askpass-helper <<'EOF'
#!/bin/sh
printf '%s\n' "$MY_SSH_PASSWORD"
EOF
chmod 700 ~/bin/ssh-askpass-helper

Force askpass even when a TTY is present:

bash
export MY_SSH_PASSWORD='secret'
SSH_ASKPASS=~/bin/ssh-askpass-helper SSH_ASKPASS_REQUIRE=force \
    ssh user@server.example.com hostname
unset MY_SSH_PASSWORD

On OpenSSH 9.9 in this lab, SSH_ASKPASS_REQUIRE=force invoked the helper without setting DISPLAY. When askpass is not selected:

  • try DISPLAY=:0 as a compatibility hint for older graphical helpers
  • confirm the helper path is executable and prints one password line on stdout

SSH_ASKPASS_REQUIRE also accepts prefer and never. Use force only when you understand that the helper program becomes the secret store.


Better long-term option: SSH keys

For scripts that run daily, public-key authentication is the better default. The automation process unlocks a key or uses an agent session instead of storing the remote account password.

Generate a modern key:

bash
ssh-keygen -t ed25519

Install the public key on the server:

bash
ssh-copy-id user@server.example.com

Connect without a password prompt when the key has no passphrase:

bash
ssh user@server.example.com

When the private key has a passphrase, load it into ssh-agent for the current session:

bash
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

Pick the key model to match who unlocks it:

  • Interactive or agent-backed runs — prefer a passphrase plus ssh-agent
  • Unattended service automation — some environments use dedicated passphrase-less keys with tight filesystem permissions and server-side restrictions; treat those keys as high-value credentials

See SSH key authentication for authorized_keys layout, permissions, and troubleshooting, and generate SSH keys for algorithm choice.


Which method should you use?

Situation Tool
One standard password prompt sshpass
One custom prompt text sshpass -P
Multiple prompts, OTP, or interactive workflow Expect
Existing askpass integration SSH_ASKPASS
Recurring unattended automation SSH keys

Common mistakes

Symptom Likely cause What to check
Manual ssh works, sshpass fails Multiple or unusual prompts; custom prompt text; or host-key stall sshpass -v; try -P for one custom prompt; Expect for MFA or multi-step flows
sshpass exits 5 Wrong password Credentials; account lockout
Host-key error or exit 6/7 Unknown or changed server key known_hosts; use accept-new only on disposable labs
passwordauthentication no on server Password method disabled in sshd Server policy; use keys instead
Automation hangs at a prompt Host-key confirmation or non-password prompt sshpass -v; preload known_hosts

Summary

OpenSSH will not take a login password from a pipe because it reads sensitive prompts through the controlling terminal. That design choice explains most scripting failures that start with echo "$PASSWORD" | ssh.

When password automation is unavoidable, sshpass is the practical default for one SSH or SCP prompt. Prefer -d, -f, or -e over -p, keep host-key verification enabled, and remember that accept-new helps only with brand-new hosts. If manual login works but sshpass does not, check for multiple prompts or custom prompt text before you blame the password itself.

Expect belongs in the toolbox when prompts multiply or change shape. SSH_ASKPASS is legitimate OpenSSH plumbing, but the helper still holds the secret. For scheduled or large-scale automation, SSH keys with ssh-agent, or a proper secret-management workflow, remain the better long-term design.


References


Frequently Asked Questions

Can I pass a password directly to the ssh command?

OpenSSH has no normal --password flag. Use an interactive terminal, sshpass, Expect, an askpass helper, or key-based authentication.

Why doesn't piping a password to ssh work?

SSH reads sensitive authentication prompts through the controlling TTY, not through ordinary standard input. sshpass can read a password from its own stdin when no -p, -e, -f, or -d option is supplied.

Is sshpass safe?

It automates password authentication, which means some process must hold the password. Avoid -p for real automation and protect whichever password source you choose.

Can sshpass automate SCP?

Yes. The same sshpass -e, -f, or -d options work when sshpass wraps scp instead of ssh.

Which is better, sshpass or Expect?

Use sshpass when the only interaction is an SSH password prompt. Use Expect when you must answer several changing prompts in one session.

Should I use a password or SSH key for scripts?

For repeated or production server automation, SSH keys with a passphrase and ssh-agent generally avoid embedding account passwords in scripts.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
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