| |

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

Fieldchage FlagShadow PositionEffect
Maximum-M5Password expires
Inactivity-I7Account locks after expiration
Expiration-E8Account disabled

chage -E Values

ValueMeaning
YYYY-MM-DDThe expiration date
-1No expiration
0Immediately expired

chage -I Values

ValueMeaning
30Lock 30 days after password expiration
0Lock immediately on expiration
-1No inactivity lock

Policy Patterns

Account TypePolicy
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

CommandPurpose
chage -l USERShow aging info
grep USER /etc/shadowShow the raw fields
awk -F: '$8 != ""' /etc/shadowAudit 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

PitfallWhy It HappensFix
Account locked unexpectedlyExpiration date passedRemove with chage -E -1
Temporary account still worksNo expiration setSet with chage -E
Dormant account remains activeNo inactivity lockSet with chage -I 30
Confusing the two expirationsSame word used for bothCheck chage -l for both lines
Service account lockedPassword aging appliedSet -M -1 -E -1
Password change resets expirationMisunderstanding of fieldsThe account expiration is independent
Audit missing accountsExpiration not setAudit 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

ItemValue
Account expirationchage -E DATE, shadow field 8
Inactivity lockchage -I DAYS, shadow field 7
Password expirationchage -M DAYS, shadow field 5
-1 valueDisables the field
0 valueImmediate 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 commandawk -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 -E flag sets the account expiration. The value is a date in YYYY-MM-DD format, or a number of days since the epoch. The value -1 removes the expiration, and 0 expires the account immediately.
  • The -I flag sets the inactivity lock. The account locks this many days after the password expires if the password was not changed. The value -1 disables the lock.
  • Temporary accounts need an expiration date. A contractor or intern account should have a date by which it stops working. The chage -E flag 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 -l output 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. The chage -l command shows one user at a time. The awk command extracts the expiration field for all users, and date converts 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!