Most Linux users know /etc/passwd and /etc/shadow exist. Fewer know what every :-separated field does, or how small mistakes in these files can hand an attacker the whole system.
The one-line summary:
/etc/passwdstores who the user is./etc/shadowstores how and when they authenticate.
/etc/passwd: the identity card
hosni:x:1000:1000:Hosni Zaaraoui:/home/hosni:/bin/bash
| # | Field | Example | Meaning |
|---|---|---|---|
| 1 | Username | hosni |
The account name used to log in |
| 2 | Password placeholder | x |
The real hash lives in /etc/shadow
|
| 3 | UID | 1000 |
What Linux actually trusts for permissions |
| 4 | GID | 1000 |
Primary group ID |
| 5 | GECOS | Hosni Zaaraoui |
User info, usually the full name |
| 6 | Home | /home/hosni |
The user's personal folder |
| 7 | Shell | /bin/bash |
What launches after login |
A few UIDs worth remembering: 0 is root, 1-999 are system and service accounts, and 1000+ are regular users.
/etc/shadow: the vault
hosni:$y$j9T$abc123...:20359:0:99999:7:::
| # | Field | Example | Meaning |
|---|---|---|---|
| 1 | Username | hosni |
Matches /etc/passwd
|
| 2 | Password hash | $y$j9T$... |
The hash, never the real password |
| 3 | Last change | 20359 |
Days since 1 Jan 1970 (that's 2025-09-28) |
| 4 | Min age | 0 |
Days before it can be changed again |
| 5 | Max age | 99999 |
Password lifetime, effectively "never expires" |
| 6 | Warning | 7 |
Days of warning before expiry |
| 7 | Inactive | (empty) | Days after expiry before the account is disabled |
| 8 | Expire | (empty) | Day when the account permanently expires |
| 9 | Reserved | (empty) | Unused |
Convert the day number to a real date:
date -d "1970-01-01 +20359 days" +%F
# 2025-09-28
Why is the second field always x?
Early Unix stored the hash directly in /etc/passwd. But that file has to be world-readable, because countless programs need to map UIDs to names. So anyone could copy every hash and crack them offline.
The fix was shadow passwords: move the hashes to a file only privileged users can read. The x is not a password, it's a pointer meaning "look in /etc/shadow".
When you log in, Linux reads your line in /etc/passwd, sees x, fetches the hash from /etc/shadow, hashes what you typed, compares the two, then checks the aging rules.
Reading a hash
A modern hash is self-describing: $id$params$salt$hash.
| Prefix | Algorithm | Verdict |
|---|---|---|
$1$ |
MD5 | Broken, replace it |
$5$ |
SHA-256 | Dated |
$6$ |
SHA-512 | Long-time default, still common |
$y$ |
yescrypt | Modern default (Debian 11+, Ubuntu 22.04+, Fedora) |
Not every password field is a hash, either:
| Value | Meaning |
|---|---|
!hash |
Password login locked (the hash is kept) |
! / !!
|
Locked, or password never set |
* |
Password login disabled |
| (empty) | No password at all, which is critical |
A locked or disabled password does not always mean the account can't log in. SSH keys can still work. To really kill an account, also run chage -E 0 user and give it a nologin shell.
Common misconfigurations
1. Overly permissive /etc/shadow (critical). Expected permissions depend on the distro:
- Debian/Ubuntu:
640,root:shadow - RHEL/Fedora:
000,root:root - Hardened baselines:
600,root:root
If other users can read it, attackers can steal hashes and crack them offline with no lockouts and no logs.
2. Writable /etc/passwd. It should be 644, owned by root. If it's world-writable, anyone can add a UID 0 account.
3. A second UID 0 account. Anything with UID 0 is effectively root.
awk -F: '$3==0 {print $1}' /etc/passwd # expect: root only
4. Empty password fields. Depending on PAM's nullok, this may allow passwordless login.
sudo awk -F: '($2=="") {print $1}' /etc/shadow
5. Interactive shells on service accounts. www-data should use /usr/sbin/nologin, not /bin/bash.
grep -vE '(nologin|false)$' /etc/passwd
6. Weak password aging. 99999 means passwords never expire.
sudo chage -l hosni
sudo chage -M 90 -m 1 -W 7 hosni
Note that PASS_MAX_DAYS in /etc/login.defs only applies to new accounts, so existing users need chage.
A quick audit script
# extra root accounts
awk -F: '$3==0 {print $1}' /etc/passwd
# duplicate UIDs
cut -d: -f3 /etc/passwd | sort -n | uniq -d
# which hash algorithms are in use
sudo awk -F: '$2 ~ /^\$/ {split($2,a,"$"); print a[2]}' /etc/shadow | sort | uniq -c
# status of every account (P=usable, L=locked, NP=no password)
sudo passwd -Sa
# file integrity
ls -l /etc/passwd /etc/shadow /etc/passwd- /etc/shadow-
sudo pwck -r
Don't forget the backup files /etc/passwd- and /etc/shadow-. They deserve the same permissions as the originals.
Editing safely
Avoid opening these files in nano or vim. Tools like useradd, passwd and usermod validate the format, prevent duplicate UIDs and lock the files during changes. If you must edit by hand, use vipw and vipw -s.
One more tip: use getent passwd user instead of grep. On systems using LDAP, SSSD or Active Directory, users may not appear in /etc/passwd at all.
Takeaway
Know who they are (/etc/passwd). Control how they prove it (/etc/shadow).
Top comments (0)