Server

sudoers Done Safely

By Prabath Thalangama· October 24, 2026· 4 min read
#linux#security#sudo

Introduction

sudo grants specific elevated privileges without sharing the root password. The rules live in /etc/sudoers and /etc/sudoers.d/. A syntax error there can break sudo for everyone, so always edit with visudo — it validates before saving.

Editing

sudo visudo                       # /etc/sudoers
sudo visudo -f /etc/sudoers.d/deploy   # a drop-in (preferred)

Prefer drop-in files in /etc/sudoers.d/ — they survive package upgrades and are easy to manage with config management. The file must not have a . or ~ in the name and should be mode 0440.

Rule syntax

user    host = (runas_user:runas_group) [tags]: command [, command ...]
# alice can run anything as root, with a password
alice   ALL = (ALL:ALL) ALL

# the deploy user can restart one service without a password
deploy  ALL = (root) NOPASSWD: /usr/bin/systemctl restart myapp.service

# members of group ops can run a specific set
%ops    ALL = (root) /usr/bin/systemctl, /usr/bin/journalctl

# run as a non-root service account
ci      ALL = (appuser) NOPASSWD: /usr/local/bin/deploy.sh

%group for a group. ALL in the host field matters when one sudoers file is shared across machines.

Aliases

Cmnd_Alias SERVICES = /usr/bin/systemctl start *, /usr/bin/systemctl stop *, \
                      /usr/bin/systemctl restart *, /usr/bin/systemctl status *
User_Alias ADMINS = alice, bob, %sre
Host_Alias WEB = web01, web02, web03

ADMINS  WEB = (root) SERVICES

The wildcard trap

# LOOKS narrow, IS full root:
deploy ALL = (root) NOPASSWD: /usr/bin/systemctl restart myapp*

myapp* matches myapp.service — and also myapp.service; rm -rf / is not how it works (sudo doesn't invoke a shell), but:

deploy ALL = (root) /bin/chmod *           # chmod any file, any mode -> trivially root
deploy ALL = (root) /usr/bin/find *        # find has -exec -> arbitrary command
deploy ALL = (root) /usr/bin/vim /etc/myapp.conf   # vim has :!sh -> root shell
deploy ALL = (root) /usr/bin/less /var/log/x       # less can spawn a shell
deploy ALL = (root) /usr/bin/tar *         # --checkpoint-action=exec -> root

Rules of thumb:

  • No wildcards in arguments unless you've thought hard about it. Enumerate exact commands.
  • No editors, pagers, find, tar, awk, sed -i on arbitrary paths, language interpreters, or systemctl without a specific unit — they all have shell-escape or arbitrary-write paths.
  • To let someone edit a file as root, use sudoedit (sudo -e), not sudo vim — it copies the file, lets the user edit as themselves, then copies back.
    deploy ALL = (root) sudoedit /etc/myapp/config.yaml
    

Useful defaults

Defaults    env_reset, timestamp_timeout=15
Defaults    logfile="/var/log/sudo.log"
Defaults    lecture="once"
Defaults:ci !requiretty           # allow sudo from a non-tty (CI) for user ci
Defaults    secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

env_reset (default) scrubs the environment; keep it. secure_path prevents a planted binary earlier in $PATH from running as root.

Verification and troubleshooting

sudo -l                         # what can I run?
sudo -l -U deploy               # what can deploy run? (as an admin)
sudo -ll                        # verbose form
visudo -c                       # check syntax of all sudoers files
sudo -k                         # forget the cached timestamp (re-prompt)
  • sudo: /etc/sudoers is world writable / syntax error — you edited without visudo and broke it. Recover via a root shell (pkexec, single-user mode, su, a container/rescue), fix perms chmod 0440, visudo -c.
  • Rule not taking effect — a later file or line matches first? sudo uses the last matching rule. Order in sudoers.d is by filename (C locale sort) — name files 10-, 20- to control it.
  • sudo: no tty present and no askpass program specified — a script/CI running sudo interactively. Use NOPASSWD for that specific command, or Defaults:user !requiretty + an askpass.
  • NOPASSWD ignored — another matching rule without NOPASSWD comes later, or the command path/args don't match exactly (absolute path required; systemctl vs /usr/bin/systemctl).
  • User can escalate beyond the rule — you granted a command with a shell-escape (see the trap section). Audit every granted binary for !, -e, --exec, :sh.
  • Group change not recognised — sudo reads group membership at login; the user needs a fresh session after being added to %ops.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.