Home/Security/sudo: no tty present and no askpass — 30-Second Diagnosis and Recovery (cron, CI, Ansible)
Securitysudoers NOPASSWDrequiretty

sudo: no tty present and no askpass — 30-Second Diagnosis and Recovery (cron, CI, Ansible)

The real cause of the sudo "no tty present and no askpass" error in cron, CI, and Ansible, plus a 30-second diagnosis table. Copy-paste recovery for a safe NOPASSWD sudoers setup, requiretty removal, and SUDO_ASKPASS.

sudo: no tty present and no askpass — 30-Second Diagnosis and Recovery (cron, CI, Ansible)
Contents
  1. sudo: no tty present and no askpass — 30-Second Diagnosis and Recovery (cron, CI, Ansible)
  2. 30-second diagnosis table: Where did it blow up?
  3. Three copy-paste recovery options
  4. Security trap: Bind NOPASSWD to least privilege
  5. Conclusion: 5-line recovery checklist
  6. FAQ

sudo: no tty present and no askpass — 30-Second Diagnosis and Recovery (cron, CI, Ansible)

It's 3 a.m. Instead of a deploy notification, a job-failure alert goes off. You open the cron log and find a single line.

CODE
sudo: no tty present and no askpass program specified

Here's the 30-second summary. In a non-interactive environment with no terminal (tty), sudo tried to prompt for a password, had nowhere to take input, and failed. On an SSH shell with a human attached, sudo can display a prompt and accept a password. cron, systemd, CI, and Ansible have no tty to receive that prompt. So sudo dies immediately with "I need to ask for a password, but there's no tty to ask on, and no askpass program was specified either."

Once you know the principle, recovery is one of three options: ① Don't ask for a password at all (NOPASSWD), ② Turn off the setting that requires a tty (requiretty), ③ Specify an askpass helper that supplies the password. Assuming you're on-call, we'll go in order: diagnosis table, copy-paste recovery, then how to avoid the security traps.

30-second diagnosis table: Where did it blow up?

Once you know which environment it ran in, the first-line fix is obvious.

Execution environmenttty present?Typical symptoms / log locationFirst-line fix
cron jobnone/var/mail/$USER or journalctl -u cron, grep CRON /var/log/syslogsudoers NOPASSWD
systemd unitnonejournalctl -u myapp.service -eNOPASSWD, or run the unit as root
GitHub Actions / GitLab CInoneFailed step in the CI job logHosted runners are usually passwordless; self-hosted runners need NOPASSWD
Ansible become: truenone by defaultMODULE FAILURE / sudo: a password is requiredbecome_password, or remove requiretty
Wrapped in su -c "sudo ..."noneScript stderrDrop the wrap + NOPASSWD

The key point is that every non-interactive environment has no tty. cron and systemd spawn child processes without a tty, and CI runners are headless. GitHub Actions' default runners already grant passwordless sudo to the runner user, so you rarely hit this error there—but on a self-hosted runner you have to configure it yourself.

Three copy-paste recovery options

(A) Add NOPASSWD to sudoers correctly — the safest, canonical fix

Why it works: If sudo never asks for a password, it has no reason to look for a tty.

Don't edit /etc/sudoers directly; always use visudo to create a drop-in file. A syntax error can lock sudo itself and cut off server access.

Bash
sudo visudo -f /etc/sudoers.d/deploy

Keep the file contents scoped to a specific user and command, as tightly as possible:

CODE
# /etc/sudoers.d/deploy
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp

Permission and syntax checks are mandatory:

Bash
sudo chmod 0440 /etc/sudoers.d/deploy
sudo visudo -c # syntax OK check
sudo -l -U deploy # deploy

If the mode isn't 0440, sudo will ignore the file or emit a warning.

(B) Remove Defaults requiretty and how to verify

Why it works: requiretty is a hard rule: "sudo may only run from a tty." Non-interactive environments have no tty, so if this rule is on, you're blocked every time.

It's sometimes enabled by default on older CentOS/RHEL. Check and comment it out:

DIFF
# /etc/sudoers (visudo )
-Defaults    requiretty
+# Defaults    requiretty
Bash
sudo grep -R requiretty /etc/sudoers /etc/sudoers.d

