RED HAT ENTERPRISE LINUX
Passwords and
Password Aging
Change passwords and adjust password aging for local user accounts
CIS126RH | RHEL System Administration 1
Mesa Community College
Password management goes beyond simply setting a password. Administrators must enforce
password aging policies that require users to change passwords regularly, prevent them
from reusing a new password too soon, warn them before expiry, and disable accounts
after extended inactivity. On RHEL 9, passwd handles immediate password
operations and chage (change age) manages the full lifecycle of password
aging stored in /etc/shadow. Both are tested on the RHCSA exam.
Learning Objectives
-
Manage passwords with passwd —
Set, expire, lock, and unlock passwords for local user accounts
using
passwdand understand how each operation changes/etc/shadow - Read and interpret /etc/shadow — Decode all eight fields of the shadow file and explain what each password aging field controls
-
Configure password aging with chage —
Use
chageflags and interactive mode to set maximum age, minimum age, warning period, inactivity timeout, and account expiry date -
Set system-wide defaults with /etc/login.defs —
Explain how
/etc/login.defscontrols the default password aging values applied to all newly created accounts
/etc/shadow: The Password Database
All password data — hashes and aging policy — is stored in /etc/shadow,
readable only by root.
# View an entry (requires root)
$ sudo grep ^alice: /etc/shadow
alice:$6$salt$hashedpw:19500:0:90:7:14:20000:
# 1 2 3 4 5 6 7 8 9
| Field | Name | Example | Meaning |
|---|---|---|---|
| 1 | Username | alice | Login name |
| 2 | Password hash | $6$salt$hash | Encrypted password; ! or !! = locked; * = no login |
| 3 | Last change | 19500 | Days since epoch (1970-01-01) of last password change |
| 4 | Minimum age | 0 | Minimum days before user can change password again |
| 5 | Maximum age | 90 | Days until password expires and must be changed |
| 6 | Warning | 7 | Days before expiry to warn the user |
| 7 | Inactive | 14 | Days after expiry before account is disabled |
| 8 | Expire | 20000 | Account expiry in days since epoch (not password expiry) |
| 9 | Reserved | (empty) | Unused; always empty |
passwd: Basic Password Operations
# Set or change a user's password (prompts interactively)
$ sudo passwd alice
Changing password for user alice.
New password:
Retype new password:
passwd: all authentication tokens updated successfully.
# A user can change their own password (no sudo needed)
$ passwd
Changing password for alice.
Current password:
New password:
Retype new password:
passwd: all authentication tokens updated successfully.
# Set password non-interactively (for scripts)
$ echo "alice:newpassword123" | sudo chpasswd
# Check password status
$ sudo passwd -S alice
alice PS 2026-05-25 0 90 7 14 (Password set, SHA512 crypt.)
# Format: USER STATUS DATE MIN MAX WARN INACTIVE
# Status: PS=set, LK=locked, NP=no password
passwd -S USERNAME shows a human-readable summary of the password
status and aging settings from /etc/shadow — without the days-since-epoch
arithmetic. Use it for quick status checks.
passwd: Locking, Unlocking, and Expiring
# Lock an account — prepends ! to password hash in /etc/shadow
$ sudo passwd -l alice
Locking password for user alice.
passwd: Success
# Verify the lock in /etc/shadow
$ sudo grep ^alice: /etc/shadow
alice:!$6$salt$hashedpw:19500:0:90:7:14::
# ^ ! indicates locked
# Unlock the account — removes the leading !
$ sudo passwd -u alice
Unlocking password for user alice.
passwd: Success
# Expire a password immediately (force change at next login)
# Sets the last-change field (field 3) to 0 in /etc/shadow
$ sudo passwd -e alice
Expiring password for user alice.
passwd: Success
# Verify — last change is now 0
$ sudo grep ^alice: /etc/shadow
alice:$6$salt$hashedpw:0:0:90:7:14::
chage: Viewing Password Aging
chage -l (list) displays all password aging settings for a user
in human-readable form — converting the days-since-epoch numbers to dates.
$ sudo chage -l alice
Last password change : May 25, 2026
Password expires : Aug 23, 2026
Password inactive : Sep 6, 2026
Account expires : never
Minimum number of days between changes : 0
Maximum number of days between changes : 90
Number of days of warning before expiry : 7
# Reading the output:
# "Password expires" = last change + maximum age
# "Password inactive" = password expiry + inactive period
# "Account expires" = absolute account expiry date (field 8)
# "Password inactive" ≠ "Account expires" — different mechanisms
# A user can view their own aging info (no sudo needed)
$ chage -l alice # works as alice or as root
Always use sudo chage -l USERNAME after configuring password aging
to confirm all values are set correctly. The human-readable dates are easier to
verify than the raw days-since-epoch numbers in /etc/shadow.
chage: Setting Password Aging Flags
# Set maximum password age (days before password expires)
$ sudo chage -M 90 alice # must change every 90 days
# Set minimum password age (days before user can change again)
$ sudo chage -m 7 alice # cannot change more than once a week
# Set warning period (days before expiry to warn user)
$ sudo chage -W 14 alice # warn 14 days before expiry
# Set inactive period (days after expiry before account disabled)
$ sudo chage -I 30 alice # disable account 30 days after expiry
# Set account expiry date (absolute date, regardless of password)
$ sudo chage -E 2026-12-31 alice
# Remove account expiry (set to -1 = never expires)
$ sudo chage -E -1 alice
# Force password change on next login (set last-change to 0)
$ sudo chage -d 0 alice
# Set multiple values in one command
$ sudo chage -M 90 -m 7 -W 14 -I 30 alice
chage Flags Reference
| Flag | Long form | /etc/shadow field | Effect |
|---|---|---|---|
-l | --list | all fields | Display aging info in human-readable format |
-d DATE | --lastday | field 3 | Set last password change date (0 = force change now) |
-m DAYS | --mindays | field 4 | Minimum days before password can be changed |
-M DAYS | --maxdays | field 5 | Maximum password age; password expires after this many days |
-W DAYS | --warndays | field 6 | Days before expiry to warn the user at login |
-I DAYS | --inactive | field 7 | Days after expiry before account is permanently disabled |
-E DATE | --expiredate | field 8 | Absolute account expiry date (YYYY-MM-DD or -1 for never) |
Lowercase -m = minimum days (smallest number).
Uppercase -M = maximum days (largest number).
A common exam trap — mix them up and the policy is inverted.
chage Interactive Mode
Running chage without flags (except the username) opens an interactive
wizard that prompts for each aging setting in sequence.
$ sudo chage alice
Changing the aging information for alice
Enter the new value, or press ENTER for the default
Minimum Password Age [0]: 7
Maximum Password Age [99999]: 90
Last Password Change (YYYY-MM-DD) [2026-05-25]: ENTER
Password Expiration Warning [7]: 14
Password Inactive [-1]: 30
Account Expiration Date (YYYY-MM-DD) [-1]: 2026-12-31
# Verify all changes
$ sudo chage -l alice
Last password change : May 25, 2026
Password expires : Aug 23, 2026
Password inactive : Sep 22, 2026
Account expires : Dec 31, 2026
Minimum number of days between changes : 7
Maximum number of days between changes : 90
Number of days of warning before expiry : 14
The Password Aging Timeline
Understanding the sequence of events in the password lifecycle prevents confusion between password expiry and account expiry.
# With these settings on alice's account:
# Last change: May 25, 2026 (-d)
# Minimum age: 7 days (-m) → can change after Jun 1
# Maximum age: 90 days (-M) → must change by Aug 23
# Warning: 14 days (-W) → warned from Aug 9 onward
# Inactive: 30 days (-I) → disabled Sep 22 if not changed
# Account expiry: Dec 31, 2026 (-E) → hard cutoff regardless
| Date | Event | What happens |
|---|---|---|
| May 25 | Password changed | Clock resets; aging begins |
| Jun 1 (+7) | Minimum age passed | User may now change their password |
| Aug 9 (+76) | Warning begins | Login shows "password expires in N days" message |
| Aug 23 (+90) | Password expires | User must change password at next login |
| Sep 22 (+30 inactive) | Account disabled | Account locked — cannot log in even to change password |
| Dec 31 | Account expiry | Account locked regardless of password status |
/etc/login.defs: System-Wide Defaults
/etc/login.defs sets the default password aging values applied
to all newly created accounts. Existing accounts are not affected by changes here.
# View password aging defaults
$ grep -E "PASS_MAX_DAYS|PASS_MIN_DAYS|PASS_MIN_LEN|PASS_WARN_AGE" \
/etc/login.defs
PASS_MAX_DAYS 99999
PASS_MIN_DAYS 0
PASS_MIN_LEN 5
PASS_WARN_AGE 7
# Edit to enforce organisation-wide defaults for NEW accounts
$ sudo vim /etc/login.defs
# Change PASS_MAX_DAYS 99999 to PASS_MAX_DAYS 90
# Change PASS_MIN_DAYS 0 to PASS_MIN_DAYS 7
# Change PASS_WARN_AGE 7 to PASS_WARN_AGE 14
# Verify the change
$ grep PASS_MAX_DAYS /etc/login.defs
PASS_MAX_DAYS 90
# These defaults only apply to accounts created AFTER the change
# Existing accounts must be updated individually with chage
Changing /etc/login.defs does not retroactively update existing
accounts. To apply a policy to all existing users, use chage on
each account individually or loop over them with a shell script.
Forcing Password Change at Next Login
A common administrative task: create a user, set an initial password, and force them to choose their own password when they first log in.
# Method 1: passwd -e (expire immediately)
$ sudo useradd newstaff
$ sudo passwd newstaff # set initial password
$ sudo passwd -e newstaff # force change on next login
# Method 2: chage -d 0 (set last change to epoch)
$ sudo chage -d 0 newstaff
# Verify
$ sudo chage -l newstaff
Last password change : password must be changed
Password expires : password must be changed
Password inactive : password must be changed
...
# When newstaff next logs in:
WARNING: Your password has expired.
You must change your password now and login again!
New password:
Practical Scenario: Applying a Password Policy
Apply a complete password aging policy to user alice:
90-day maximum, 7-day minimum, 14-day warning, 30-day inactive grace period.
# Step 1: Check current settings
$ sudo chage -l alice
Maximum number of days between changes : 99999 ← needs to change
# Step 2: Apply the complete policy in one chage command
$ sudo chage -M 90 -m 7 -W 14 -I 30 alice
# Step 3: Verify all settings applied correctly
$ sudo chage -l alice
Last password change : May 25, 2026
Password expires : Aug 23, 2026
Password inactive : Sep 22, 2026
Account expires : never
Minimum number of days between changes : 7
Maximum number of days between changes : 90
Number of days of warning before expiry : 14
# Step 4: Also update /etc/login.defs for future accounts
$ sudo sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' /etc/login.defs
$ sudo sed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE 14/' /etc/login.defs
Password and Aging Quick Reference
| Task | Command |
|---|---|
| Set a user's password | sudo passwd USERNAME |
| Set password (non-interactive) | echo "USER:PASS" | sudo chpasswd |
| Check password status | sudo passwd -S USERNAME |
| Lock an account | sudo passwd -l USERNAME |
| Unlock an account | sudo passwd -u USERNAME |
| Expire password (force change) | sudo passwd -e USERNAME or sudo chage -d 0 USERNAME |
| View all aging settings | sudo chage -l USERNAME |
| Set maximum password age | sudo chage -M DAYS USERNAME |
| Set minimum password age | sudo chage -m DAYS USERNAME |
| Set warning period | sudo chage -W DAYS USERNAME |
| Set inactive grace period | sudo chage -I DAYS USERNAME |
| Set account expiry date | sudo chage -E YYYY-MM-DD USERNAME |
| Remove account expiry | sudo chage -E -1 USERNAME |
| Interactive aging setup | sudo chage USERNAME |
| Set default aging for new accounts | Edit /etc/login.defs |
Common Mistakes
| Mistake | What goes wrong | Correct approach |
|---|---|---|
Confusing chage -m and chage -M |
Minimum and maximum ages are swapped — policy is inverted (e.g. max=7 days, min=90 days) | Lowercase -m = minimum; uppercase -M = maximum. Verify with chage -l |
Confusing -I (inactive) with -E (expiry date) |
Wrong field is set — account locked by wrong mechanism | -I sets days-after-expiry grace; -E sets absolute calendar date |
| Changing /etc/login.defs and expecting existing users to be affected | login.defs only applies to new accounts — existing users are not updated | Run chage on each existing user individually to apply the new policy |
Locking with passwd -l but forgetting SSH keys still work |
User cannot log in with password but can still access the system via SSH key | To fully block access also expire or remove their SSH authorized_keys, or use usermod -L combined with account expiry |
Not verifying with chage -l after setting aging values |
Typos or flag confusion go undetected until the policy fails to enforce correctly | Always run sudo chage -l USERNAME after any aging change to confirm |
Using chage -E 0 thinking it means "never expires" |
Day 0 = January 1, 1970 — the account is immediately locked | Use chage -E -1 to set no expiry date |
Reading /etc/shadow After chage
After running chage, examine /etc/shadow directly to
confirm which fields were modified and understand the raw values.
# Before: default settings
$ sudo grep ^alice: /etc/shadow
alice:$6$hash:19500:0:99999:7:-1::
# min max warn inactive
# After: chage -M 90 -m 7 -W 14 -I 30 alice
$ sudo grep ^alice: /etc/shadow
alice:$6$hash:19500:7:90:14:30::
# min max warn inactive
# After: passwd -l alice (locked)
$ sudo grep ^alice: /etc/shadow
alice:!$6$hash:19500:7:90:14:30::
# ^ ! = locked
# After: chage -d 0 alice (force change)
$ sudo grep ^alice: /etc/shadow
alice:$6$hash:0:7:90:14:30::
# ^ 0 = password must be changed
Knowledge Check
Answer these before moving to the next slide.
- Write the commands to lock user
bob's account and then unlock it. What indicator in/etc/shadowshows the account is locked? - Write the single
chagecommand to configure userbobwith: maximum password age of 60 days, minimum of 5 days, warning of 10 days, and inactive grace period of 14 days. - Write the command to force bob to change his password the next time he logs in. Name two ways to achieve this.
- What is the difference between
chage -I 30andchage -E 2026-12-31? - A student runs
chage -m 90 -M 7 bob. What is wrong with this configuration, and what should the correct command be? - An administrator changes
PASS_MAX_DAYSfrom 99999 to 90 in/etc/login.defs. Does this affect useralicewho was created yesterday? Why or why not?
Knowledge Check — Answers
- Lock:
sudo passwd -l bob. Unlock:sudo passwd -u bob.
A locked account has a!prepended to the password hash in field 2 of/etc/shadow:bob:!$6$hash... sudo chage -M 60 -m 5 -W 10 -I 14 bob- Method 1:
sudo passwd -e bob
Method 2:sudo chage -d 0 bob
Both set the last-change date in/etc/shadowfield 3 to 0, which causes the system to treat the password as immediately expired. chage -I 30sets the inactive grace period — a relative number of days after the password expires during which the user can still log in and change their password. After 30 days past expiry, the account is disabled.
chage -E 2026-12-31sets the absolute account expiry date — regardless of password status, the account is locked on that specific calendar date.- The flags are reversed:
-m(lowercase) is minimum and-M(uppercase) is maximum. The command sets minimum=90 days and maximum=7 days, which is impossible — the minimum cannot exceed the maximum. The correct command:sudo chage -m 7 -M 90 bob - No — it does not affect alice.
/etc/login.defsonly sets the defaults used whenuseraddcreates new accounts. Alice's aging settings were copied from login.defs at account creation time and are now stored in/etc/shadow. To update alice's policy, runsudo chage -M 90 aliceexplicitly.
Key Takeaways
-
passwdmanages the password itself;chagemanages aging policy.passwd -l/-ulocks and unlocks.passwd -eexpires immediately.passwd -Sshows current status. All changes are written to/etc/shadow. -
Know all eight
/etc/shadowfields and what controls each one. Fields 4–7 are the aging policy: minimum age, maximum age, warning days, inactive period. Field 8 is the absolute account expiry date. Field 3 set to 0 forces a password change. Field 2 starting with!means the account is locked. -
chageflags: lowercase-m= minimum; uppercase-M= maximum.-W= warning days.-I= inactive grace period (relative to expiry).-E= account expiry date (absolute).-d 0= force password change. Always verify withchage -l USERNAME. -
/etc/login.defssets defaults for new accounts only. ChangePASS_MAX_DAYS,PASS_MIN_DAYS, andPASS_WARN_AGEto enforce organisation-wide policies for future users. Applychageindividually to existing accounts — login.defs changes do not retroactively update them.
Graded Lab
- Create user
labpassand set a password. Runsudo chage -l labpassto record the default aging settings. View the raw/etc/shadowentry withsudo grep labpass /etc/shadowand identify each field. - Apply a complete password policy to
labpass: maximum age 45 days, minimum age 3 days, warning 10 days, inactive grace 7 days. Use a singlechagecommand. Verify all four settings withchage -l labpass. - Force
labpassto change their password on next login usingpasswd -e labpass. Verify withchage -l labpassthat "password must be changed" appears. Then runsudo chage -d 0 labpassand confirm the /etc/shadow field 3 is 0. - Lock
labpasswithpasswd -l labpass. Confirm the ! appears in/etc/shadow. Attempt a login aslabpassto confirm it fails. Unlock withpasswd -u labpassand confirm the ! is removed. - Set an account expiry date of 2026-12-31 on
labpasswithchage -E 2026-12-31. Verify withchage -l labpassthat "Account expires: Dec 31, 2026" appears. Then remove the expiry withchage -E -1and confirm. - Edit
/etc/login.defsto setPASS_MAX_DAYS 60. Create a new usernewdefaultsand check their aging withchage -l— confirm they inherit the 60-day maximum. Confirm thatlabpassstill has 45 days (the /etc/login.defs change did not affect existing accounts).
"Change passwords and adjust password aging for local user accounts."
Know passwd -l/-u/-e/-S, chage -l/-M/-m/-W/-I/-E/-d,
and /etc/login.defs for new-account defaults.