Skip to content

Root and sudo: Controlling Administrative Authority

Managing a server requires elevated authority at times, but running an entire session as root magnifies the effect of every typo, every wrong variable, and every compromised program. sudo turns administration from an all-or-nothing state into a policy that can be scoped to a specific user, a target account, a host, and a command.

umask completed the picture of how permission bits are decided for a process; this article covers the identity that process runs under in the first place. You will separate root, UID 0, /root, and /; choose between su, sudo, sudo -u, sudo -i, and sudo -s for a given task; use sudo -l, -v, -k, and -- in practice; understand why shell redirection happens before sudo even starts; read the user, host, RunAs, and command parts of a sudoers rule; validate a drop-in file syntactically with visudo; and weigh how a single allowed command can still hide broad capability, using the sudo log to see what was actually run.

Warning

Placeholders such as <username> and <command> below are not literal values — replace them with a verified value before running anything. Do not copy a command containing < or > straight into a shell; those characters have shell redirection meaning.

What Root Is, and What It Is Not

root is the name usually given to the administrator account with UID 0. What matters to the kernel is not the name but the numeric UID and the process's capabilities. Linux splits the traditional superuser's abilities into separate capabilities, but a process running as UID 0 typically still holds a broad set that can bypass most file permission checks.

Three similar-looking terms should not be confused:

  • root: the account, usually UID 0.
  • /root: that account's home directory.
  • /: the root of the file system tree.

Check the current process's identity with a command, not with the # shown in the prompt:

id
id -u
whoami
printf 'HOME=%s PWD=%s\n' "$HOME" "$PWD"

A representative result for an ordinary user:

uid=1000(ali) gid=1000(ali) groups=1000(ali),27(sudo)
1000
ali
HOME=/home/ali PWD=/home/ali

If id -u returns 0, the current process is running as root. groups=...sudo may indicate membership in Ubuntu's broad administrator group, but exactly which commands are actually allowed is determined by sudo -l.

Auditing UID 0 Accounts

Check local /etc/passwd for UID 0 entries, read-only:

awk -F: '$3 == 0 {printf "user=%s uid=%s home=%s shell=%s\n", $1, $3, $6, $7}' /etc/passwd

Typical result:

user=root uid=0 home=/root shell=/bin/bash

An unexpected second UID 0 account has the same classic superuser identity as root. Do not remove it without first understanding why it exists — it could be tied to a recovery mechanism, an appliance, or a directory service policy. /etc/passwd only shows local entries; if there are centralized NSS sources, audit those with your identity team as well.

Note

On Ubuntu Server, the root password is locked by default and the first user is usually added to the sudo group. On the RHEL family, the administrator group is often wheel; root password and login policy depend on the installation choices made. Do not treat a locked password as proof that every access path — including an SSH key, console, or recovery mode — is also closed.

su and sudo Do Not Do the Same Job

su starts a shell or command under the target user's identity. When an ordinary user runs su, authentication usually relies on the target account's own password and PAM policy:

su [options] [-] [user [argument...]]

To get a full login environment:

su --login <username>

--login clears most of the environment, sets HOME, SHELL, USER, and LOGNAME, PATH to match the target account, moves to its home directory, and starts a login shell. A plain su <username> keeps part of the current directory and environment, and can mix the two accounts' environments together.

sudo instead asks a policy plugin: "can this caller run exactly this command as this target?" The default target user is root, but -u selects another account:

sudo [options] [command [argument...]]
sudo -u user [options] [command [argument...]]

For example, checking a file under the service's own account rather than as root:

sudo -u <service_account> -- id
sudo -u <service_account> -- test -r <file_path>
echo $?

Here sudo is not used to elevate privilege at all — it is used to drop from an administrator session down to a less-privileged target account for testing. -- marks everything after it as the command, not a sudo option.

Need Common tool
one administrative command sudo -- <command>
one test under a different account sudo -u <user> -- <command>
working inside the target user's login environment sudo -iu <user> or su --login <user>
controlled, repeated interactive recovery as root sudo -i, followed by exit
a different account from within a root-run script often runuser, or the service manager

sudo su - is usually an unnecessary extra layer: sudo opens su as root, and su opens another root shell on top of that. It becomes harder to see and audit which policy applied to whom. Express the goal directly with sudo -i or sudo -u instead, when possible.

