"How do I add a user to sudoers?" is one of the first questions asked on any new AlmaLinux or Rocky Linux box — usually right after you've created the non-root account you actually plan to log in as. The literal answer is a single command. The useful answer depends on what you actually want: full sudo with a password, full sudo without one, a narrow grant for a specific command, or a service-account grant that can only run one script. This guide walks through every common shape of that grant on RHEL-derived distros, the safe way to edit sudoers, how to verify the grant works, and how to avoid the classic mistakes that lock you out of the box.
Both AlmaLinux and Rocky Linux are RHEL rebuilds, so everything below applies identically to both — and to RHEL, CentOS Stream, and Oracle Linux 8/9. The commands assume version 8 or 9; the only behavioral differences from older RHEL/CentOS 7 are noted inline.
The wheel group: the canonical answer
On AlmaLinux and Rocky Linux, the group that grants sudo is wheel. The default /etc/sudoers already contains the line that lets wheel members run anything with a password:
%wheel ALL=(ALL) ALL
So adding a user to sudoers is, in the common case, just adding them to wheel:
sudo usermod -aG wheel alice
That's it. After Alice logs out and back in (or starts a new shell), she can run sudo for any command and be prompted for her own password.
Two details matter:
-ais not optional.usermod -G wheel alice(without-a) replaces Alice's group list with justwheel, dropping her from every other group she's in. Always use-aG.- The membership change requires a fresh login. Group membership is read at session start.
id alicewill show the new group immediately, but Alice's existing shell still has the old group set. New SSH sessions, newsu -invocations, and newsudocalls pick up the change.
To verify:
id alice
# uid=1001(alice) gid=1001(alice) groups=1001(alice),10(wheel)
# Or just check group membership
getent group wheel
# wheel:x:10:alice
Why "wheel"? It's a Unix tradition — the group named
wheel(originally "big wheels", as in important people) has carried the "can become root" privilege on BSD and System-V-derived Unixes for decades. Debian/Ubuntu usesudoas the equivalent group name; RHEL/CentOS/Rocky/Alma usewheel. Functionally identical, just a different name.
What "sudo" actually does, briefly
A quick mental model that prevents most mistakes:
sudo <command>runs<command>as root by default (or as another user with-u).- Authorization is decided by
/etc/sudoers(and any file dropped into/etc/sudoers.d/). - By default,
sudoasks for the invoking user's password, not root's. (If your password isn't working, you may be thinking ofsu, which asks for root's.) - After a successful auth, sudo caches the credential for ~5 minutes (the
timestamp_timeoutdefault), so subsequentsudocalls in the same session don't re-prompt. - On AlmaLinux/Rocky 8/9, the default policy is
targetpw=offandrootpw=off— your password authenticates you, not root's.
The privilege itself is not granted by being in wheel directly — it's granted by the line in /etc/sudoers that says members of wheel can run things. Removing that line (or commenting it out) breaks wheel-based sudo even though the group still exists.
Editing sudoers safely: always use visudo
Never edit /etc/sudoers with a regular editor. Use visudo:
sudo visudo
visudo does three things vi /etc/sudoers does not:
- Locks the file so two admins can't simultaneously corrupt it.
- Validates the syntax on save — a typo that makes the file unparseable would otherwise lock everyone out of
sudo, including root. - Refuses to save invalid edits, prompting you to fix the error or abandon the change.
If visudo finds an error, it shows:
>>> /etc/sudoers: syntax error near line 99 <<<
What now? [e]dit again, e[x]it without saving, exit and [Q]uit:
Always pick e and fix it. Picking Q saves a broken sudoers file, which on a fresh machine where you haven't sudo'd yet may leave you with no escape.
To use a specific editor (the default on EL is vi):
sudo EDITOR=nano visudo
To validate a sudoers file separately (e.g. one you're writing programmatically):
sudo visudo -cf /etc/sudoers.d/alice
visudo -c is the right thing to run in any deploy pipeline that writes sudoers files — fail the deploy on a non-zero exit rather than discovering it on the next reboot.
Per-user grants without touching /etc/sudoers: /etc/sudoers.d/
Editing /etc/sudoers directly works, but it's not the recommended path on modern RHEL-family distros. The base /etc/sudoers ends with:
#includedir /etc/sudoers.d
That tells sudo to read every file in /etc/sudoers.d/ as if it were appended to sudoers itself. The convention is one file per user or per role, named after the user/role:
sudo visudo -f /etc/sudoers.d/alice
Inside, write the grant directly:
alice ALL=(ALL) ALL
Save and exit. visudo -f validates the syntax of that specific file the same way visudo validates /etc/sudoers.
This pattern has real advantages:
- Easy to audit.
ls /etc/sudoers.d/is the list of everyone with custom sudo rights. - Easy to remove.
rm /etc/sudoers.d/alicerevokes the grant in one step. No risk of mangling the main file. - Configuration-management friendly. Ansible's
template/copymodules, Puppet'sfile, and Terraform'slocal-execall drop files cleanly into/etc/sudoers.d/. Editing/etc/sudoersfrom CM is fragile (idempotency is hard); dropping a managed file isn't. - No risk of corrupting
/etc/sudoers. Even if the file insudoers.dis malformed, sudo will skip it and log a warning rather than refuse to run entirely. (This is one of the reasons RHEL ships#includedirenabled by default.)
A file in /etc/sudoers.d/ must not contain a . or ~ in the filename. The #includedir directive deliberately ignores files matching ~ (editor backups) and files containing . (e.g. .rpmsave, alice.bak). alice is fine; alice.conf is silently ignored. This trips people up regularly.
Permissions matter too:
sudo chmod 0440 /etc/sudoers.d/alice
sudo chown root:root /etc/sudoers.d/alice
visudo -f writes those permissions itself, but if you cp or mv a file into the directory, set them by hand.
Common grant shapes
Pick the narrowest one that solves your problem.
Full sudo with a password (the standard admin grant)
alice ALL=(ALL) ALL
Equivalent to membership in wheel. Use this for human admins.
Full sudo without a password
alice ALL=(ALL) NOPASSWD: ALL
NOPASSWD: removes the password prompt entirely for the matched commands. Convenient — and dangerous. A user with passwordless full sudo is effectively root with no extra friction; an attacker who lands a shell as that user is also effectively root with no extra friction.
Reasonable uses: CI bastions, automation accounts, single-user homelab boxes. Unreasonable uses: production human admin accounts.
If you must combine wheel-with-password and a NOPASSWD list for a specific user, the order in the file matters — later rules override earlier ones:
%wheel ALL=(ALL) ALL
alice ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
Now Alice is in wheel (so she gets full sudo with a password) plus can run systemctl restart nginx without a password.
Narrow grant: just one or two commands
This is usually what you actually want for service accounts:
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myapp, /usr/bin/systemctl status myapp
The deploy account can restart and check status of myapp without a password — and nothing else. If it tries sudo bash or sudo systemctl restart sshd, sudo refuses and logs the attempt.
A few ergonomic notes:
- Always use absolute paths.
systemctlworks in your shell because of$PATH;sudoersdoesn't trust$PATH. Use/usr/bin/systemctl, notsystemctl. (Usewhich systemctlto find it.) - Beware of arguments.
NOPASSWD: /usr/bin/systemctl restart myapppermits exactly that command;sudo systemctl restart myapp.service(with the explicit suffix) is a different string and will be denied. To allow argument variations, drop the argument:NOPASSWD: /usr/bin/systemctlpermits anysystemctlinvocation, which is much broader. - Wildcards are tempting and risky.
NOPASSWD: /usr/bin/systemctl restart *looks tight but*matches spaces, so an attacker can passrestart x; rm -rf /and the rule still matches. Prefer enumerating exact commands or usingCmnd_Alias.
Cmnd_Alias for groups of related commands
For repeated lists, name them:
Cmnd_Alias APP_OPS = /usr/bin/systemctl restart myapp, \
/usr/bin/systemctl reload myapp, \
/usr/bin/systemctl status myapp, \
/usr/bin/journalctl -u myapp
deploy ALL=(ALL) NOPASSWD: APP_OPS
Cleaner, easier to audit, easier to extend.
Run as a different non-root user
alice ALL=(postgres) /usr/bin/psql
Now sudo -u postgres psql works, but sudo psql (which would default to root) does not. This is the right pattern for "let this admin run db commands as the db user" without giving them root.
Run as another user, no password
alice ALL=(www-data) NOPASSWD: ALL
Alice can run anything as www-data without a password, but is not granted root.
A complete worked example
Create a new admin user from scratch on a fresh AlmaLinux or Rocky Linux box:
# 1. Create the user (skip if it already exists)
sudo useradd -m -s /bin/bash alice
# 2. Set the password (alice will use this for sudo)
sudo passwd alice
# 3. Add to wheel — the canonical sudo group on EL
sudo usermod -aG wheel alice
# 4. (Optional) Drop in an SSH key so password isn't needed for login
sudo mkdir -p /home/alice/.ssh
sudo chmod 700 /home/alice/.ssh
echo 'ssh-ed25519 AAAA... alice@workstation' | sudo tee /home/alice/.ssh/authorized_keys
sudo chmod 600 /home/alice/.ssh/authorized_keys
sudo chown -R alice:alice /home/alice/.ssh
# 5. Verify
id alice
getent group wheel
Now log in as alice from another terminal (don't close the existing root session yet — keep the safety net) and confirm:
ssh alice@server
sudo whoami
# [sudo] password for alice: ********
# root
Once sudo whoami returns root, the grant works. Now you can disable root SSH login (PermitRootLogin no in /etc/ssh/sshd_config) and password authentication (PasswordAuthentication no) with confidence — alice has the keys to the kingdom and a key-based way in.
The "keep the original session open" rule applies whenever you're changing auth: SSH config, sudoers, PAM. If the change locks you out, the open session is your way back in to fix it.
Verifying a grant: what each user can actually do
# What can the current user run with sudo?
sudo -l
# What can a specific user run? (run as root)
sudo -l -U alice
The output enumerates every matching rule, including which commands are NOPASSWD-eligible and which require a password. Run this any time you wonder whether a sudoers edit took effect — it's the authoritative answer, parsed exactly the way sudo itself parses the rules.
Sample output for a wheel member:
User alice may run the following commands on server:
(ALL) ALL
Sample output for a narrow grant:
User deploy may run the following commands on server:
(ALL) NOPASSWD: /usr/bin/systemctl restart myapp, /usr/bin/systemctl status myapp
If a rule you wrote isn't showing up here, the file is most likely:
- Misnamed (contains
.or~), - Has the wrong permissions (must be
0440, ownerroot:root), - Or has a syntax error and was rejected.
sudo journalctl -t sudoand/var/log/secureshow the parser warnings.
Logging and auditing sudo use
Every sudo call is logged. On AlmaLinux/Rocky:
# Recent sudo activity
sudo journalctl -t sudo --since "1 hour ago"
# Or the classic auth log
sudo tail -n 100 /var/log/secure | grep sudo
Each entry includes the user, the working directory, the target user, and the command. For a more detailed audit trail (every command typed in a sudo session, with timestamps), enable I/O logging in sudoers:
Defaults log_input,log_output
Defaults iolog_dir="/var/log/sudo-io"
Replay a session with sudoreplay. This is heavy and noisy — turn it on only for accounts that warrant it (shared admin accounts on regulated systems, post-incident forensics, etc.).
For continuous monitoring of who's using sudo and when, ship /var/log/secure (or the journal) to a central log system and alert on patterns: a service account suddenly running sudo bash, multiple incorrect password entries in a row, or any sudo activity from a user who shouldn't have it. Xitoring's server monitoring collects log signals from EL hosts via Xitogent — pair it with systemd service health checks so a misconfigured sudoers file (one that breaks a service that runs sudo internally) is visible the moment it bites.
Common mistakes that lock you out
The classic ways to break sudo on a fresh AlmaLinux or Rocky box, and how to avoid them.
Editing /etc/sudoers without visudo
A typo in the main file makes sudo refuse to parse it at all. Every subsequent sudo call fails:
>>> /etc/sudoers: syntax error near line 42 <<<
sudo: parse error in /etc/sudoers near line 42
sudo: no valid sudoers sources found, quitting
Recovery: if you have an existing root shell open, visudo and fix it. If you don't, you need physical/console access — boot into single-user mode (rescue mode in EL parlance), edit /etc/sudoers, fix the syntax, reboot. This is the textbook reason to always use visudo.
Removing yourself from wheel
sudo gpasswd -d alice wheel # removes alice from wheel
If alice was the only sudo user and there's no root SSH login enabled, that's a lockout. Recovery: console boot to single-user mode, add yourself back. Or — better — keep one open root session whenever you're touching wheel membership.
Files in /etc/sudoers.d/ that are silently ignored
A file named alice.conf is skipped (the . rule). A file named alice~ is skipped (editor backup). A file with permissions 0644 instead of 0440 is rejected. The grant looks like it should work, cat-ing the file shows the right content, but sudo -l doesn't list it. Always:
sudo ls -l /etc/sudoers.d/
sudo visudo -c
visudo -c cross-validates everything sudo would actually load.
sudo works but sudo -i fails / interactive shell weirdness
Some sudoers configurations restrict what you can do inside a sudo session. The requiretty default (off on modern EL but sometimes set in custom configs) blocks sudo from non-interactive sessions. If a CI job runs sudo systemctl restart myapp and it works locally but fails in CI with sudo: sorry, you must have a tty to run sudo, the fix is to remove Defaults requiretty from sudoers — or to allocate a TTY in the CI job (ssh -tt for SSH-driven runs).
Sudo prompts for password when you expected NOPASSWD
Two usual causes:
- Rule order. Later rules override earlier ones. If
%wheel ALL=(ALL) ALLcomes afteralice ALL=(ALL) NOPASSWD: /usr/bin/systemctl ..., alice gets prompted for a password because the wheel rule is the last one matching. Move the user-specific NOPASSWD line below the wheel line. - Caching.
sudocaches credentials for 5 minutes. If you just ransudowith a password, the nextsudowon't prompt — that's not because of NOPASSWD, that's the cache. Runsudo -kto clear the cache and test fresh.
Adding a user to sudo instead of wheel
sudo usermod -aG sudo alice # wrong on EL
The sudo group exists on Debian/Ubuntu but not by default on RHEL/Rocky/Alma. Adding a user to a non-existent (or unprivileged) sudo group does nothing. The right group on EL is wheel. (usermod will silently create the sudo group entry on some configurations but won't grant any rights — there's no matching line in sudoers.)
Using su and confusing it with sudo
su switches user identity and asks for the target user's password (root's, by default). sudo runs a single command as another user and asks for your own password. They're two different tools. If "sudo isn't working" but you can su - into root, your sudo grant is wrong, not your password.
Removing or revoking sudo
To remove sudo from a user:
# Remove from wheel (covers the wheel-based grant)
sudo gpasswd -d alice wheel
# And/or delete any per-user file in sudoers.d
sudo rm /etc/sudoers.d/alice
sudo -l -U alice after removal should show:
User alice is not allowed to run sudo on server.
For service accounts that are being decommissioned, the right cleanup is:
sudo rm /etc/sudoers.d/<name>
sudo userdel -r <name> # deletes home directory too; check before running
Operational tips
- One file per user/role in
/etc/sudoers.d/. Easier to audit, revoke, and put under configuration management. The main/etc/sudoersshould ideally only contain defaults and the wheel rule. - Always run
sudo visudo -cafter any sudoers change made outside ofvisudoitself. Catches the silent-skip and parse-error cases before they hurt. - Keep one root session open whenever you're editing sudoers, PAM, or sshd_config. A second terminal is the cheapest insurance there is.
- Prefer narrow grants over
NOPASSWD: ALL. A service account that can only run a handful of commands is much less dangerous than one that can run anything as root without a password. - Never use shell metacharacters or wildcards in command paths.
sudoersdoes not parse them the way a shell does, but it also doesn't escape them. Enumerate commands or useCmnd_Alias. - Use
sudo -l -U <user>to audit grants. It's the authoritative parsed view, not acatof the file. - Log sudo use centrally. A sudo command from an account that shouldn't have it, or a flood of failed sudo attempts, is one of the cleanest pre-incident signals you'll get.
Troubleshooting
alice is not in the sudoers file. This incident will be reported.— Alice is not inwheeland there's no per-user file.sudo usermod -aG wheel alice, then have alice log out and back in.sudo: no tty present and no askpass program specified. — A NOPASSWD rule is missing for a non-interactive context, orrequirettyis enforced. Either add NOPASSWD for the specific command, or allocate a TTY in your automation (ssh -t).sudo: parse error in /etc/sudoers near line N. — The main sudoers file has a syntax error. Fix viavisudofrom an existing root shell, or boot to rescue mode if you're locked out.- A
/etc/sudoers.d/<name>file exists butsudo -l -U <name>doesn't show it. — Check the filename for.or~, check permissions are0440 root:root, and runsudo visudo -cfor parser warnings. sudoworks, butsudo -iorsudo bashdoesn't. — A specific command grant doesn't include shell access. Either widen the grant (ALLinstead of a specific command list) or accept that this account isn't supposed to have a root shell.- The user can sudo, but the password isn't accepted. — Sudo wants the user's password, not root's. If the user's password is genuinely correct and still rejected, check
/var/log/securefor PAM errors andpasswd -S <user>for account lock status. sudoprintsyou must have a tty to run sudofrom a script. — AddDefaults:<user> !requiretty(preferred) or run the script with a TTY (ssh -tt,script).- Group membership added but
sudostill says "not in sudoers". — The user's session is using cached group info.idconfirms group membership, butsudoreads it from the active session. Have them log out and back in (or runnewgrp wheelfor a temporary subshell with the new group).
Summary
To add a user to sudoers on AlmaLinux or Rocky Linux:
- The canonical answer:
sudo usermod -aG wheel <user>. The default/etc/sudoersalready grantswheelmembers full sudo with a password. - For per-user grants, drop a file into
/etc/sudoers.d/<user>withsudo visudo -f /etc/sudoers.d/<user>. Easier to audit, easier to revoke, configuration-management friendly. - Never edit
/etc/sudoersdirectly withoutvisudo. A syntax error locks everyone out of sudo, including root. - Pick the narrowest grant that works.
NOPASSWD: ALLis convenient and rarely the right answer for human accounts. - Use absolute paths in command grants and avoid wildcards.
- Verify with
sudo -l -U <user>— the parsed, authoritative view of what a user can run. - Log centrally and alert on anomalies. Misuse of sudo is one of the highest-signal events on a server.
- Keep an open root session whenever you edit sudoers, PAM, or sshd_config. It's the cheapest possible safety net.
The mechanics are simple — a group, a file, a check. The discipline around them (visudo, narrow grants, central logging, an open safety session) is what separates a server you trust to run unattended from one that's one editor mistake away from a midnight rescue boot.