/etc/shadow File Format and Password Aging in Linux
Learn how Linux /etc/shadow stores password hashes, account-aging fields, expiration dates, locks, and safe administration commands.
/etc/shadow is the protected local-account database used by Linux systems. It stores password-verification data and password-aging metadata for local users. Understanding its fields helps administrators diagnose password expiration, account locks, and temporary-account policies.
Purpose of /etc/shadow
A password hash is a one-way derived value used to verify a submitted password. During authentication, the system processes the password supplied by the user and compares the resulting hash with the stored value. It does not normally decrypt a stored password, because the original password is not stored in reversible form.
Password-hash records are separated from the general account information in /etc/passwd. Many programs need to read usernames, user IDs, home directories, or login shells, but they should not receive password-verification data. The separation allows broader read access to basic account information while protecting sensitive hash data.
Access control and security
Ordinary users normally cannot read /etc/shadow. Access is generally limited to root and privileged account-management utilities, although the exact owner, group, and permission arrangement varies by distribution.
Do not copy the file unnecessarily, expose it in logs or bug reports, or make it world-readable. Do not broadly change its permissions to solve an access problem. Use authorized tools such as chage and passwd instead of editing the file directly.
Record structure
The file contains one account record per line. Each record has colon-delimited fields:
username:password:lastchg:min:max:warn:inactive:expire:reservedThe conventional layout has nine fields. A field can be empty, and the meaning of an empty field depends on that field's position. The following example uses an abbreviated, fictional hash:
alice:$y$j9T$example...:16182:0:30:7:::| Position | Field name | Example value | Meaning | Empty or special-value behavior |
|---|---|---|---|---|
| 1 | username | alice | Local login name, corresponding to the username in /etc/passwd. | Should identify the account record. |
| 2 | password | $y$j9T$example... | Password hash, often represented using modular crypt format. | Markers such as ! or * commonly make password authentication unusable. An empty value has security implications and depends on PAM and distribution policy. |
| 3 | lastchg | 16182 | Days since 1970-01-01 UTC when the password was last changed. | 0 generally forces a password change at the next successful login. |
| 4 | min | 0 | Minimum number of days before the password may be changed again. | 0 permits an immediate change. |
| 5 | max | 30 | Maximum number of days the password remains valid. | A large value such as 99999 is effectively no expiration in many configurations, but policy and tool behavior should be checked. |
| 6 | warn | 7 | Number of days before password expiration during which warnings are shown. | Behavior for an empty value can vary by policy and implementation. |
| 7 | inactive | empty | Number of days after password expiration before password-based use is disabled because the password was not changed. | An empty field is not the same as a numeric inactivity period; it normally means no post-expiration inactivity interval is configured. |
| 8 | expire | empty | Absolute account-expiration date, expressed as days since the Unix epoch. | Empty normally means no account-expiration date is configured. |
| 9 | reserved | empty | Reserved for future use. | Normally empty; do not repurpose it. |
Understanding the password field
The password field normally contains a password hash. It may use the modular crypt format, a representation that can encode an algorithm identifier, options, a salt, and the resulting hash. A salt is stored with the hash data so that identical passwords do not produce identical stored values.
| Password-field pattern | Typical interpretation | Administrative implication |
|---|---|---|
| Normal modular hash | Password authentication can be checked against the stored hash. | Still protect the file; hashes are sensitive even though they are not plaintext passwords. |
Leading exclamation mark, such as !$y$... | Password authentication is commonly locked. | Use passwd -l or another supported tool to manage the lock. Other authentication methods may be governed separately. |
| Asterisk or another unusable value | Password login is commonly prevented; this is frequent for service or system accounts. | Do not assume the account is disabled in every possible authentication path. |
| Empty field | No stored password hash is present. | This is not a normal secure configuration. Whether login is accepted depends on PAM and distribution policy; investigate and correct it through supported administration tools. |
Converting lastchg and expire values
The Unix epoch is the reference date 1970-01-01. In shadow records, lastchg and expire use a count of days from that date, rather than a human-readable calendar date.
For example, a record with lastchg=16182 has a password-change date of 2014-04-22. On GNU/Linux, convert it with:
date -u -d '1970-01-01 + 16182 days' +%FUsing UTC avoids confusion caused by local time zones. For administrative verification, prefer the readable output from chage -l.
Password and account aging
Password expiration and account expiration are separate controls. max limits the password's lifetime; warn controls advance warnings; inactive controls what happens after an expired password remains unchanged; and expire sets an absolute end date for the account itself.
| Event | Relevant field | When it occurs | Effect on the user |
|---|---|---|---|
| Password changed | lastchg | When the password is set or changed. | The password-aging clock starts again. |
| Warning interval begins | warn and max | warn days before the password reaches its maximum age. | The user receives password-expiration warnings. |
| Password expires | max | After the maximum password age has elapsed. | The user is generally required to change the password before normal password use continues. |
| Inactive interval ends | inactive | The configured number of days after password expiration. | Password-based account use may be disabled because the expired password was not changed. |
| Account reaches absolute expiration date | expire | On the configured epoch-day date. | The account can be prevented from use independently of the password's age. |
For the sample values lastchg=16182, max=30, and warn=7, the password was changed on 2014-04-22. It becomes due for change 30 days later, and warnings begin seven days before that due date. Because inactive and expire are empty, the record does not show a configured post-expiration inactivity interval or fixed account-expiration date.
A different record may have password aging but no account expiration:
alice:$y$j9T$example...:16182:0:30:7:::Here, max and warn are populated while expire is empty. A password can therefore expire even though no fixed date has been configured to end the account.
For a temporary contractor account, an expire value can be populated:
contractor:$y$j9T$example...:19800:0:60:14:7:20000:This account has a password lifetime, a seven-day post-expiration inactivity period, and an absolute account-expiration date. The absolute date is separate from the password's expiration schedule and can prevent account use even if the password is otherwise current.
Safe inspection and administration
Use account-management commands rather than manually editing /etc/shadow. When account sources may be configured through the Name Service Switch, getent is preferable because it follows the configured account lookup mechanism:
sudo getent shadow USERNAMETo inspect one exact local record directly:
sudo grep '^USERNAME:' /etc/shadowThe direct command is useful for local-file inspection, but it does not account for non-file account sources. Display password-aging information in readable form with:
sudo chage -l USERNAMESome passwd implementations also provide concise status information:
sudo passwd -S USERNAMESet a minimum age of zero days, a maximum age of 30 days, and a seven-day warning period with:
sudo chage -m 0 -M 30 -W 7 USERNAMESet an absolute account-expiration date with a supported tool:
sudo chage -E YYYY-MM-DD USERNAMELock password authentication using the password-management utility:
sudo passwd -l USERNAMEChanges to local account records should be coordinated with /etc/passwd and performed through supported tools. Direct edits risk malformed records, inconsistent account data, or unexpected behavior from PAM and other authentication components.
Troubleshooting
The user is unexpectedly required to change a password
- Run
sudo chage -l USERNAMEand inspect the last-change date and maximum age. - Check whether
lastchgis zero, which generally forces a change at the next successful login. - Check whether the password has passed its maximum age.
- Verify the system date and time if the calculation appears incorrect.
The user cannot log in after the password expired
- Determine whether the password is merely expired or whether the configured
inactiveperiod has elapsed. - Check the inactive period and account-expiration date with
sudo chage -l USERNAME. - Use authorized recovery procedures rather than modifying
/etc/shadowby hand.
A password reset does not permit authentication
- Check whether the password field is locked with a leading
!. - Check whether an absolute account-expiration date has passed.
- Review PAM policy, the login shell, account status, and other restrictions that can prevent login independently of the password hash.
The computed expiration date differs by one day
- Use UTC for epoch-day conversion.
- Remember that policy comparisons may use calendar-date boundaries rather than an elapsed 24-hour interval.
- Use
chage -las the primary human-readable verification tool.
An unprivileged user receives permission denied
This is normally expected. Use sudo only when authorized, and do not weaken /etc/shadow permissions.
Exam-relevant notes
/etc/shadowstores password hashes and aging metadata, not ordinary plaintext passwords.- Fields are colon-delimited, with one account record per line.
lastchg,expire, and related date values use days since the Unix epoch.minis the waiting period before another password change;maxis the password lifetime;warnis the advance-warning period.inactiveis a post-password-expiration inactivity setting, not simply the current disabled state.expireis an absolute account-expiration date and is independent of password expiration.- A leading
!commonly locks password authentication, while*or another invalid hash commonly prevents password login. - Use
passwd,chage, and other supported tools instead of directly editing the shadow file.
For related Linux command and filesystem fundamentals, see Linux and Bourne Again Shell (Bash).