Skip to content

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:

$ id
uid=1000(alice) gid=1000(alice) groups=1000(alice),27(sudo),128(docker)
  • 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:

who   where=(as_whom)   what
alice ALL=(ALL:ALL)     ALL
%sudo ALL=(ALL:ALL)     ALL

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) ALL in /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:

$ sudo visudo -c
/etc/sudoers: parsed OK
/etc/sudoers.d/deploy: parsed OK

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.

  1. Create a user and look at the entries:
    sudo useradd -m -s /bin/bash lab1
    getent passwd lab1
    sudo getent shadow lab1        # note the hash field: ! or * or $y$... depending on state
    id lab1
    
  2. Add lab1 to a group, then confirm:
    sudo groupadd labgroup
    sudo usermod -aG labgroup lab1
    id lab1
    
  3. Give lab1 a narrow sudo rule, validate it, and test it:
    echo 'lab1 ALL=(root) NOPASSWD: /usr/bin/id' | sudo tee /etc/sudoers.d/lab1
    sudo chmod 0440 /etc/sudoers.d/lab1
    sudo visudo -c
    sudo -l -U lab1
    sudo -u lab1 sudo -n id        # runs id as root, without a password
    sudo -u lab1 sudo -n ls /root  # must be refused
    
  4. Cleanup:
    sudo rm /etc/sudoers.d/lab1
    sudo userdel -r lab1           # -r also removes /home/lab1 and the mail spool
    sudo groupdel labgroup
    

How to verify you did it correctly

  • Step 1: id lab1 shows uid=<n>(lab1) gid=<n>(lab1) groups=<n>(lab1), with <n> at or above 1000.
  • Step 2: id lab1 now lists labgroup under groups=.
  • Step 3: visudo -c prints parsed OK for the new file; sudo -u lab1 sudo -n id prints uid=0(root)...; the ls /root attempt fails with sudo: a password is required (the rule only covers id, and -n forbids prompting).
  • Step 4: getent passwd lab1 and ls /etc/sudoers.d/lab1 both return nothing/not found.

Common mistakes

  • Forgetting that group changes need a new login. The running session keeps its old group list.
  • Granting docker group membership casually. Anyone in docker can start a container that mounts / and edit it as root, so the group is root-equivalent. Treat it like sudo.
  • Using sudo in a way that loses the environment. sudo resets most environment variables by design; if a command needs one, pass it explicitly (sudo VAR=value cmd) rather than disabling env_reset.
  • Putting a sudoers drop-in with the wrong mode. Files in /etc/sudoers.d/ must not be writable by group/other (use 0440); 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 ~, so deploy.conf can silently do nothing.

⚠ Dangerous commands

  • usermod -G docker alice (no -a) replaces all supplementary groups. If alice was in sudo, 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/sudoers with nano/vim directly. One syntax error and sudo refuses to work for everyone. Always visudo; keep a root shell open in another terminal while changing rules.
  • NOPASSWD: ALL for a person, or any rule allowing a shell, editor or less (they can spawn a shell): that is unrestricted root without a password prompt. Allow exact commands instead.
  • userdel -r deletes 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 ...).

Review questions

  1. What does the kernel actually use to decide access: names or numbers? Where do names come from?
  2. Why does usermod -G without -a risk locking you out?
  3. You added a user to a group but id in their open terminal does not show it. Why?
  4. Which command validates sudoers syntax before it can break sudo for everyone?
  5. Why is membership in the docker group effectively root access?