PAM: The Authentication Policy Behind sudo and su

Both su and sudo hand the actual "is this password correct, and is this account even allowed to authenticate right now" decision to Pluggable Authentication Modules (PAM), rather than checking /etc/shadow themselves. This is why the same sudo binary can, depending on the server's PAM configuration, check a local password, an LDAP directory, a hardware token, or reject a login outright after too many failures — the policy lives outside the program that calls it.

/etc/pam.d/: One File per Service, Stacked Rules

Each service that authenticates users has its own file under /etc/pam.d//etc/pam.d/sudo, /etc/pam.d/su, /etc/pam.d/sshd, and so on:

cat /etc/pam.d/sudo
#%PAM-1.0
session    required   pam_limits.so
session    required   pam_env.so readenv=1 user_readenv=0
@include common-auth
@include common-account
@include common-session-noninteractive

@include common-auth pulls in a shared file so the same authentication policy does not have to be duplicated across every service. Each line inside has the shape:

type    control    module    [arguments]
  • type is the phase: auth (verify identity), account (is the account allowed — expired, locked, time-of-day restricted), password (changing a credential), or session (setup/teardown around the session, such as pam_limits.so above).
  • module is the .so file that does the actual check, such as pam_unix.so (the traditional /etc/shadow check) or pam_faillock.so.
  • control decides how this line's result affects the overall stack outcome:
Control flag Behavior on failure Behavior on success
required Records the failure but keeps evaluating the rest of the stack; the overall result still fails at the end Continues to the next line
requisite Fails immediately, stops the stack right there Continues to the next line
sufficient Ignored — moves to the next line as if this one were absent, unless nothing above it already failed Immediately succeeds, skipping the rest of the stack
optional Generally ignored unless it is the only module of that type in the stack Generally ignored unless it is the only module of that type in the stack

The distinction between required and requisite matters for one specific reason: required deliberately keeps running the remaining checks even after a failure, so an attacker probing the system cannot tell from timing or behavior which exact check failed first. requisite trades that away for an immediate stop, which is appropriate when continuing would be pointless or risky (for example, once an account is confirmed locked).

pam_faillock: Locking Out Repeated Failed Logins

pam_faillock is the standard OS-level answer to "how do you stop repeated password-guessing at the login prompt" — it counts consecutive authentication failures for an account within a time window and locks it once a threshold is reached, independent of anything the network layer (like fail2ban watching SSH logs) might also be doing.

RHEL-family systems enable pam_faillock by default through authselect; on Ubuntu it ships with the libpam-modules package but is not wired into common-auth out of the box, so it must be added explicitly — either with sudo pam-auth-update (if a Faillock profile is offered) or by editing the PAM stack directly. Either way, the resulting stack threads pam_faillock around pam_unix in common-auth (RHEL-family: system-auth) in this shape:

auth    required                                     pam_faillock.so preauth
auth    [success=1 default=ignore]                   pam_unix.so nullok
auth    [default=die]                                pam_faillock.so authfail
auth    required                                      pam_faillock.so authsucc
  • preauth: runs before the password check, and refuses immediately if the account is already locked.
  • After pam_unix.so checks the password, authfail records a new failure (only reached if pam_unix.so did not already succeed), and authsucc clears the failure counter on a successful login.

The tuning that matters most lives in /etc/security/faillock.conf (or as module arguments directly):

grep -E '^(deny|fail_interval|unlock_time)' /etc/security/faillock.conf 2>/dev/null
  • deny: how many consecutive failures lock the account.
  • fail_interval: the rolling time window those failures must fall within to count.
  • unlock_time: how long the lock lasts before it clears automatically (0 means it stays locked until an administrator intervenes).

Checking and clearing a lockout, read-only first:

sudo faillock --user <username>
<username>:
When                Type  Source                                           Valid
2026-07-29 09:14:02 RHOST 203.0.113.10                                     V
2026-07-29 09:14:05 RHOST 203.0.113.10                                     V

Each row is one recorded failure, with its source address where available. Once the cause is understood — for example, a genuine forgotten password rather than an active attack — clear the lock explicitly:

sudo faillock --user <username> --reset

Danger

pam_faillock is a two-edged control: a misconfigured deny value, or an attacker who knows a valid username but not the password, can lock out a legitimate administrator. Always keep console access or a second, already-authenticated session available before changing PAM stacks, exactly as with sudoers, and test the change on a non-production host first.

Interview angle

"How do you stop SSH brute-force at the OS level" has two complementary answers, and naming only one is an incomplete answer in an interview: pam_faillock locks the account itself after too many bad passwords, regardless of source; a network-layer tool such as fail2ban blocks the offending IP address after watching log patterns. The two solve different failure modes — a distributed attack from many IPs against one account defeats fail2ban's per-IP banning but not pam_faillock's per-account lockout, while a slow, patient attacker rotating usernames can stay under pam_faillock's per-account threshold but still gets caught by IP-based banning.

A Safe Daily Workflow with sudo

Check the Policy First

sudo --version
sudo -l

sudo --version shows the implementation and version; sudo -l shows the current user's permitted commands. sudo -l may prompt for a password; typically the caller enters their own, but PAM and sudo policy can change that behavior.

A representative, shortened result:

User ali may run the following commands on web01:
    (root) /usr/bin/systemctl reload catalog.service
    (www-data) /usr/bin/php /srv/catalog/bin/health.php

The first rule allows ali to reload catalog.service as root only; the second allows running a specific PHP health check as www-data. A rule like (ALL) ALL grants very broad authority in practice.

Checking whether a specific command matches policy, without running it:

sudo -l /usr/bin/systemctl reload catalog.service
echo $?

0 means the policy matches; a non-zero status means either no permission or an authentication error. This command does not reload the service.

Manage the Timestamp

After successful authentication, sudo can keep a credential timestamp for a period of time:

sudo -v

-v refreshes the timestamp without running a command, and may prompt for a password. Once administrative work is finished:

sudo -k

-k invalidates the current credential timestamp; the next sudo normally requires re-authentication. -K can be used to remove the timestamp record entirely, but -k is enough when closing a routine session.

For automation that should not hang at a prompt, non-interactive mode is available with -n:

sudo -n -l
echo $?

Without a valid credential or a NOPASSWD policy, -n fails immediately instead of prompting. Writing the password into a script, or hiding it with echo password | sudo -S ..., is not a safe form of automation.

Prefer One Command Over a Root Shell

sudo -- systemctl status <service_name>.service

sudo here grants elevated authority only to the systemctl process. sudo -i instead opens a login root shell, running everything that follows at elevated authority:

sudo -i
id
pwd
exit

sudo -s also opens a target shell, but does not fully imitate a login environment; the shell and environment depend more on the current settings. Recovery, or several connected actions, may genuinely require a root shell, but prepare a snapshot/console, a clear checklist, and an exit criterion first.

Warning

Inside sudo -i, the prompt often shows #, but keep checking id, pwd, and the target before every important step regardless. Do not leave a root shell open for unrelated work; run exit and sudo -k once finished.

What the Shell Does Before sudo Runs

Shell redirection, globbing, and variable expansion all happen before sudo ever starts. Because of that:

sudo printf '%s\n' 'value' > /etc/<app>/app.conf

commonly fails: printf can be root, but the plain, unprivileged current shell is the one that opens /etc/... for the > redirection.

If the goal is installing one finished file with a safe mode, a purpose-built tool is clearer:

sudo install -o root -g <service_group> -m 0640 \
    <verified_source_file> /etc/<app>/app.conf

sudo tee can be used to write a text stream:

printf '%s\n' 'value' | sudo tee /etc/<app>/app.conf >/dev/null

tee truncates and replaces the existing file's content; a secret can end up in terminal history or process arguments. In production, know the current file, the backup, a syntax check, and an atomic deployment method before doing this.

sudo sh -c '...' avoids the redirection problem, since the shell itself becomes root, but it also grants broad authority to quoting, globbing, and every command inside the string. If a single, specific install, tee, cp, or service tool is enough, do not reach for a root shell string.

Environment and Command Resolution

sudo usually filters the environment according to policy, dropping dangerous variables, and can replace PATH with a setting such as secure_path. So an alias, a function, or a virtual environment found in a plain shell can naturally fail to work under sudo.

type -a <command>
command -v <command>
sudo -- sh -c 'printf "user=%s home=%s path=%s\n" "$USER" "$HOME" "$PATH"'

The last command opens a root shell purely for diagnostics; its output shows the target environment. In a production sudoers rule, write the executable with an absolute path. Do not let root code run through an untrusted current directory, a user-writable script, or a modifiable interpreter/import path.

Options that request preserving the full environment, and the SETENV sudoers tag, can widen this protective boundary. Allowlisting only the variables actually needed is safer. The exact behavior depends on the implementation and policy — check with sudo --version, sudo -V, or the local man sudoers.

Editing a System File with sudoedit

sudo vim /etc/<app>/app.conf runs the editor as root. Many editors can open a shell, load a plugin, or run an external command — considerably more authority than the editing task itself needs.

sudoedit, or sudo -e, uses a dedicated flow for editing instead:

sudoedit /etc/<app>/app.conf
# the same action:
sudo -e /etc/<app>/app.conf

Sudo normally creates a temporary copy of the file, runs the editor as the calling user, and copies the edited version back to the protected location afterward. Policy can restrict sudoedit and its paths specifically.

Warning

sudoedit does not protect against a syntax mistake or a logically wrong configuration. Take a backup first, then run the application's own --test/config-check tool, and only then reload. A symlink, a user-writable parent directory, or a wildcard sudoedit rule each deserve their own risk analysis.

Reading sudoers Policy

The main policy usually lives in /etc/sudoers, with additional rules under /etc/sudoers.d/. A simplified rule:

USER HOST = (RUNAS_USER:RUNAS_GROUP) TAGS: COMMAND

For example:

deploy ALL=(root) /usr/bin/systemctl reload catalog.service

The parts:

  • deploy: the caller who gets the authority.
  • ALL: the hosts this rule applies to — not "all commands."
  • (root): the target user the command runs as.
  • /usr/bin/systemctl reload catalog.service: the specific allowed executable and arguments.

A group is written with %:

%catalog-ops ALL=(root) /usr/bin/systemctl reload catalog.service

Using ALL in the command field grants authority close to full administration. NOPASSWD removes the authentication barrier entirely; it should only be justified for a controlled automation account, a specific command, and other compensating controls.

An Allowed Command Can Still Be Broad

Sudoers seeing an executable's name does not sandbox everything that command can do:

  • a shell, an interpreter, or env can run almost arbitrary code;
  • an editor or pager may open a shell escape;
  • a package manager can run hooks or scripts;
  • tools like cp, mv, chmod, chown can replace a critical file and indirectly lead to root;
  • how safe a service reload is depends on who can write to the unit, ExecReload, an environment file, or loaded configuration;
  • a wildcard argument may unintentionally match an unexpected space or option.

So "just one binary" is not always the least privilege. Audit the command's arguments, the files it reads, its plugins/hooks, and any user-writable parent directories together.

Safe Validation with visudo

Opening /etc/sudoers in a plain editor bypasses locking and syntax checking. visudo locks the file and checks its syntax before saving:

sudo visudo
sudo visudo -f /etc/sudoers.d/<rule_name>

A new rule can be checked before it is ever installed on the system. This lab does not touch /etc:

mkdir -p ~/lab/root-and-sudo
candidate=~/lab/root-and-sudo/catalog-deploy

printf '%s\n' \
    'deploy ALL=(root) /usr/bin/systemctl reload catalog.service' \
    > "$candidate"
chmod 0600 "$candidate"
visudo -cf "$candidate"
echo $?

The result depends on the implementation but resembles:

/home/ali/lab/root-and-sudo/catalog-deploy: parsed OK
0

If visudo points out a bad line, fix the candidate; do not install it into /etc/sudoers.d/. Confirm /usr/bin/systemctl is a real path on the target server with command -v systemctl and the appropriate package ownership tool.

Note

Starting with Ubuntu 25.10, the default sudo is sudo-rs, written in Rust; 26.04 LTS also supports the classic Sudo project under the sudo.ws/visudo.ws names. Everyday usage is similar, but compatibility is not complete. RHEL 9 normally uses classic Sudo. For complex sudoers syntax, read the actual server's implementation man page and validate with that same visudo.

Practical Scenario: Only One Reload for the Deploy Account

Requirement: the deploy user can reload catalog.service, but must not restart any other service and must not obtain a root shell. The application's configuration is verified separately, before the reload.

This is a risky production change to sudoers. Keep a console or a second verified administrator session open. Do not proceed without a VM snapshot/configuration management and a clear rollback plan.

1. Check the Account, the Command, and Hidden Dependencies

getent passwd deploy
command -v systemctl
systemctl show -p FragmentPath -p DropInPaths catalog.service
systemctl cat catalog.service

Check the ownership and mode of the FragmentPath, drop-ins, whatever script ExecReload calls, and the service's environment files:

unit_path=$(systemctl show -p FragmentPath --value catalog.service)
stat -c '%A %a %U:%G %n' -- "$unit_path" /usr/bin/systemctl
namei -l "$unit_path"

If deploy, or a group it controls, can write to the unit, the reload script, or a configuration file root reads, then even a rule limited to systemctl reload can become an indirect path to running code as root. Fix that ownership first, using Permission Security.

2. Current Policy and a Rollback Point

sudo -v
sudo visudo -cf /etc/sudoers
sudo test ! -e /etc/sudoers.d/catalog-deploy
echo $?

If the last status is 0, the name is free. If the file already exists, do not overwrite it: view its content with sudo visudo -f, find its configuration management source, and plan a separate backup.

For the new rule, take at least a backup of the main sudoers file into a protected location:

sudo cp -a /etc/sudoers /root/sudoers.before-catalog-deploy
sudo stat -c '%A %U:%G %n' /root/sudoers.before-catalog-deploy

Your organization's backup policy should also cover /etc/sudoers.d/ and any central policy source.

3. Build and Validate the Candidate Offline

candidate=$(mktemp)
printf '%s\n' \
    'deploy ALL=(root) /usr/bin/systemctl reload catalog.service' \
    > "$candidate"
chmod 0600 "$candidate"
visudo -cf "$candidate"

The candidate is in an ordinary user directory, so do not store a secret in it. Do not proceed unless the result shows parsed OK and exit status 0.

4. Install the Drop-In and Validate the Whole Policy

sudo install -o root -g root -m 0440 \
    "$candidate" /etc/sudoers.d/catalog-deploy
sudo visudo -cf /etc/sudoers

install sets the new file's owner, group, and mode in one step. /etc/sudoers needs an include directive that reads the drop-in directory. In classic Sudo, a drop-in's filename must not contain a . and must not end with ~.

If global validation fails, remove the rule from the include directory from an already-open second administrator session:

sudo mv /etc/sudoers.d/catalog-deploy /root/catalog-deploy.disabled
sudo visudo -cf /etc/sudoers

This rollback does not delete the new rule outright — it moves it to /root for later review. If you were updating an existing file instead, restore the specific backup taken earlier.

5. Test Policy Matching Without Running the Command

In a new login session for deploy:

sudo -l /usr/bin/systemctl reload catalog.service
echo $?
sudo -l /usr/bin/systemctl restart catalog.service
echo $?

The first status is expected to be 0, the second non-zero. This test does not run either command. If the result differs, look for a broader grant coming from another sudoers rule, using sudo -ll and the include files.

6. A Controlled Reload and Verification

First run the application's official config-check command as an ordinary or appropriate service account. Only inside a confirmed maintenance window:

sudo /usr/bin/systemctl reload catalog.service
echo $?
systemctl is-active catalog.service

Danger

A reload sends a signal to the service or runs ExecReload with elevated authority; a bad configuration can cause an outage. Have a test host, a config check, monitoring, and a service-specific rollback ready. If reload is not supported, do not blindly substitute restart for it.

7. Audit and Cleanup

journalctl _COMM=sudo --since today
rm -i -- "$candidate"

Depending on the sudo implementation and log configuration, entries can live in the journal, /var/log/auth.log on Ubuntu, /var/log/secure on the RHEL family, a dedicated logfile, or a central log server. A plain sudo log shows who requested which command and when — do not assume it fully records every system call, output, or file change the command produced. I/O logging requires a separate policy.

Common Mistakes

Doing Everyday Work in a Root Shell

A root shell widens the blast radius of a mistake to the entire session. If the task is one command, write an explicit sudo -- command; invalidate the timestamp with sudo -k once finished.

Assuming sudo Asks for root's Password

Default sudo policy commonly checks the caller's own password. su normally relies on the target's password instead. PAM and policy can change this, so read the prompt text rather than guessing.

