Users, Groups, and sudo Explained¶
Every file, process and socket on a Linux system belongs to a numeric user ID and group ID. Names like alice or docker are only labels looked up in a database. Understanding that database, and how sudo decides who gets to act as root, is the base for permissions, SSH access, service accounts and almost every "permission denied" you will debug.
About the output on this page
id, stat, visudo -V and getent output was captured on Ubuntu 22.04 (sudo 1.9.9). Output of commands that change accounts (useradd, usermod, gpasswd) is representative, using a made-up user alice; those commands need root and were not run for this article.
How it works¶
Identity is a number¶
The kernel only knows a user ID (UID) and group IDs (GIDs). id shows all of them for the current session:
uid=1000(alice): the user. UID 0 is root and is the only UID the kernel treats as privileged.gid=1000(alice): the primary group. New files get this group.groups=...: supplementary groups. Access is granted if any of them matches.
Conventionally, UIDs below 1000 (the threshold is UID_MIN in /etc/login.defs) belong to the system and service accounts, and ordinary people start at 1000.
Where accounts are stored¶
| File | Contents | Typical mode |
|---|---|---|
/etc/passwd | One line per user: name:x:UID:GID:comment:home:shell | -rw-r--r-- (world-readable) |
/etc/shadow | Password hashes and aging fields | -rw-r----- root:shadow |
/etc/group | Groups and their members | -rw-r--r-- |
/etc/sudoers, /etc/sudoers.d/* | Who may run which commands via sudo | -r--r----- root:root |
The x in the password field of /etc/passwd means "the hash lives in /etc/shadow". The split exists because /etc/passwd must be readable by everyone (programs map UIDs to names with it), but hashes must not be.
Do not parse these files by hand in scripts. Use getent, which goes through the same lookup chain as the rest of the system (/etc/nsswitch.conf), so it also finds users from LDAP, SSSD or other sources:
getent passwd alice # alice:x:1000:1000:Alice:/home/alice:/bin/bash
getent group sudo # sudo:x:27:alice
Creating users and managing groups¶
sudo useradd -m -s /bin/bash alice # -m creates /home/alice, -s sets the login shell
sudo passwd alice # set a password interactively
sudo usermod -aG docker alice # APPEND alice to the docker group
sudo gpasswd -d alice docker # remove alice from docker
The -a in usermod -aG matters: it means append. See the warning below.
On Debian and Ubuntu, adduser alice is a friendlier wrapper that asks for a password and copies the skeleton files; useradd is the low-level tool and is the one available on every distribution.
A group change does not apply to sessions that are already open. A process receives its group list at login, so alice must log out and in again (or start a new session with newgrp docker for one shell) before the new group takes effect. id in the old session still shows the old list. This is the usual reason for "I added myself to the group but still get permission denied".
How sudo decides¶
sudo is a setuid-root program (-rwsr-xr-x root:root /usr/bin/sudo). When you run sudo command, it reads the sudoers rules, checks whether your user, or one of your groups, is allowed to run that command as the target user, asks for your own password (not root's) unless a rule says otherwise, and then executes the command.
A sudoers rule has this shape:
Read the first line as: user alice, on any host (ALL), may run as any user and group, any command. A leading % means a group, so the second line gives every member of group sudo the same rights. That is the whole mechanism behind "add the user to the sudo group":
- Debian and Ubuntu ship
%sudo ALL=(ALL:ALL) ALLin/etc/sudoers. - RHEL-family systems ship the same rule for the group
wheel.
Narrower rules are better than ALL:
# /etc/sudoers.d/deploy
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp, /usr/bin/systemctl status myapp
Always edit with visudo, never with a plain editor. It locks the file and checks the syntax before saving. For a drop-in file use sudo visudo -f /etc/sudoers.d/deploy. To check without editing:
Inspect what a user may do with sudo -l (for yourself) or sudo -l -U alice (as root).
Ubuntu/Debian vs. RHEL-family differences¶
| Ubuntu/Debian | RHEL-family | |
|---|---|---|
| Admin group | sudo | wheel |
| Friendly user tool | adduser / deluser | only useradd / userdel |
| Root account | Locked on Ubuntu by default (use sudo -i); Debian asks for a root password at install unless left empty | Root password usually set at install |
| Default home perms | /home/user is 0750 on recent Ubuntu | 0700 |
Adding to the admin group therefore reads usermod -aG sudo alice on Debian/Ubuntu and usermod -aG wheel alice on RHEL-family systems.
Practical exercise¶
Do this on a disposable VM, not a production host.
- Create a user and look at the entries:
- Add
lab1to a group, then confirm: - Give
lab1a narrow sudo rule, validate it, and test it: - Cleanup:
How to verify you did it correctly¶
- Step 1:
id lab1showsuid=<n>(lab1) gid=<n>(lab1) groups=<n>(lab1), with<n>at or above 1000. - Step 2:
id lab1now listslabgroupundergroups=. - Step 3:
visudo -cprintsparsed OKfor the new file;sudo -u lab1 sudo -n idprintsuid=0(root)...; thels /rootattempt fails withsudo: a password is required(the rule only coversid, and-nforbids prompting). - Step 4:
getent passwd lab1andls /etc/sudoers.d/lab1both return nothing/not found.
Common mistakes¶
- Forgetting that group changes need a new login. The running session keeps its old group list.
- Granting
dockergroup membership casually. Anyone indockercan start a container that mounts/and edit it as root, so the group is root-equivalent. Treat it like sudo. - Using
sudoin a way that loses the environment.sudoresets most environment variables by design; if a command needs one, pass it explicitly (sudo VAR=value cmd) rather than disablingenv_reset. - Putting a sudoers drop-in with the wrong mode. Files in
/etc/sudoers.d/must not be writable by group/other (use0440); otherwise sudo ignores or rejects them. - Names with a dot or tilde in
/etc/sudoers.d/. The include mechanism skips files whose name contains.or ends in~, sodeploy.confcan silently do nothing.
⚠ Dangerous commands¶
usermod -G docker alice(no-a) replaces all supplementary groups. Ifalicewas insudo, she just lost admin rights; if it was your only admin account, you may be locked out of privileged access. Always use-aG.- Editing
/etc/sudoerswithnano/vimdirectly. One syntax error andsudorefuses to work for everyone. Alwaysvisudo; keep a root shell open in another terminal while changing rules. NOPASSWD: ALLfor a person, or any rule allowing a shell, editor orless(they can spawn a shell): that is unrestricted root without a password prompt. Allow exact commands instead.userdel -rdeletes the home directory with no undo. Back it up first, and check the account is not running processes (pgrep -u alice).- Deleting or renumbering UID 0 or system accounts. Never change the UID of an existing user without also fixing ownership of all their files (
find / -xdev -uid <old> -exec chown ...).
Related articles¶
- Linux Filesystem Hierarchy and Core Concepts: ownership and permission bits
- SSH Hardening and Key-Based Authentication: per-user keys and who may log in
Review questions¶
- What does the kernel actually use to decide access: names or numbers? Where do names come from?
- Why does
usermod -Gwithout-arisk locking you out? - You added a user to a group but
idin their open terminal does not show it. Why? - Which command validates sudoers syntax before it can break
sudofor everyone? - Why is membership in the
dockergroup effectively root access?