How to work around “sorry, you must have a tty to run sudo” without sacrificing security

While working on $client‘s Linux server last week, I found myself installing a cron job that ran as root. The cron job called a custom bash script that, in turn, called out to various custom maintenance tasks client had already written. One task in particular had to run as a different user.

During testing, I discovered that the odd-ball task failed to run, and found the following error in the system log:

sudo: sorry, you must have a tty to run sudo

I traced this error to a line trying to invoke a perl command as a user called dynamic:

sudo -u dynamic /usr/bin/perl run-periodic-tasks --load 5 --randomly

A simple Google search turned up an obvious solution to the error: use visudo to disable sudo’s tty requirement, allowing sudo to be invoked from any shell lacking a tty (including cron). This would have solved my problem, but it just felt wrong, dirty, and most troublingly insecure.

One reason why sudo ships with the requiretty option enabled by default is, among other reasons, to prevent remote users from exposing the root password over SSH. Disabling this security precaution for a simple maintenance task already running as root seemed totally unnecessary, not to mention irresponsible. Moreover, client‘s script didn’t even need a tty.

Thankfully, there’s a better way: use su --session-command and send the whole job to the background.

su --session-command="/usr/bin/perl run-periodic-tasks --load 5 --randomly" dynamic &

This line launches a new, non-login shell (typically bash) as the other user in a separate, background process and runs the command you passed using the shell’s -c option. Sending the command to the background (using &) continues execution of the rest of the cron job.

A process listing would look like this:

root     28109     1  0 17:10 ?        00:00:00 su --session-command=/usr/bin/perl run-periodic-tasks --load 5 --randomly dynamic
dynamic  28110 28109  0 17:10 ?        00:00:00 bash -c /usr/bin/perl run-periodic-tasks --load 5 --randomly

Note the parent process (PID 28109) is owned by root but the actual perl process (PID 28110) is being run as dynamic.

This in-script solution that replaces sudo -u user cmd with su --session-command=cmd user seems much better than relying on a change in sudo‘s default (and more secure) configuration to me.

18 replies on “How to work around “sorry, you must have a tty to run sudo” without sacrificing security”

  1. This is one gem of a one-line code that can save someone A LOT OF aggravation! This coming from a guy who spent last 4 hours trying to figure out how to execute a shell script from within cPanel’s chkservd service. Situations where you need to run sudo but don’t have a tty are not limited to cron jobs. If you are running a site on cPanel, you might want to use its tailwatchd service (which chkservd is a part of) to make sure essential services are always running. Sure, there are restart scripts for all major services but if you are adding one of our own, you’ll have to write a script that is able to restart the service and, of course, for safety you are most likely not going to want to run them as root.

    Oh, and there is another rub: cPanel actually does not allow changing “requiretty” setting: you can change it, but on the next major cPanel upgrade they’ll automatically rewrite it back to a safer default. This was driving me absolutely NUTS!

    I also wanted to make a small suggestion: older versions of su don’t have –session-command= option. It is much safer to use just –command= because it does the same thing and will work on all versions.

    Cheers and keep up the good work!

  2. A small addition to avoid the “This account is currently not available” error when trying to run a command as a user who doesn’t have a valid login shell and

    Add the –shell option

    su –shell=/bin/bash –session-command=”/path/to/command -argument=something” username &

  3. Hi,

    Good suggestion. But I still don’t understand one thing.

    su –session-command= will still prompt for the root password, which means I have to enter the password manually i.e. I can’t run it non-interactively. How do I get around this, because I need to have everything run in auto mode without any user interaction.


  4. P.S. With sudo, I could automate it by using “echo $password | sudo -S “. But that doesn’t seem possible with su –session-command, right?

  5. If you are attempting to run a command remotely (assuming /etc/sudoers allows you to run it with NOPASSWD), pass the -t option to ssh:

    ssh -t myhost “sudo -n yum -y update”

    The -n option to sudo will prevent sudo from prompting for a password, if the command requires a password you’ll get an error.

  6. Sounds good if you’re starting as root. I’m working the opposite direction — srv1 is running a periodic task that wants to grab a backup from certain areas on srv2. I don’t want the task it triggers on srv2 to run as root; but it needs root to grab a few user directory backups.

    The remote task is authenticated via an unsigned private key, so running it as root really isn’t much of an option — I don’t want an unsigned private key for root running around!

    Oh, and I carefully turn *off* pty because I need an 8-bit clean channel (the remote task sends the backup bzipped tar file out stdout back to the periodic task on srv1, which then stores it on srv1).

    So far turning off requiretty for the backup user is looking like my best option.

Comments are closed.