241

I have configured sudo to run without a password, but when I try to ssh 'sudo Foo', I still get the error message sudo: sorry, you must have a tty to run sudo.

Why does this happen and how can I work around it?

8 Answers 8

308

That's probably because your /etc/sudoers file (or any file it includes) has:

Defaults requiretty

...which makes sudo require a TTY. Red Hat systems (RHEL, Fedora...) have been known to require a TTY in default sudoers file. That provides no real security benefit and can be safely removed.

Red Hat have acknowledged the problem and it will be removed in future releases.

If changing the configuration of the server is not an option, as a work-around for that mis-configuration, you could use the -t or -tt options to ssh which spawns a pseudo-terminal on the remote side, but beware that it has a number of side effects.

-tt is meant for interactive use. It puts the local terminal in raw mode so that you interact with the remote terminal. That means that if ssh I/O is not from/to a terminal, that will have side effects. For instance, all the input will be echoed back, special terminal characters (^?, ^C, ^U) will cause special processing; on output, LFs will be converted to CRLFs... (see this answer to Why is this binary file being changed? for more details.

To minimise the impact, you could invoke it as:

ssh -tt host 'stty raw -echo; sudo ...' < <(cat)

The < <(cat) will avoid the setting of the local terminal (if any) in raw mode. And we're using stty raw -echo to set the line discipline of the remote terminal as pass through (effectively so it behaves like the pipe that would be used instead of a pseudo-terminal without -tt, though that only applies after that command is run, so you need to delay sending something for input until that happens).

Note that since the output of the remote command will go to a terminal, that will still affect its buffering (which will be line-based for many applications) and bandwidth efficiency since TCP_NODELAY is on. Also with -tt, ssh sets the IPQoS to lowdelay as opposed to throughput. You could work around both with:

ssh -o IPQoS=throughput -tt host 'stty raw -echo; sudo cmd | cat' < <(cat)

Also, note that it means the remote command cannot detect end-of-file on its stdin and the stdout and stderr of the remote command are merged into a single stream.

So, not so good a work around after all.

If you've a got a way to spawn a pseudo-terminal on the remote host (like with expect, zsh, socat, perl's IO::Pty...), then it would be better to use that to create the pseudo-terminal to attach sudo to (but not for I/O), and use ssh without -t.

For example, with expect:

ssh host 'expect -c "spawn -noecho sh -c {
     exec sudo cmd >&4 2>&5 <&6 4>&- 5>&- 6<&-}
 exit [lindex [wait] 3]" 4>&1 5>&2 6<&0'

Or with script (here assuming the implementation from util-linux):

ssh host 'SHELL=/bin/sh script -qec "
              sudo cmd <&3 >&4 2>&5 3<&- 4>&- 5>&-
            " /dev/null 3<&0 4>&1 5>&2'

(assuming (for both) that the login shell of the remote user is Bourne-like).

6
  • big thank you for this : I used the last (script -qec ....) trick today. I added some commands before and after : ssh host 'cmds before ; SHELL=/bin/sh script -qec "sudo cmdsudoed here with args <&3 >&4 2>&5 3<&- 4>&- 5>&-" /dev/null 3<&0 4>&1 5>&2 ; cmds after here' # the ones after don't display ... probably due to all those &- ? Are they mandatory? (if I understand the order correctly: they retrieve the current values(=fd) of stdin, stdout and stderr, redirects (copy) sudo's in/out/err to the same destinations, then close them for script? thus it closes the outer stdin/out/err too?) Commented Dec 3, 2020 at 13:23
  • moreover I remember reading on this site somewhere that closing fd0 1 or 2 is not recommended: [edit: found it: of course, it was from you :) ) : unix.stackexchange.com/a/446591/27616 : as I don't see output from the commands after the closing of 3, 4, 5: does it mean it closed my ssh shell's 0, 1 and 2 ? and therefore... my successive commands could end up writing in the wrong place? :/ Commented Dec 3, 2020 at 13:35
  • @OlivierDulac, no n>&- (or n<&- which is the same) closes fd n. Only exec 0<&- 1<&- 2<&- (or exec <&- >&- 2>&-) would close stdin/stdout/stderr from now on. We close those fds 3, 4, 5 for sudo, in the shell started by script because neither sudo nor cmd need them and they've served their purpose (restore the original remote shell stdin/out/err inside the script session). I don't know why you don't see the output of cmds after here. Commented Dec 3, 2020 at 14:15
  • Thanks for the answer, I am puzzled as well, and will need further testing to figure why this is happening... Commented Dec 7, 2020 at 8:33
  • Stephane, I have noticed I lose stdout after the first error: in your last (script) exemple, instead of sudo cmd, I have: sudo su - someuser -c \"id ; echo $? ; cd /_not_a_dir_ ; echo $? ; id\" (the rest is the same as in your answer) : I only see the output of the first id and the 0, and then nothing else. I don't see the error from the cd /_not_a_dir_ , nor the subsequent echo $?, nor the last id. if I instead : cd / ; I see all (stdout) outputs Commented Feb 2, 2021 at 16:40
