/보안/sudo: no tty present and no askpass — 30-Second Diagnosis and Recovery (cron, CI, Ansible)
Securitysudo 에러sudoers NOPASSWD

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)

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 확인
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 제거 후 사용 권장
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.

확인 정보
✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 · ·업데이트 ·

Comments

Be the first to comment.