sudo and privilege
sudo is a policy engine: sudoers rules decide who may run what command as whom. visudo is the only safe editor. NOPASSWD is a deliberate tradeoff. su swaps your entire identity; sudo elevates for a single command. Least privilege: grant exactly what is needed.
A developer asks for sudo access “just to restart nginx”. You give it. Three months later you discover they have been running sudo bash to get a root shell and making undocumented changes. Or you inherit a server where every developer is in the sudoers file with ALL=(ALL:ALL) NOPASSWD:ALL — effectively root without authentication. Or an auditor flags that your CI pipeline runs as root because someone added sudo to every deployment command. Privilege management on Linux is not about whether to use sudo — it is about using it with a precision that limits the blast radius when credentials leak or humans make mistakes.
After this lesson you can write sudoers rules with the correct syntax, explain why you must only edit sudoers with visudo, articulate the difference between su and sudo, design a least-privilege grant for a common real-world task, and explain the security tradeoff of NOPASSWD.
sudo is a policy engine, not just a “run as root” button. Before executing anything, sudo reads the sudoers policy, matches the calling user + host + target user + command, and allows or denies. It also logs every invocation to syslog/journald.
# Basic use: run a single command as root
sudo systemctl restart nginx
# Run as a specific user (not root)
sudo -u postgres psql -c 'SELECT version();'
# Run as a specific user AND group
sudo -u www-data -g www-data /opt/app/run.sh
# Get a root shell (avoid this in production — log the session instead)
sudo -i # login shell: sources /root/.profile, /root/.bashrc
sudo -s # non-login shell: inherits most of the caller's environment
# See what you are allowed to do
sudo -l
# Matching Defaults entries for alice on server1:
# env_reset, mail_badpass, secure_path=...
# User alice may run the following commands on server1:
# (root) /usr/bin/systemctl restart nginx
# (root) NOPASSWD: /usr/bin/journalctl -u nginx *
# Every sudo invocation is logged
journalctl -t sudo --since '10 minutes ago'
# Jun 21 14:32:01 server1 sudo[8821]: alice : TTY=pts/0 ; PWD=/home/alice ;
# USER=root ; COMMAND=/usr/bin/systemctl restart nginxThe audit trail is one of the primary reasons to use sudo over su. su logs only the authentication; sudo logs the exact command. In a post-incident review, sudo logs tell you precisely what a user did.
sudoers syntax follows a strict pattern — and mistakes in it can lock you out of root. The basic form is who where=(runas) what.
# NEVER edit /etc/sudoers directly — always use visudo
# visudo locks the file, checks syntax before saving, and prevents two editors
sudo visudo
# The rule format:
# user host=(run_as_user:run_as_group) command
# Examples:
alice ALL=(ALL:ALL) ALL
# alice can run any command as any user/group on any host — too broad
alice ALL=(root) /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx
# alice can restart or reload nginx only
%ops ALL=(root) /usr/sbin/useradd, /usr/sbin/userdel
# the ops GROUP can manage users
# NOPASSWD: no password prompt for these commands
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp
# deploy user (e.g. a CI runner) can restart myapp without password
# Aliases for clarity in large sudoers files
Cmnd_Alias NGINX_CMDS = /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx
alice ALL=(root) NGINX_CMDS
# Drop-in files: preferred over editing the main file
# /etc/sudoers.d/alice-nginx:
alice ALL=(root) /usr/bin/systemctl restart nginx
# Included automatically if /etc/sudoers has '#includedir /etc/sudoers.d'Every rule in sudoers is additive. There is no “deny” in the traditional sense (there is a ! negation but it has security pitfalls — avoid it). If a user matches any rule, they can run that command. The first matching rule wins in some implementations; in sudo, ALL matching rules apply additively.
visudo is mandatory — never edit sudoers with a plain text editor. A syntax error in /etc/sudoers disables sudo entirely. If you also lack root SSH access, you are locked out.
# What visudo does that plain editors don't:
# 1. Acquires an exclusive lock on /etc/sudoers
# 2. Writes to a temp file, runs 'visudo -c' (syntax check) before saving
# 3. Refuses to save if syntax is invalid
# 4. Sets the correct permissions (0440) automatically
# Check syntax of a sudoers file without opening it:
sudo visudo -c -f /etc/sudoers.d/alice-nginx
# /etc/sudoers.d/alice-nginx: parsed OK
# What happens if you put a bad line in /etc/sudoers:
# sudo /usr/bin/systemctl restart nginx
# >>> /etc/sudoers: syntax error near line 42 <<<
# sudo: parse error in /etc/sudoers near line 42
# sudo: no valid sudoers sources found, quitting
# sudo: unable to initialize policy plugin
# Recovery when locked out (requires physical/console access or root SSH):
# Boot to single-user mode (systemd: add 'single' to kernel cmdline in GRUB)
# mount -o remount,rw / (if read-only)
# visudo (fix the error)The drop-in approach (/etc/sudoers.d/) exists partly for this reason: errors in a drop-in file cause sudo to print a warning but still allow rules from the main file to apply (depending on the #includedir strictness). It is also the correct pattern for configuration management tools — Ansible, Chef, and Puppet write individual files to /etc/sudoers.d/ rather than modifying the main file.
su and sudo solve different problems. Understanding the distinction shapes how you design access policies.
# su: substitute user — opens a shell as a different user
# Requires the TARGET user's password (or root's)
su - alice # login shell as alice (- means: load alice's environment)
su alice # non-login shell as alice (inherits caller's env)
su - # login shell as root (requires root password)
# sudo: run a command with elevated privileges
# Requires YOUR OWN password (the calling user's), not root's
sudo systemctl restart nginx
sudo -u alice /opt/run.sh
# Key differences:
# su: you become the user for an interactive session; logs only the su event
# sudo: one command, then back to your own identity; logs the exact command
# Why sudo won over su in multi-admin environments:
# - No need to share the root password (fatal in team settings)
# - Per-command granularity (can restart nginx, cannot delete files)
# - Full audit log per command
# - Can be revoked per-user without changing the root password
# su still has valid uses:
# - Switching to a service account to debug it in its own environment
# (sudo -u postgres psql works but doesn't load postgres's .bashrc)
# - Bootstrapping a system where sudo is not yet installed
su - postgres # loads postgres's environment, useful for pg_dump debuggingThe rule of thumb: sudo for administrative one-off commands; su -u for extended interactive sessions as a service account where you need the full environment.
Least privilege means granting exactly the access required — and thinking through the escape hatches. Common mistakes that turn a “limited” sudo grant into full root:
# BAD: text editors launched via sudo open a shell with :!bash
sudo vi /etc/nginx/nginx.conf # :!bash in vi gives root shell
sudo nano /etc/hosts # ^R command gives shell
# BAD: allowing any path means the user controls what gets run
alice ALL=(root) /usr/bin/python3 # python3 -c 'import os; os.system("bash")'
alice ALL=(root) /home/alice/scripts/ # user controls the scripts directory
# BAD: wildcard without anchoring the argument
alice ALL=(root) /usr/bin/systemctl * nginx
# covers: sudo systemctl start nginx — intended
# also covers: sudo systemctl daemon-reexec && sudo systemctl bash
# (sudoers wildcards match spaces too — this is the classic bypass)
# GOOD: specific commands with specific arguments
alice ALL=(root) /usr/bin/systemctl restart nginx, \
/usr/bin/systemctl reload nginx, \
/usr/bin/systemctl status nginx
# GOOD: use sudoedit for file editing — it copies to a temp file, edits as
# the calling user's editor, then copies back — no shell escape possible
alice ALL=(root) sudoedit /etc/nginx/nginx.conf
# GOOD: least-privilege service restart pattern for CI
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp.service
# NOPASSWD is acceptable here: deploy is a service account, not a human;
# it has no interactive login; the command is specific; secret rotation
# is handled at the API key level, not the sudo password levelThe NOPASSWD tradeoff: it removes the authentication factor, so the only thing protecting the grant is the secrecy of the credentials used to become the deploy user. Acceptable for a dedicated CI service account with a narrowly scoped command; never acceptable for a human developer’s account.
Writing a least-privilege sudo grant for a deployment pipeline.
Requirement: a CI/CD pipeline running as user deploy needs to restart the myapp.service systemd unit and rotate a log file. It must not require a password (unattended). It must not be able to do anything else as root.
# Step 1: identify the exact commands needed
# - systemctl restart myapp.service
# - mv /var/log/myapp/app.log /var/log/myapp/app.log.1
# Step 2: check for shell-escape risks
# systemctl: no shell escape risk for restart subcommand
# mv: limited path — but we must lock it to specific paths
# Step 3: write the drop-in file via visudo
sudo visudo -f /etc/sudoers.d/deploy-myapp
# Content:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp.service
deploy ALL=(root) NOPASSWD: /bin/mv /var/log/myapp/app.log /var/log/myapp/app.log.[0-9]*
# Step 4: verify syntax
sudo visudo -c -f /etc/sudoers.d/deploy-myapp
# /etc/sudoers.d/deploy-myapp: parsed OK
# Step 5: test as the deploy user
sudo -u deploy sudo -l
# User deploy may run the following commands on server1:
# (root) NOPASSWD: /usr/bin/systemctl restart myapp.service
# (root) NOPASSWD: /bin/mv /var/log/myapp/app.log /var/log/myapp/app.log.[0-9]*
sudo -u deploy sudo systemctl restart myapp.service
# (success — no password prompted)
sudo -u deploy sudo systemctl restart nginx
# Sorry, user deploy is not allowed to execute '/usr/bin/systemctl restart nginx'The wildcard [0-9]* in the mv rule allows log rotation with numeric suffixes while preventing the deploy user from moving the log file to arbitrary paths like /etc/cron.d/evil.
▸Common mistake
Never grant sudo access to commands that launch a text editor, a pager, or a shell directly. The classic bypass: sudo less /var/log/syslog → type !bash → root shell. The same applies to vim, man, awk, python3, perl, find -exec, and many other commands that can spawn subprocesses. The safe alternative for file editing is sudoedit (or sudo -e), which edits a copy as the calling user’s own process — no shell escapes possible. If a user “needs sudo” for a task that is really about reading a file, consider giving them read permission on the file directly instead.
A developer needs to edit /etc/nginx/nginx.conf as root. Which sudoers grant is safest?
sudo is a policy engine: it reads sudoers rules, matches user + host + target + command, logs the result, and optionally authenticates. The rule format is who where=(runas) command. visudo is the only safe editor — it syntax-checks before saving. /etc/sudoers.d/ is the right place for drop-in rules (config management friendly). NOPASSWD removes the authentication factor — acceptable for dedicated service accounts with narrow command grants, never for human developers. su swaps your entire identity (needs the target’s password); sudo elevates for one command (needs your own password) and logs the exact command. Least privilege: grant specific commands with specific arguments; avoid editors, shells, and commands with unrestricted argument wildcards; use sudoedit for file-editing grants.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.
Apply this
Put this lesson to work on a real build.