On a Linux system, what does each of /etc/passwd, /etc/shadow and /etc/group hold, and why was the password hash moved out of /etc/passwd?
answer
- one line per account, colon-separated
- world-readable identity, root-only secret
- seven fields, then the hash file
- the number is what the kernel enforces
- getent, not grep
basics
~20 s/etc/passwd holds one line per account — name, UID, primary GID, real name, home directory and login shell — and is world-readable. /etc/shadow holds the password hashes and ageing fields, readable only by root. /etc/group lists groups and their extra members.
solid answer
~50 s`/etc/passwd` is the account record: seven colon-separated fields — login name, a password placeholder, numeric UID, primary GID, GECOS/comment, home directory and login shell. It has to stay world-readable because ordinary tools map UIDs to names with it, so the password hash was split out into `/etc/shadow`, which only root can read. Shadow carries the hash plus ageing fields — last change, minimum and maximum age, warning, inactivity and expiry. `/etc/group` maps a group name to a GID and lists the *supplementary* members; a user's primary group lives in their passwd line, not here. The second field of a passwd line is normally `x`, meaning "look in shadow". Privilege is decided by the numeric UID, not the name: any account with UID 0 is root. In practice query these through `getent passwd` so you also see accounts coming from LDAP or SSSD.
code
bash · 8 lines# account record: name:x:UID:GID:GECOS:home:shell
getent passwd www-data
# the secret half: name:hash:lastchg:min:max:warn:inactive:expire:
sudo getent shadow www-data
# ageing fields printed in human form
sudo chage -l www-datago deeper
Be able to name the seven /etc/passwd fields in order and say plainly that the hash lives in /etc/shadow, which only root can read.
Explain why passwd must stay world-readable, what the shadow ageing fields mean, and how a primary group differs from supplementary membership in /etc/group.
Show you check accounts with getent because NSS sources exist, and treat UID 0 duplicates, empty hash fields and stale service accounts as findings you would act on.
Own the policy question: where account identity should live for a fleet, how UID and GID ranges are allocated consistently across hosts, and what joiner-mover-leaver automation must guarantee.
## The three files A Unix account is not an object in a database engine; it is a line of text in a flat file, and understanding those lines explains most account behaviour on Linux. - `/etc/passwd` — the account record: identity, home, shell. World-readable. - `/etc/shadow` — the secret half: the password hash and ageing policy. Root-only. - `/etc/group` — group names, GIDs, and supplementary membership lists. World-readable. - `/etc/gshadow` — the rarely used secret half of group records (group passwords and administrators). Root-only. ## /etc/passwd, field by field ``` alice:x:1000:1000:Alice Chen,,,:/home/alice:/bin/bash ``` Seven colon-separated fields: 1. **Login name** — what you type; humans use it, the kernel does not. 2. **Password placeholder** — historically the hash itself; today almost always `x`, meaning "the real hash is in /etc/shadow". 3. **UID** — the number the kernel actually enforces on. 4. **Primary GID** — the group a new file gets, and the group the login session starts with. 5. **GECOS** — comment field, conventionally comma-separated real name, room, phones. 6. **Home directory** — where the login session starts, and what `$HOME` becomes. 7. **Login shell** — the program `login`, `sshd` or `su` executes. Service accounts normally get `/usr/sbin/nologin` (`/sbin/nologin` on RHEL-family) or `/bin/false` so nobody gets an interactive session on them. ## Why the hash moved The file must be readable by everyone, because unprivileged programs constantly translate UID 1000 into "alice" — `ls -l`, `ps`, a mail client. When the hash lived in field 2, any user could copy the whole file and grind it offline. Shadow passwords keep the world-readable identity data in place and move the crackable material into a file only root can open: ``` alice:$y$j9T$SALT...$HASH...:19800:0:99999:7::: ``` Fields are: name, hash, days since epoch of last change, minimum days between changes, maximum age, warning period, inactivity grace after expiry, absolute account-expiry date, reserved. The `chage -l alice` command prints these in human form. The hash itself is `$id$params$salt$hash`. `$6$` is sha512crypt, `$y$` is yescrypt (the default on Debian 11+/Ubuntu 22.04+), `$2b$` is bcrypt; RHEL-family still defaults to `$6$`. ## Locked, no password, and no login These are three different things and interviewers like the distinction: - **Locked** — the hash field is prefixed with `!` (what `passwd -l` / `usermod -L` do). No password can ever match, but other authentication paths, such as an SSH key, still work. - **No password** — the hash field is *empty*. Depending on PAM configuration this can mean authentication succeeds with no password at all. That is a finding, not a feature. - **`*`** — conventionally used for system accounts that were never meant to authenticate with a password. - **No login shell** — `/usr/sbin/nologin` refuses to start an interactive shell, but it is a convenience, not a security boundary: root can still run `su -s /bin/bash svcuser`. ## /etc/group ``` deploy:x:2001:alice,bob ``` Name, placeholder, GID, and the comma-separated list of *supplementary* members. Alice's **primary** group is the GID in her passwd line and usually does not appear here — which is why `getent group deploy` and `id alice` can legitimately disagree about who is "in" a group. ## UID 0 and the UID ranges The kernel grants root privilege on the **number** 0, not on the string `root`. A second passwd line with UID 0 is a fully privileged account with a different name, and finding one is a classic backdoor indicator. Conventional ranges are set in `/etc/login.defs`: `UID_MIN`/`UID_MAX` (typically 1000–60000) for human accounts, `SYS_UID_MIN`/`SYS_UID_MAX` for daemon accounts allocated below 1000. These are policy for `useradd`, not kernel rules. ## The files are not the whole truth On a machine joined to a directory service, accounts arrive over NSS (LDAP, SSSD, AD) and never appear in the flat files. That is why the correct habit is `getent passwd alice` and `getent group deploy` rather than `grep alice /etc/passwd`: `getent` asks every source configured for that database, so an empty grep proves nothing about whether the account exists.
- You find a second account in /etc/passwd whose third field is 0. What does that mean?It is root under another name. The kernel makes every privilege decision on the numeric UID, so that account bypasses permission checks exactly like `root` does — with the bonus that its logins may not look suspicious in a report that only greps for the name `root`. Treat it as a compromise indicator, not a naming convenience, unless config management deliberately created it.
- Why is `grep alice /etc/passwd` a poor way to check whether an account exists?Because accounts can come from sources other than the file. NSS (`/etc/nsswitch.conf`) can add LDAP, SSSD or AD to the `passwd` database, and those accounts are never written locally. `getent passwd alice` asks every configured source and returns the record that will actually be used at login; the grep only sees local lines.
- What is the difference between a locked account and one with an empty password field in /etc/shadow?A locked account has `!` prepended to the hash, so no password can ever match it, but non-password authentication such as an SSH public key still works. An empty hash field means there is no password to check at all, which some PAM configurations will accept as successful authentication. The first is a control; the second is a vulnerability.
saying these in an interview costs you the question
- Thinking /etc/shadow is world-readable like /etc/passwd
- Claiming root is special because of the name root
- Saying /etc/group lists a user's primary group
- Confusing a locked account with a disabled login shell
- Assuming every account must appear in the local files