32

By default, SUDO is configured to require a TTY. That is, SUDO is expected to be run from a login shell. You can defeat this requirement by adding the -t switch to your SSH invocation:

ssh -t someserver sudo somecommand

The -t forces allocation of a pseudo-tty.

If you want to perform this globally, modify /etc/sudoers to specify !requiretty. This can be done a a per user, per group or all-encompassing level.

2
  • 5
    No, that's not the default. It's only the redhat distribution of sudo that had requiretty in its default sudoers. It will be fixed in newer releases Commented Apr 1, 2014 at 20:28
  • 1
    @StephaneChazelas +1 for enlightening me that it's largely indigenous to Red Hat and its siblings and another ++ if I could for the current bug report!
    – JRFerguson
    Commented Apr 1, 2014 at 20:38
19

Use the -t flag to ssh to force tty allocation.

$ ssh luci tty
not a tty
$ ssh luci -t tty
/dev/ttys003
$
1
  • 1
    Add a second -t to force allocation when Pseudo-terminal will not be allocated because stdin is not a terminal.
    – Samveen
    Commented Mar 1, 2017 at 9:35
11

I ran into this problem using Docker and Centos 7. I ended up doing the following:

yum install -y sudo

sed -i -e 's/Defaults requiretty.*/ #Defaults requiretty/g' /etc/sudoers

I found this hack at https://hub.docker.com/r/liubin/fluentd-agent/~/dockerfile

1
  • 2
    This answer made the most sense for me, using Docker and CentOS 7 as well. It was easy to understand as well as copy/paste friendly!
    – DaShaun
    Commented May 2, 2016 at 18:58
1

An interesting alternative is to run FreeIPA or IdM to manage your users and sudoer rules centrally. You can then create sudo rules and assign the option

!requiretty

in the rule. The command will then run as expected. You will also have the benefits of managing all the servers and users from a single set of configurations.

1

One more option that I didn't see in replies in any such questions is open_init_pty. I was unable to change sudo configuration as well as to provide -t option in any quantity (in fact you can't do anything if ssh is called by Java program such as bamboo or anything like that). Suddenly, man -k pty solved my problem.

So my solution was to change sh very_useful_script.sh to /usr/sbin/open_init_pty /bin/sh very_useful_script.sh. You cant call sudo directly, for example open_init_pty sudo -u wildfly id.

0

I found this question while Googling and I encountered this error for a completely different reason.

My fix was to stop calling downstream shell scripts as sudo from my parent shell script, when the parent shell script was already called with sudo.

0

I had the same issue. In my case, the solution was two lines

myscript=$(cat ssh_test.sh)
ssh -t user@host "$myscript"

Explanation:

  • Place the commands you want to run (including sudo commands) into a script e.g. "ssh_test.sh".

  • Read the whole script into a variable called "myscript".

  • Invoke ssh with just one -t and supply the variable instead of a command.

Prior to this, I ran into problems using combinations of reading from stdin and using heredocs

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .