The sudo (superuser do) tool allows designated users to run commands with elevated privileges without sharing the root password. This is fundamental to the principle of least privilege: each administrator gets exactly the access they need for their role, no more. When access is misconfigured — for example with blanket ALL=(ALL) ALL entries or unsafe NOPASSWD directives — sudo becomes a privilege escalation path rather than a control. On RHEL 9, the sudoers configuration system is mature and powerful, supporting per-user rules, per-group rules, command aliases, runas aliases, host aliases, audit logging, and per-command password requirements.

This guide covers configuring sudo on RHEL 9: using visudo safely, adding users to the wheel group, writing fine-grained sudoers rules, using the /etc/sudoers.d/ drop-in directory, configuring password-less sudo for specific commands, setting up sudo audit logging, and avoiding common misconfigurations.

Prerequisites

  • RHEL 9 server with root access (you need root to configure sudoers)
  • At least one non-root user account to grant sudo access

Step 1 — Understand How sudo Works on RHEL 9

On RHEL 9, members of the wheel group have full sudo access by default. This is configured in /etc/sudoers:

## Allows people in group wheel to run all commands
%wheel  ALL=(ALL)       ALL

When a user runs sudo command, sudo checks the sudoers rules in order and grants or denies the request based on the first matching rule.

Step 2 — Add a User to the wheel Group

The simplest way to grant full sudo access is to add the user to wheel:

usermod -aG wheel adminuser

The change takes effect on next login. Verify:

groups adminuser
# or
id adminuser

Test as that user:

su - adminuser
sudo whoami    # Should print: root

Step 3 — Always Edit sudoers with visudo

Never edit /etc/sudoers directly with a text editor. A syntax error in sudoers can lock all users out of sudo and prevent recovery without booting to rescue mode. visudo validates the syntax before saving:

visudo

To use a different editor:

EDITOR=nano visudo

Step 4 — Grant Full sudo Access to a Specific User

Add directly in /etc/sudoers (using visudo):

adminuser  ALL=(ALL)  ALL

Rule format: USER HOST=(RUNAS_USER:RUNAS_GROUP) COMMAND

  • USER: who this rule applies to (%groupname for a group)
  • HOST: which host the rule applies to (ALL means any host)
  • RUNAS_USER: which user to run as (ALL means any user, typically root)
  • COMMAND: what commands are allowed (ALL means any command)

Step 5 — Grant Limited sudo Access

Best practice is to grant only the specific commands a user needs. Use sudoers drop-in files in /etc/sudoers.d/ to keep rules organised:

visudo -f /etc/sudoers.d/webteam
# Allow webteam members to restart Nginx and Apache only
%webteam  ALL=(ALL)  /usr/bin/systemctl restart nginx, /usr/bin/systemctl restart httpd
%webteam  ALL=(ALL)  /usr/bin/systemctl reload nginx, /usr/bin/systemctl reload httpd
%webteam  ALL=(ALL)  /usr/bin/systemctl status nginx, /usr/bin/systemctl status httpd
# Allow dbadmin to run only database backup script
dbadmin  ALL=(ALL)  /opt/scripts/backup-db.sh
# Allow appuser to restart only their application
appuser  ALL=(ALL)  /usr/bin/systemctl restart myapp, /usr/bin/systemctl stop myapp, /usr/bin/systemctl start myapp

Step 6 — Configure Password-Less sudo for Specific Commands

Some automation scenarios require running a command via sudo without interactive password entry. Restrict NOPASSWD to specific commands, never to ALL:

visudo -f /etc/sudoers.d/deploy
# CI/CD deployment user can restart the app without a password
deploy  ALL=(ALL)  NOPASSWD: /usr/bin/systemctl restart myapp
deploy  ALL=(ALL)  NOPASSWD: /usr/bin/systemctl reload nginx

Never use deploy ALL=(ALL) NOPASSWD: ALL — this gives root equivalent access without any authentication barrier.

Step 7 — Use Command Aliases for Cleaner Rules

Command aliases group related commands for readable policy definitions:

visudo
# Define aliases
Cmnd_Alias NETWORKING = /usr/sbin/ip, /usr/bin/nmcli, /usr/sbin/ifconfig
Cmnd_Alias SERVICES = /usr/bin/systemctl start *, /usr/bin/systemctl stop *, /usr/bin/systemctl restart *, /usr/bin/systemctl reload *
Cmnd_Alias STORAGE = /usr/sbin/lvm, /usr/sbin/fdisk, /usr/bin/parted
Cmnd_Alias USERTOOLS = /usr/sbin/useradd, /usr/sbin/usermod, /usr/sbin/userdel

# Apply to a group
%netadmin  ALL=(ALL)  NETWORKING
%sysadmin  ALL=(ALL)  SERVICES, STORAGE
%useradmin ALL=(ALL)  USERTOOLS

Step 8 — Configure sudo Audit Logging

By default, sudo logs to /var/log/secure and via syslog. To also log the full command output (useful for compliance), install sudo-logsrvd or configure the IO log:

visudo
# Enable IO logging for all commands
Defaults  log_input, log_output
Defaults  iolog_dir="/var/log/sudo-io/%{user}/%{command}"

View the audit log for all sudo activity:

grep sudo /var/log/secure | tail -20

Or use auditd for more detailed tracking:

auditctl -w /usr/bin/sudo -p x -k sudo_exec
ausearch -k sudo_exec -ts today

Step 9 — Prevent Privilege Escalation via sudo

Be aware of commands that can be used to escape to a full shell even when granted only for a specific purpose:

# DANGEROUS — these commands allow shell escape and should never be in sudoers:
# vim, nano, less, more, man, find, awk, python3, perl, bash, sh

# To prevent shell escapes, explicitly deny dangerous commands
# Cmnd_Alias SHELLS = /usr/bin/bash, /usr/bin/sh, /usr/bin/zsh
# Deny_Cmnd SHELLS

# Only allow specific, absolute paths — no wildcards at the end of paths
# GOOD: /usr/bin/systemctl restart nginx
# BAD:  /usr/bin/systemctl *  (allows "sudo systemctl start bash")

Step 10 — Configure sudo Timeout (Credential Caching)

By default, sudo remembers your password for 5 minutes. Adjust this:

visudo
# Set sudo password cache to 10 minutes
Defaults  timestamp_timeout=10

# Require password each time (no caching)
Defaults  timestamp_timeout=0

# Per-user override
Defaults:adminuser  timestamp_timeout=30

Verification Checklist

# List all sudo rules (requires sudo itself)
sudo -l

# List sudo rules for a specific user (as root)
sudo -l -U adminuser

# Check which groups a user is in
id adminuser

# Test that syntax is valid
visudo -c

# Check drop-in files syntax
visudo -c -f /etc/sudoers.d/webteam

Troubleshooting

  • sudo: command not found — sudo is not in PATH. Use the full path /usr/bin/sudo or check echo $PATH.
  • user is not in the sudoers file — add to wheel group with usermod -aG wheel username then log out and in again.
  • sudo asks for password even with NOPASSWD — ensure the NOPASSWD rule is the last matching rule. Later rules override earlier ones. Verify with sudo -l -U username.
  • Locked out of sudo due to syntax error — boot to rescue mode or use the console. Run visudo -c to find the error, correct it, and reboot.

Security Considerations

  • Grant least privilege — give users only the commands they actually need.
  • Audit sudo usage regularly: grep sudo /var/log/secure | grep -i "command".
  • Review sudoers files whenever staff leave. Remove or disable their entries.
  • Never grant sudo access to text editors, shell interpreters, or file managers — they all allow full shell escape.

Conclusion

Your RHEL 9 server now has a properly configured sudo policy: users are added to the wheel group for full access, drop-in files in /etc/sudoers.d/ provide fine-grained per-team rules, and audit logging captures all sudo activity. Sudo is properly restricted to prevent privilege escalation through editor escape paths.

Next steps: How to Perform a System Security Audit with auditd on RHEL 9, How to Configure SELinux on RHEL 9, and How to Set Up SSH Key-Based Authentication on RHEL 9.