How to use sudo over SSH without leaking your password

Use ssh -t user@server 'sudo command' and type the password at the prompt: SSH encrypts it like everything else in the session. The leak comes from putting the password in a command, because ssh server 'echo pass | sudo -S command' shows it in the process list of both machines and can land in shell history. For automation, use a NOPASSWD rule for one exact command.

Updated . Run with sudo 1.9.16 and OpenSSH 10.0 on Debian 13 in a throwaway container, from an OpenSSH 10.3 client on macOS.

Is the sudo password sent in plain text?

Not on the network. Everything in an SSH session is encrypted, including what you type at a sudo prompt. The password is decrypted on the server, as it has to be for sudo to check it, so a compromised server can capture it. This is one reason not to reuse that password anywhere else.

The normal way: ssh -t

sudo needs a terminal to ask for a password, and a remote command gets none by default:

$ ssh user@server 'sudo whoami'
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required

The error suggests -S, and this is where people go wrong. The simple fix is -t, which asks for a terminal:

ssh -t user@server 'sudo whoami'

You get the normal prompt, nothing is echoed, and nothing goes in a command line.

What leaks with sudo -S

Here is the pattern to avoid:

ssh user@server 'echo mypassword | sudo -S systemctl restart myapp'

The password is now part of a command, so it appears in three places:

  • The process list on the server. While the command ran, another unprivileged account on the test server saw this with plain ps: user bash -c echo mypassword | sudo -S -p "" sleep 3.
  • The process list on your machine. The whole text is an argument of ssh, so ps shows it there as well.
  • Your local shell history, if you typed it, and any script or CI log that holds it.

If you really must feed sudo from a script

Read the password without echo and send it over standard input, so it never becomes an argument:

read -rs SUDO_PW
printf '%s\n' "$SUDO_PW" | ssh user@server 'sudo -S -p "" whoami'
unset SUDO_PW

printf is a shell builtin, so it does not start a process that ps can show. In the same test the other account saw only sudo -S -p sleep 3. -p "" removes the prompt from the output. sudo uses standard input for the password here, so the remote command should not need input of its own.

For automation: NOPASSWD for one exact command

A script should not hold a password at all. Allow only what it needs:

# /tmp/deploy.sudoers
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp, /usr/bin/journalctl -u myapp -n 100 --no-pager

Check the file before installing it, because a broken sudoers file can lock you out of sudo:

sudo visudo -cf /tmp/deploy.sudoers
sudo install -m 0440 -o root -g root /tmp/deploy.sudoers /etc/sudoers.d/deploy
sudo visudo -c

Then use it, with -n so a mismatch fails at once and does not hang waiting for a password:

$ ssh deploy@server 'sudo -n /usr/bin/systemctl restart myapp'
$ ssh deploy@server 'sudo -n /usr/bin/systemctl stop myapp'
sudo: a password is required

List what an account may run with sudo -l. Four traps:

  • A command with no arguments in the rule allows any arguments. With /usr/bin/id alone in the rule, both sudo -n /usr/bin/id and sudo -n /usr/bin/id -u ran in our test. Write the arguments you mean.
  • File names with a dot are ignored. /etc/sudoers.d/deploy.conf had no effect in our test, while the same file named deploy worked.
  • Some commands can start a shell. Editors, pagers, find -exec and tar can run other programs as root, so allowing one of them can amount to allowing everything. This is why the rule above has --no-pager: without it, journalctl can open a pager.
  • Wildcards match spaces too. The sudoers manual warns that wildcards in arguments match any character, including white space, so they can match more arguments than you planned.

How long sudo remembers you

By default sudo does not ask again for 5 minutes, and the record is separate for each terminal. In the test, one ssh -t session ran a second sudo -n whoami with no prompt. To end it early, run sudo -k. To ask every time for one account, add this with visudo:

Defaults:deploy timestamp_timeout=0

A root shell left open is where most slips happen. See how to avoid mistakes on production servers.

sudo -A and askpass

With -A, sudo runs the program named in SUDO_ASKPASS and reads the password from its output:

SUDO_ASKPASS=$HOME/bin/askpass sudo -A whoami

It keeps the password out of the process list, but the helper must get it from somewhere. On a server with no graphical session, that usually means a file, as in our test, so the secret now sits on disk. A narrow NOPASSWD rule is normally the better answer.

Ansible

Use --ask-become-pass (-K) to be prompted, or keep ansible_become_password in a file encrypted with Ansible Vault. Do not pass it with -e on the command line, because that puts it in the process list and your history like any other argument.

A NOPASSWD rule for a few commands does not work with Ansible. Its documentation says privilege escalation cannot be limited to certain commands, because modules run from a temporary file whose name changes each time. It suggests using a separate account without this constraint.

Questions

Is the sudo password sent in plain text over SSH?

No. Everything in an SSH session is encrypted, including what you type at a sudo prompt. It is decrypted on the server, which has to check it.

Why does ssh say a terminal is required to read the password?

A remote command gets no terminal by default, and sudo needs one to ask for the password. Add -t, as in ssh -t user@server 'sudo command'.

Is NOPASSWD safe for scripts?

It is safer than a stored password when the rule names one exact command with its arguments. Avoid editors, pagers and other programs that can start a shell.

Sources

More in Securing a server

From the blog

All guides