Note that in recent sudo and OpenSSH, requiretty is off by default, so on a modern distro (A) alone usually solves it.

(C) SUDO_ASKPASS + sudo -A — supply the password via a helper

Why it works: Instead of a tty, you tell sudo about a helper program that prints the password to stdout.

Bash
cat > /usr/local/bin/askpass.sh <<'EOF'
#!/bin/sh
echo "$MY_SECRET"
EOF
chmod 700 /usr/local/bin/askpass.sh

export SUDO_ASKPASS=/usr/local/bin/askpass.sh
sudo -A systemctl restart myapp

⚠️ Warning: This approach can leak the password into environment variables, the process list, and the script file. Avoid storing it in plaintext; inject it from Vault or a CI secret if you can. Prefer (A) NOPASSWD, and treat (C) as a last resort.

Handling this in Ansible

become: true internally calls sudo and hits the same error. Two tips:

INI
# ansible.cfg
[ssh_connection]
pipelining = True # sudo requiretty
 # requiretty after
YAML
# playbook — NOPASSWD
- hosts: web
  become: true
  tasks:
    - name: restart app
      ansible.builtin.systemd:
        name: myapp
        state: restarted

If you really need a password, inject ansible_become_password safely via --ask-become-pass or Vault.

Security trap: Bind NOPASSWD to least privilege

If you panic and drop this in, the incident starts:

CODE
deploy ALL=(ALL) NOPASSWD:ALL # ❌

If the deploy account is compromised, that's an instant full root takeover. Three rules to keep the principle of least privilege (PoLP):

  • Whitelist commands with absolute paths: specify the full path like /usr/bin/systemctl. Just systemctl can be bypassed via PATH manipulation.
  • Pin the arguments too: lock it down to the target service, e.g. NOPASSWD: /usr/bin/systemctl restart myapp.
  • Split by service in sudoers.d: don't dump everything in one file; split by role (deploy, backup) for management and audit.

Always verify the actual grants with sudo -l -U deploy.

One war story: Years ago on-call, I slapped in NOPASSWD:ALL thinking "we'll tighten it later." That temporary setting got flagged in a security audit six months later. Scoping the command from the start is actually the fastest path. These days the standard is to version-control sudoers with Ansible/Terraform as IaC, and turn on audit logging (/var/log/sudo.log) at the same time.

Conclusion: 5-line recovery checklist

  1. Confirm where it blew up using the diagnosis table (cron/systemd/CI/Ansible)
  2. Add command-scoped NOPASSWD with sudo visudo -f /etc/sudoers.d/<role>
  3. Verify permissions and syntax with chmod 0440 + visudo -c
  4. Confirm actual grants with sudo -l -U <user>
  5. grep for leftover requiretty; comment it out if present

Prevent recurrence: Version-control sudoers as IaC, and add a visudo -c syntax-check gate to the CI pipeline. That stops a single bad sudoers line from locking the whole server.

FAQ

Q. Why doesn't this error show up on GitHub Actions? A. GitHub-hosted runners already grant passwordless sudo to the runner user. On a self-hosted runner, you have to set NOPASSWD in /etc/sudoers.d/ yourself to avoid this error.

Q. Doesn't removing requiretty weaken security? A. requiretty only forces a tty; it does not strengthen authentication. Its main effect is blocking automation. Get your security from a command whitelist, least privilege, and audit logging—not from requiretty.

Q. Should I use NOPASSWD or SUDO_ASKPASS? A. Prefer command-scoped NOPASSWD. The password is never stored anywhere, so it's safer. SUDO_ASKPASS can leak the password into processes and environment variables; treat it as a last resort.

✦ ✦ ✦
Sources and verification

Sources and checks vary by article. Only articles with a recorded check date, scope and sources below were checked, and only within that scope. For other articles, verify against the current docs for your version before applying anything, and email us about errors. Editorial policy · email us

This article has no recorded human check (date and scope) yet.

Published ·
Topic-related official docs (auto-linked · not per-claim sources)OWASP documentation ↗Ansible documentation ↗

Comments

Be the first to comment.