Assuming Restricting the Binary Name Is Enough

An interpreter, editor, package manager, or user-writable script behind one permitted command can lead to arbitrary root action. Check arguments, imports, hooks, and file ownership too.

Editing sudoers with a Plain Editor

A syntax error can lock administrators out of sudo entirely. visudo or visudo -f provides locking and parser checking; prepare a second admin session and a rollback in advance regardless.

Granting ALL and NOPASSWD for Convenience

ALL removes the command boundary; NOPASSWD removes the authentication barrier. Automation still needs a dedicated account, a specific command and arguments, a host restriction, logging, and credential protection.

Expecting sudo to Make Redirection Root Too

> and >> are executed by the current shell. Use a purpose-built command such as install or tee; do not open a root shell with sudo sh -c for more than the task needs.

Assuming sudo's Environment Matches a Plain Shell

Aliases, functions, PATH, HOME, and locale variables can all differ. Use an absolute executable path and only the environment actually required; do not rely on a user-writable PATH.

Assuming the sudo Log Proves Everything That Happened

The log typically records the command that was invoked, but not a full trace of every file it opened or network action it took. For anything that matters, combine I/O logging, an audit framework, change records, and application logs.

Treating an Unlocked Root Password as the Fix for a sudo Problem

Enabling a locked root password on Ubuntu increases the risk of a shared credential and direct login. Restore a broken sudoers file through console/recovery, or fix the specific policy, before reaching for that.

Exercises

  1. Build a short report of the current identity and environment from id, id -u, whoami, HOME, and PWD. Do not use the prompt character as evidence.
  2. Compare the output of sudo --version, sudo -l, and sudo -n -l. Explain which implementation is running and why the non-interactive check succeeded or failed.
  3. Compare id, HOME, PWD, and SHELL between su --login <test_user> and, if an administrator allows it, sudo -iu <test_user>. Do not open login access for a system or service account.
  4. In ~/lab/root-and-sudo, create one syntactically correct and one broken sudoers candidate. Validate both with visudo -cf; do not install either into /etc/sudoers*.
  5. Write a sudoers rule and threat model for "an operator can reload only one specific service." Cover the executable, arguments, unit, ExecReload, environment file, logging, and rollback.

Verification criterion: for every elevated action, state the caller, the target user, the exact command/arguments, the authentication method, the log source, and the rollback path.

When you finish the lab:

find ~/lab/root-and-sudo -xdev -maxdepth 2 \
    -printf '%y %m %u:%g %p\n'

If the result shows only your test candidates:

rm -rI -- ~/lab/root-and-sudo
sudo -k

Warning

rm -rI deletes the lab files with no undo mechanism. Check the read-only listing above first. sudo -k does not delete any file; it invalidates the current sudo credential timestamp.

References

  • Ubuntu Server documentation: user management, root, and sudo-rs: https://ubuntu.com/server/docs/how-to/security/user-management/
  • Ubuntu Server documentation: security suggestions: https://ubuntu.com/server/docs/explanation/security/security_suggestions/
  • Red Hat Enterprise Linux 9: managing sudo access: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/configuring_basic_system_settings/managing-sudo-access_configuring-basic-system-settings
  • Sudo manual: sudo(8): https://www.sudo.ws/docs/man/sudo.man/
  • Sudo manual: sudoers(5): https://www.sudo.ws/docs/man/sudoers.man/
  • Sudo manual: visudo(8): https://www.sudo.ws/docs/man/visudo.man/
  • util-linux manual: su(1): https://man7.org/linux/man-pages/man1/su.1.html
  • util-linux manual: runuser(1): https://man7.org/linux/man-pages/man1/runuser.1.html
  • Linux man-pages: credentials(7): https://man7.org/linux/man-pages/man7/credentials.7.html
  • Linux man-pages: capabilities(7): https://man7.org/linux/man-pages/man7/capabilities.7.html
  • Linux-PAM System Administrators' Guide: https://www.linux-pam.org/Linux-PAM-html/Linux-PAM_SAG.html
  • Linux man-pages: pam.conf(5): https://man7.org/linux/man-pages/man5/pam.conf.5.html
  • Linux man-pages: pam_faillock(8): https://man7.org/linux/man-pages/man8/pam_faillock.8.html