Users, groups and the account files
passwd, shadow, group and creating accounts.
A Linux machine identifies you by a number, not a name. This lesson shows the three files that define every account (/etc/passwd, /etc/shadow and /etc/group), what UID 0 means, why service accounts have no login shell, how to create and remove a user with the standard tools, the difference between locking a password and expiring an account, and the three commands that tell you who can take over a machine you have just been handed. Reading these files is the first thing to do on any server you inherit or are asked to secure.
Who the machine thinks you are
id prints your identity as the kernel sees it: your UID, your primary group's GID, and every supplementary group you belong to.
This account is UID 1001, in the groups adm (which may read logs) and sudo (which may become root). One number is special. UID 0 is root, the superuser, the account that skips the permission checks the earlier lessons described. Root is defined by the number, not the name: give any account UID 0 and it is root, whatever you called it.
Where the accounts live: /etc/passwd
Every account is written in /etc/passwd, a world-readable file (it holds no secrets, only the facts needed to look an account up). Rather than read the raw file, ask the system with getent (get entries), which reads /etc/passwd and any networked directory the machine is joined to, and returns the same colon-separated format.
Split a line on its colons: username, an x where the password hash once sat (it now lives in /etc/shadow), the UID, the primary group's GID, a comment field, the home directory, and the login shell. Look at daemon, www-data and sshd: their shell is /usr/sbin/nologin, a small program whose only job is to refuse a login and exit. These are service accounts, one per running program, so that a broken web server leaves an attacker as the limited www-data and not as root. When you audit a box, a service account whose shell is /bin/bash instead of nologin is worth a hard look.
Where the secrets live: /etc/shadow
/etc/shadow holds the password hashes and the password-aging rules. Its permissions tell the story before you read a byte:
/etc/passwd is world-readable (644), but /etc/shadow is 640, readable only by root and the shadow group, so an ordinary user cannot copy the hashes and attack them offline. On RHEL 10 the file is stricter still, mode 0000 owned by root:root, which only root can read because root is not bound by permission bits. Each line splits on colons into the username, the hash, and the aging fields:
name : hash : last-change : min : max : warn : inactive : expire :
The dates are counted in days since 1 January 1970. The hash is self-describing: it begins with a marker between dollar signs that names the algorithm, and $y$ is yescrypt, the modern default on both Ubuntu and RHEL (both set ENCRYPT_METHOD YESCRYPT in /etc/login.defs). A single * means the account never had a usable password, normal for service accounts, and a leading ! means the password is locked. A leaked /etc/shadow lets an attacker grind guesses offline where no login prompt slows them, so the weak point becomes the password itself: keep the file root-only and treat a copy of it with the same care as the original.
Creating users and managing groups
useradd is the low-level tool and adduser is the friendlier Ubuntu wrapper. One default surprises people: on Ubuntu 26.04, useradd gives a new account the shell /bin/sh, not bash, so you pass -s /bin/bash when you want an interactive bash user.
The home directory came out drwxr-x--- (mode 0750), from HOME_MODE in /etc/login.defs, so other ordinary users cannot look inside it; RHEL 10 uses 0700 and defaults the shell to /bin/bash. Give every person their own named account, never a shared login, so the logs can tell who did what.
Every user has one primary group (the GID in their passwd line, which owns files they create) and any number of supplementary groups. groupadd creates a group, and getent group NAME shows its line from /etc/group: the name, an x, the GID and the member list, empty for a new group.
usermod -aG adds a user to a supplementary group; the -a (append) is not optional, as the quiz shows. The member list in /etc/group then names the user:
groupdel NAME removes a group again, once no user has it as their primary group.
Membership in sudo (or wheel on RHEL) is what lets an account become root through sudo, the subject of the next lesson. Some other groups are just as powerful without looking it: docker, lxd and incus can each start a container that mounts the host and take it over, and disk can read and write the raw disk. When you review who can administer a machine, read those groups the same way you read sudo.
Locking a password versus expiring an account
When someone leaves, you have two different tools and they do different things. To see both, create a second account, ess-acct-bob, and give it a password. passwd -S prints an account's password status: a new account shows L (locked), because no password has been set, and after chpasswd sets one (here a random one from openssl rand that nobody knows; interactively you would run sudo passwd NAME) it shows P, a usable password.
The other fields are the date of the last password change, the minimum and maximum days between changes (0 and 99999, the defaults from /etc/login.defs, which means no forced expiry), the warning period (7 days) and the inactivity period (-1, none). passwd -l locks the password by putting a ! in front of the hash:
passwd -S reports L again. But locking the password does not stop a login with an SSH key, because key logins never consult the password: the lab gave bob a key and logged in with it after this step, and it still worked. To block every login method, including keys, expire the account by setting an expiry date in the past:
usermod --expiredate 1 sets the expiry to day 1, 2 January 1970, counted in days since 1 January 1970. Use 1 rather than 0: shadow(5) warns that 0 is read either as "never expires" or as 1 January 1970, depending on the program. At login, PAM (the library every login goes through, used by sshd with its default UsePAM yes) checks the expiry for password and key logins alike: in the lab, bob's next key login was refused with Your account has expired, and sshd logged account ess-acct-bob has expired. Expiry does not end sessions that are already open. Proper offboarding does more (end their running sessions, remove their authorized_keys and group memberships, check their cron jobs and timers); the hardening lesson "Users, groups and account lifecycle" runs the full sequence. Lock or expire first and delete much later, because deleting an account destroys the record of what it did.
When the time does come, sudo userdel -r NAME removes the account with its home directory and mail spool; on a server without local mail it prints a harmless warning that no spool was found. First run sudo find / -xdev -user NAME to find files the account owns elsewhere, because anything left behind becomes a file with no owner, the red flag from the ownership lesson.
Three checks on a strange machine
Three short commands answer the questions that matter first: who is secretly root, who has no password, and who can become root. awk splits each line on colons and prints the field you ask for.
The first prints every account with UID 0; a healthy system prints only root, and a second name is a backdoor superuser. The second prints any account with an empty password field, which should print nothing. The third shows who holds admin rights through the sudo group.
The group is not the whole answer. Ubuntu's /etc/sudoers also grants full rights to %admin (% marks a group name), a group kept from older releases, and reads every file in /etc/sudoers.d/, where a rule can name a single user. sudo -l -U NAME, covered in the next lesson, reports what one account can really run. It also helps to know the numbering: /etc/login.defs sets UID_MIN 1000, so accounts for people start at 1000 and system accounts such as sshd (UID 987 here) sit below it. A human-looking name with a low UID, or a service name with a UID of 1000 or more, is worth a second look.
Try this
Create a throwaway user with sudo useradd -m -s /bin/bash tester and confirm with id tester and ls -ld /home/tester that the home directory is drwxr-x---. Create a group with sudo groupadd testers, add the user with sudo usermod -aG testers tester, and check that id tester shows both groups and getent group testers lists the member. Set a password with sudo passwd tester, lock it with sudo passwd -l tester and read sudo passwd -S tester, then expire the account with sudo usermod --expiredate 1 tester and read sudo chage -l tester. Run awk -F: '($3 == 0) {print $1}' /etc/passwd and confirm only root appears. Remove the user with sudo userdel -r tester and the group with sudo groupdel testers.
Takeaway
Root is whoever has UID 0, and admin power comes from the sudo, wheel, docker and similar groups, so read /etc/passwd, /etc/shadow permissions and getent group on any box you inherit. To take away access, expire the account with usermod --expiredate 1 rather than only locking the password, because passwd -l leaves SSH-key logins working.