LFCS 35 🐧 Account Aging and Expiration
Password aging and account aging are different things, and confusing them is one of the most common mistakes in user administration. Password aging controls how long a password remains valid. Account aging controls how long the account itself remains valid, regardless of the password. A contractor hired for six months needs an account that stops working after six months even if the password is changed on schedule. A temporary service account needs to expire when the project ends. A dormant account needs to be locked automatically when nobody has used it. The mechanisms that handle these requirements live in /etc/shadow, in the chage command, and in the system’s login enforcement. This chapter covers account expiration, inactivity lockout, and the distinction between the fields that control each.
Key point: Account expiration is a hard limit. When the expiration date passes, no login of any kind is possible, regardless of the password’s state. Inactivity lockout is a soft limit that applies after password expiration. The two are stored in separate fields in /etc/shadow and are set with different flags.
Why account aging exists
The temporary-account problem. Contractors, interns, and seasonal workers have a defined end date. The account should stop working on that date without anyone remembering to disable it. The chage -E flag sets the date, and the login system enforces it automatically.
The dormant-account problem. An account that has not been used in months is a risk. The user may have left, the password may have been shared, and nobody notices because nothing is being accessed. The inactivity field locks the account after a defined period of non-use following password expiration.
The forgotten-account problem. Administrators create accounts for projects and forget to clean them up. An account with no expiration date lives forever, and a forgotten account is an unmonitored entry point. Setting an expiration date at creation time makes the cleanup automatic.
The audit problem. Auditors ask which accounts are active, which have expirations, and which are dormant. The expiration fields in /etc/shadow are the authoritative answer. An account without an expiration date is an account the organization cannot bound in time.
The compliance problem. Regulations such as PCI DSS and HIPAA require that access be revoked when it is no longer needed. Account expiration is the technical control that enforces the policy without manual intervention.
The distinction problem. Password expiration and account expiration both use the word “expiration,” and both appear in the chage -l output. The difference is what happens after the deadline. A password that expires forces a change. An account that expires stops working entirely.
a. Account expiration with chage -E
The -E flag sets the date on which the account expires. The value is stored in the eighth field of /etc/shadow as the number of days since January 1, 1970.
The basic usage
sudo chage -E 2026-12-31 alice
The account is usable until December 31, 2026. On the next day, login is refused with a message that the account has expired.
The date formats
The -E flag accepts a date in YYYY-MM-DD format or a number of days since the epoch.
sudo chage -E 2026-12-31 alice
sudo chage -E 20819 alice # Same date as days since epoch
The human-readable form is preferred. The numeric form appears in /etc/shadow.
The special values
The value -1 removes the expiration. The value 0 sets the expiration to January 1, 1970, which is immediately in the past.
sudo chage -E -1 alice # No expiration
sudo chage -E 0 alice # Immediately expired
The -1 value is the way to clear an expiration that was set previously.
The verification
The chage -l output shows the account expiration in the “Account expires” line.
sudo chage -l alice
# Account expires : Dec 31, 2026
After the date passes, the same command shows the date in the past, and the login attempt fails.
The expired login message
When a user with an expired account tries to log in, the system refuses with a message that identifies the cause.
Your account has expired; please contact your system administrator
The message appears before the password prompt, so the user knows the account is disabled and not that the password is wrong.
The shadow field
The expiration is stored as the eighth field in /etc/shadow.
alice:$6$...:19737:7:90:14:30:20454:
The value 20454 is the number of days since the epoch corresponding to December 31, 2026. An empty field means no expiration.
The account expiration and password expiration
The two are independent. An account can have a password that expires every 90 days and an account expiration that is a year away. The password expiration forces a change; the account expiration disables the account.
sudo chage -M 90 alice # Password expires every 90 days.
sudo chage -E 2026-12-31 alice # Account expires on a date.
The chage -l output shows both lines separately.
b. Inactivity lockout with chage -I
The -I flag sets the number of days after password expiration before the account is locked. It is stored in the seventh field of /etc/shadow.
The basic usage
sudo chage -I 30 alice
If Alice’s password expires and she does not change it within 30 days, the account is locked. The lock is implemented by the login system, not by the password field, and the account appears locked in the chage -l output.
The special values
The value -1 disables the inactivity lock. The value 0 locks the account immediately when the password expires.
sudo chage -I -1 alice # No inactivity lock
sudo chage -I 0 alice # Lock immediately on expiration
The 0 value is aggressive. The user must change the password at the same login where they discover it has expired, which is sometimes the desired behavior for high-security accounts.
The relationship to password expiration
The inactivity period starts counting when the password expires. If the password expires on day 90 and the inactivity period is 30 days, the account locks on day 120. If the user changes the password on day 100, the count resets, and the account is not locked.
Day 0: Last password change
Day 90: Password expires
Day 100: User changes password
Day 120: Would have been the lock date, but the password was changed.
The inactivity field only matters if the user ignores the expiration.
The shadow field
The inactivity is stored in the seventh field.
alice:$6$...:19737:7:90:14:30:20454:
The value 30 means 30 days of inactivity after expiration before the lock. An empty field means no inactivity lock.
The lock message
When the inactivity lock is in effect, the login message differs from the account expiration message.
Your account has expired; please contact your system administrator
The message is the same on most systems, because the lock is implemented through the same mechanism as the expiration. The chage -l output distinguishes the two: the “Password inactive” line shows the date.
The unlocking
If the user returns after the inactivity lock, the administrator must reset the password or extend the inactivity period. The lock is not cleared by the user.
sudo chage -I -1 alice # Remove the inactivity lock
sudo passwd alice # Reset the password
sudo chage -d 0 alice # Force a change on next login
The combination resets the account to a usable state and forces the user to set a new password.
c. Combining policies and enforcement
Account expiration, inactivity, and password aging interact in specific ways. The interaction determines what actually happens when a deadline passes.
The three fields in chage -l
The chage -l output has three lines that look similar but mean different things.
Password expires : Apr 15, 2026
Password inactive : Apr 25, 2026
Account expires : Dec 31, 2026
- Password expires is the maximum days field. After this date, the user must change the password.
- Password inactive is the inactivity field. After this date, the account is locked if the password was not changed.
- Account expires is the account expiration field. After this date, the account is disabled regardless of the password.
The enforcement order
The login system checks the three conditions in order. The account expiration is checked first. If the account has expired, login is refused. If not, the password expiration is checked. If the password has expired, the user is prompted to change it. If the inactivity lock is in effect, the login is refused with the same message as the account expiration.
The policy combination
A typical policy sets all three.
sudo chage -M 90 -W 14 -I 30 -E 2026-12-31 alice
The password expires every 90 days. The user is warned 14 days before. If the password is not changed within 30 days after expiration, the account is locked. The account itself expires on December 31, 2026.
The temporary-account pattern
A temporary account has an expiration date and no other aging.
sudo useradd -m -s /bin/bash temp
sudo passwd temp
sudo chage -E 2026-06-30 temp
The account works until June 30, 2026, and then stops. No password aging is needed because the account is short-lived.
The dormant-account pattern
An account that must lock after inactivity needs the inactivity field set.
sudo chage -M 90 -I 30 alice
The password expires every 90 days. If the user does not change it within 30 days, the account locks. This handles the dormant case without a fixed end date.
The service-account pattern
A service account should not expire. The expiration and inactivity fields are set to disable aging.
sudo chage -M -1 -m 0 -I -1 -E -1 myservice
The password never expires, the inactivity lock is disabled, and the account never expires. The account is used only for running a service and cannot log in interactively because its shell is /usr/sbin/nologin.
The audit view
The expiration fields are visible to the administrator through chage -l, but a bulk audit needs a different approach. The awk command extracts the fields from /etc/shadow directly.
sudo awk -F: '$8 != "" { print $1, $8 }' /etc/shadow
The command prints the username and the expiration field for every account that has one. The field is in days since the epoch, and a conversion is needed for readability.
The conversion helper
A small script converts the numeric field to a readable date.
sudo awk -F: '$8 != "" && $8 > 0 {
"date -d \"1970-01-01 + " $8 " days\" +%Y-%m-%d" | getline d
print $1, d
}' /etc/shadow
The awk command runs date for each account and prints the username with the converted date. The result is a list of accounts with their expiration dates.
Complete Example Session
# ============================================
# PART 1: SET ACCOUNT EXPIRATION
# ============================================
sudo chage -E 2026-12-31 alice
sudo chage -l alice
# Account expires : Dec 31, 2026
# ============================================
# PART 2: VERIFY THE SHADOW FIELD
# ============================================
sudo grep alice /etc/shadow
# alice:$6$...:19737:7:90:14:30:20454:
# ============================================
# PART 3: SET INACTIVITY LOCK
# ============================================
sudo chage -I 30 alice
sudo chage -l alice
# Password inactive : Apr 25, 2026
# ============================================
# PART 4: COMBINE PASSWORD AND ACCOUNT AGING
# ============================================
sudo chage -M 90 -W 14 -I 30 -E 2026-12-31 alice
sudo chage -l alice
# Last password change : Jan 15, 2026
# Password expires : Apr 15, 2026
# Password inactive : May 15, 2026
# Account expires : Dec 31, 2026
# Minimum number of days : 0
# Maximum number of days : 90
# Number of days of warning : 14
# ============================================
# PART 5: TEMPORARY ACCOUNT
# ============================================
sudo useradd -m -s /bin/bash temp
sudo passwd temp
sudo chage -E 2026-06-30 temp
# ============================================
# PART 6: SERVICE ACCOUNT WITHOUT AGING
# ============================================
sudo useradd -r -s /usr/sbin/nologin myservice
sudo chage -M -1 -m 0 -I -1 -E -1 myservice
# ============================================
# PART 7: VERIFY AFTER EXPIRATION
# ============================================
# Simulate an expired account by setting the expiration to yesterday.
sudo chage -E $(date -d "yesterday" +%Y-%m-%d) alice
sudo chage -l alice
# Account expires : ...
su - alice
# Your account has expired; please contact your system administrator
# ============================================
# PART 8: REMOVE THE EXPIRATION
# ============================================
sudo chage -E -1 alice
sudo chage -l alice
# Account expires : never
# ============================================
# PART 9: AUDIT ALL EXPIRATIONS
# ============================================
sudo awk -F: '$8 != "" && $8 > 0 {
"date -d \"1970-01-01 + " $8 " days\" +%Y-%m-%d" | getline d
print $1, d
}' /etc/shadow
# alice 2026-12-31
# temp 2026-06-30
# ============================================
# PART 10: THE COMPLETE POLICY
# ============================================
# Regular user: password rotates, account expires in a year.
sudo chage -M 90 -m 7 -W 14 -I 30 -E 2027-01-01 alice
# Temporary user: fixed end date, no other aging.
sudo chage -M -1 -m 0 -I -1 -E 2026-06-30 temp
# Service account: no aging, no expiration.
sudo chage -M -1 -m 0 -I -1 -E -1 myservice
The ten parts showed setting account expiration, verifying the shadow field, setting the inactivity lock, combining policies, creating a temporary account, creating a service account, verifying after expiration, removing the expiration, auditing all expirations, and the complete policy patterns.
Quick Reference
The Three Aging Fields
| Field | chage Flag | Shadow Position | Effect |
|---|---|---|---|
| Maximum | -M | 5 | Password expires |
| Inactivity | -I | 7 | Account locks after expiration |
| Expiration | -E | 8 | Account disabled |
chage -E Values
| Value | Meaning |
|---|---|
YYYY-MM-DD | The expiration date |
-1 | No expiration |
0 | Immediately expired |
chage -I Values
| Value | Meaning |
|---|---|
30 | Lock 30 days after password expiration |
0 | Lock immediately on expiration |
-1 | No inactivity lock |
Policy Patterns
| Account Type | Policy |
|---|---|
| Regular user | -M 90 -W 14 -I 30 -E YYYY-MM-DD |
| Temporary | -E YYYY-MM-DD only |
| Dormant | -M 90 -I 30 |
| Service | -M -1 -m 0 -I -1 -E -1 |
Verification Commands
| Command | Purpose |
|---|---|
chage -l USER | Show aging info |
grep USER /etc/shadow | Show the raw fields |
awk -F: '$8 != ""' /etc/shadow | Audit expirations |
Best Practices
✅ Do This:
# Set an expiration date for temporary accounts.
sudo chage -E 2026-06-30 temp # ✅
# Combine password aging with inactivity lock.
sudo chage -M 90 -I 30 alice # ✅
# Disable aging for service accounts.
sudo chage -M -1 -I -1 -E -1 myservice # ✅
# Verify with chage -l.
sudo chage -l alice # ✅
# Audit expirations in bulk.
sudo awk -F: '$8 != "" && $8 > 0 { ... }' /etc/shadow # ✅
# Remove an expiration with -1.
sudo chage -E -1 alice # ✅
❌ Don’t Do This:
# Don't confuse password and account expiration.
sudo chage -M 90 alice # ❌ This is password aging, not account.
# Don't leave temporary accounts without expiration.
sudo useradd temp # ❌ Account lives forever
# Don't set expiration without documenting it.
sudo chage -E 2026-06-30 temp # ❌ Note the date somewhere
# Don't apply aging to service accounts.
sudo chage -M 90 myservice # ❌ Service account locks
# Don't forget the inactivity lock.
sudo chage -M 90 alice # ❌ Expired password remains changeable forever
# Don't use -E 0 unless immediate expiration is intended.
sudo chage -E 0 alice # ❌ Account disabled immediately
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Account locked unexpectedly | Expiration date passed | Remove with chage -E -1 |
| Temporary account still works | No expiration set | Set with chage -E |
| Dormant account remains active | No inactivity lock | Set with chage -I 30 |
| Confusing the two expirations | Same word used for both | Check chage -l for both lines |
| Service account locked | Password aging applied | Set -M -1 -E -1 |
| Password change resets expiration | Misunderstanding of fields | The account expiration is independent |
| Audit missing accounts | Expiration not set | Audit with awk and set dates |
Real-World Examples
1. Set Account Expiration
sudo chage -E 2026-12-31 alice
2. Set Inactivity Lock
sudo chage -I 30 alice
3. Combine Policies
sudo chage -M 90 -W 14 -I 30 -E 2026-12-31 alice
4. Temporary Account
sudo useradd -m temp && sudo passwd temp && sudo chage -E 2026-06-30 temp
5. Service Account
sudo chage -M -1 -m 0 -I -1 -E -1 myservice
6. Remove Expiration
sudo chage -E -1 alice
7. Verify Expiration
sudo chage -l alice | grep "Account expires"
8. Audit All Expirations
sudo awk -F: '$8 != "" && $8 > 0 { print $1, $8 }' /etc/shadow
9. Convert Days to Date
date -d "1970-01-01 + 20454 days" +%Y-%m-%d
10. Test Expiration
sudo chage -E $(date -d yesterday +%Y-%m-%d) alice && su - alice
Visual
The Three Aging Fields
┌────────────────────────────────────────────────────────────┐
│ THREE AGING FIELDS IN /etc/shadow │
│ │
│ alice:$6$...:19737:7:90:14:30:20454: │
│ ──┬── ──┬── ──┬─ │ │ │ │ ──┬── │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ └─ ACCOUNT EXPIRATION │
│ │ │ │ │ │ │ │ (chage -E) │
│ │ │ │ │ │ │ │ Hard limit. │
│ │ │ │ │ │ │ └─ INACTIVITY LOCK │
│ │ │ │ │ │ │ (chage -I) │
│ │ │ │ │ │ │ Locks after password expiry. │
│ │ │ │ │ │ └─ warning days │
│ │ │ │ │ └─ MAXIMUM (chage -M) │
│ │ │ │ └─ minimum days │
│ │ │ └─ last password change │
│ │ └─ password hash │
│ └─ username │
└────────────────────────────────────────────────────────────┘
The Timeline
┌────────────────────────────────────────────────────────────┐
│ PASSWORD AGING AND ACCOUNT EXPIRATION │
│ │
│ Day 0: Last password change │
│ │ │
│ ├─────────────────────────────────────▶ Day 90 │
│ │ Password expires│
│ │ │
│ ├─────────────────────────────────────▶ Day 104 │
│ │ Warning ends │
│ │ │
│ ├─────────────────────────────────────▶ Day 120 │
│ │ Account locks │
│ │ (inactivity) │
│ │ │
│ ├─────────────────────────────────────▶ Day 365 │
│ │ Account expires │
│ │ (chage -E) │
│ │
│ The password expiration forces a change. │
│ The inactivity lock disables the account if ignored. │
│ The account expiration disables the account regardless. │
└────────────────────────────────────────────────────────────┘
The chage -l Output
┌────────────────────────────────────────────────────────────┐
│ chage -l alice │
│ │
│ Last password change : Jan 15, 2026 │
│ Password expires : Apr 15, 2026 ← maximum │
│ Password inactive : May 15, 2026 ← inactivity │
│ Account expires : Dec 31, 2026 ← expiration │
│ Minimum number of days : 7 │
│ Maximum number of days : 90 │
│ Number of days of warning : 14 │
│ │
│ Three different dates, three different fields. │
│ "Password expires" → chage -M │
│ "Password inactive" → chage -I │
│ "Account expires" → chage -E │
└────────────────────────────────────────────────────────────┘
The Policy Patterns
┌────────────────────────────────────────────────────────────┐
│ POLICY PATTERNS BY ACCOUNT TYPE │
│ │
│ REGULAR USER │
│ ┌──────────────────────────────────────────────────┐ │
│ │ chage -M 90 -W 14 -I 30 -E 2027-01-01 │ │
│ │ Password rotates, account bounded in time. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ TEMPORARY ACCOUNT │
│ ┌──────────────────────────────────────────────────┐ │
│ │ chage -E 2026-06-30 │ │
│ │ Fixed end date, no other aging needed. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ DORMANT ACCOUNT │
│ ┌──────────────────────────────────────────────────┐ │
│ │ chage -M 90 -I 30 │ │
│ │ Locks after inactivity, no fixed end. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ SERVICE ACCOUNT │
│ ┌──────────────────────────────────────────────────┐ │
│ │ chage -M -1 -m 0 -I -1 -E -1 │ │
│ │ No aging, no expiration. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Account expiration | chage -E DATE, shadow field 8 |
| Inactivity lock | chage -I DAYS, shadow field 7 |
| Password expiration | chage -M DAYS, shadow field 5 |
-1 value | Disables the field |
0 value | Immediate expiration or lock |
| Temporary account | -E DATE only |
| Service account | -M -1 -m 0 -I -1 -E -1 |
| Dormant account | -M 90 -I 30 |
| Audit command | awk -F: '$8 != ""' /etc/shadow |
Key takeaways:
- Account expiration and password expiration are different. Account expiration disables the account entirely. Password expiration forces a password change. They are stored in separate fields and set with different flags.
- The
-Eflag sets the account expiration. The value is a date inYYYY-MM-DDformat, or a number of days since the epoch. The value-1removes the expiration, and0expires the account immediately. - The
-Iflag sets the inactivity lock. The account locks this many days after the password expires if the password was not changed. The value-1disables the lock. - Temporary accounts need an expiration date. A contractor or intern account should have a date by which it stops working. The
chage -Eflag sets it, and the login system enforces it. - Service accounts should not have aging. The password, inactivity, and expiration fields should all be disabled. The account’s shell is
/usr/sbin/nologin, so it cannot log in interactively in any case. - The
chage -loutput shows three dates. “Password expires,” “Password inactive,” and “Account expires” are three different fields with three different effects. Reading the output correctly is what prevents the confusion. - Auditing requires reading
/etc/shadow. Thechage -lcommand shows one user at a time. Theawkcommand extracts the expiration field for all users, anddateconverts the numeric value to a readable date.
Remember: Account aging is the policy that bounds an account’s lifetime. The account expiration date is a hard limit: after the date, no login of any kind is possible. The inactivity lock is a soft limit: it applies after the password expires if the user does not change it. The two are stored in separate fields in /etc/shadow and are set with the chage -E and chage -I flags. The password aging fields are separate again, and the three combine into policies that match the account’s purpose: temporary accounts get a date, dormant accounts get an inactivity lock, service accounts get no aging at all. The result is a system where every account has a defined lifetime, and where the login system enforces the policy without manual intervention.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!