chageManage password aging and account expiry
chage reads and sets the aging fields in /etc/shadow: when a password must be changed, how much warning the user gets, how long after expiry the account stays usable, and the date the whole account expires. It's how you enforce rotation policies and time-boxed contractor access.
chage [-l] [-M MAX] [-m MIN] [-W WARN] [-I INACTIVE] [-E DATE] [-d LASTDAY] LOGINAn auditor asks whether passwords on devops-lab ever expire.
sudoChanging aging fields writes /etc/shadow, so it needs root. chage -l on yourself doesn't.
Every /etc/shadow line carries aging fields after the hash: last change (days since 1970-01-01), minimum days between changes, maximum days, warning days, inactive days and account expiry (also days since 1970). chage just rewrites those numbers; -l translates them into dates. Nothing runs on a timer: the checks happen at login, in PAM's account phase (pam_unix). If today is past last-change + max, the user is told to change the password before getting a shell; if it's also past the inactive period, or past the account expiry date, the login is refused for every method, SSH keys included. -d 0 sets last change to day 0, which forces a change at next login. Ubuntu's defaults (max 99999, i.e. never) come from PASS_MAX_DAYS and friends in /etc/login.defs and only apply to newly created users.
-lList the aging information in readable form-M DAYSMaximum days a password is valid (-1 removes the limit)-m DAYSMinimum days between changes (stops instant reuse)-W DAYSDays of warning before the password expires-I DAYSDays after password expiry before the account is disabled-E DATEAccount expiry date (YYYY-MM-DD); -E -1 removes it-d 0Set last change to 1970: force a change at next login(no option)Interactive mode: prompts for each valueEditing PASS_MAX_DAYS in /etc/login.defs and assuming existing users now expirelogin.defs is only read when an account is created. Existing shadow entries keep their values.
sudo chage -M 90 USER for each existing user (loop over getent passwd with UID >= 1000)Using password expiry (-M) to cut off a contractor on a dateAn expired password just forces a change; the user picks a new one and carries on.
sudo chage -E 2026-12-31 USER (account expiry blocks every login)Forcing -M 90 on a service account used by automationWhen it expires, cron jobs and deploys that use the account can fail at night.
Service accounts: no password (locked), key or token auth, chage -M -1Try these on a Linux machine of your own. Each task has a hint and a solution.
Show the aging information for devops.
Create user temp5 and require a password change every 60 days with 10 days' warning.
Make temp5's account expire on 2026-12-31, then remove the expiry again.
Force temp5 to change its password at next login and show the raw shadow fields.
chageManage password aging and account expiry
chage reads and sets the aging fields in /etc/shadow: when a password must be changed, how much warning the user gets, how long after expiry the account stays usable, and the date the whole account expires. It's how you enforce rotation policies and time-boxed contractor access.
chage [-l] [-M MAX] [-m MIN] [-W WARN] [-I INACTIVE] [-E DATE] [-d LASTDAY] LOGINAn auditor asks whether passwords on devops-lab ever expire.
sudoChanging aging fields writes /etc/shadow, so it needs root. chage -l on yourself doesn't.
Every /etc/shadow line carries aging fields after the hash: last change (days since 1970-01-01), minimum days between changes, maximum days, warning days, inactive days and account expiry (also days since 1970). chage just rewrites those numbers; -l translates them into dates. Nothing runs on a timer: the checks happen at login, in PAM's account phase (pam_unix). If today is past last-change + max, the user is told to change the password before getting a shell; if it's also past the inactive period, or past the account expiry date, the login is refused for every method, SSH keys included. -d 0 sets last change to day 0, which forces a change at next login. Ubuntu's defaults (max 99999, i.e. never) come from PASS_MAX_DAYS and friends in /etc/login.defs and only apply to newly created users.
-lList the aging information in readable form-M DAYSMaximum days a password is valid (-1 removes the limit)-m DAYSMinimum days between changes (stops instant reuse)-W DAYSDays of warning before the password expires-I DAYSDays after password expiry before the account is disabled-E DATEAccount expiry date (YYYY-MM-DD); -E -1 removes it-d 0Set last change to 1970: force a change at next login(no option)Interactive mode: prompts for each valueEditing PASS_MAX_DAYS in /etc/login.defs and assuming existing users now expirelogin.defs is only read when an account is created. Existing shadow entries keep their values.
sudo chage -M 90 USER for each existing user (loop over getent passwd with UID >= 1000)Using password expiry (-M) to cut off a contractor on a dateAn expired password just forces a change; the user picks a new one and carries on.
sudo chage -E 2026-12-31 USER (account expiry blocks every login)Forcing -M 90 on a service account used by automationWhen it expires, cron jobs and deploys that use the account can fail at night.
Service accounts: no password (locked), key or token auth, chage -M -1Try these on a Linux machine of your own. Each task has a hint and a solution.
Show the aging information for devops.
Create user temp5 and require a password change every 60 days with 10 days' warning.
Make temp5's account expire on 2026-12-31, then remove the expiry again.
Force temp5 to change its password at next login and show the raw shadow fields.