Home / Alt manpages / chage(1)

  • chage(1)
  • User command
  • linux

Set Linux Password Expiry Safely with chage

You will finish with a safe workflow for inspecting and changing Linux password ageing: list the current values, set a maximum password lifetime and warning period, and verify the result. The installed command comes from the Ubuntu passwd package, version 1:4.13+dfsg1-4ubuntu3.2, and its local manual identifies it as shadow-utils 4.13.

Allow about ten minutes. You need a shell and the login name you intend to inspect. Listing your own account is normally unprivileged. Changing ageing values is restricted to root, so use sudo only for a deliberate administrative change.

1. Inspect the account before changing it

Start with a read-only listing. Replace LOGIN with the exact account name, not a display name:

$ chage -l LOGIN

For your own account, omit sudo. An administrator can inspect another account with:

$ sudo chage -l LOGIN

The output has seven useful fields: the last password change, password expiry, password inactivity, account expiry, minimum and maximum days between changes, and the warning period. On this machine, an ordinary self-check looks like this:

$ chage -l andy
Last password change                    : Feb 06, 2026
Password expires                         : never
Password inactive                        : never
Account expires                          : never
Minimum number of days between password change : -1
Maximum number of days between password change : -1
Number of days of warning before password expires : -1

Exact spacing and dates vary. The important trap is that never and -1 are meaningful states, not missing output. The local manual says that -1 removes the relevant expiry or inactivity check for options that support it. The output is based on /etc/shadow; it does not include login effects supplied by LDAP or every inconsistency between /etc/passwd and /etc/shadow.

Checkpoint

Save or copy the listing before changing anything. It is the easiest record from which to reconstruct an accidental change.

2. Set a password lifetime and warning period

The common policy is a maximum lifetime with advance warning. This example permits a password for 90 days and warns for the final 14 days:

$ sudo chage --maxdays 90 --warndays 14 LOGIN

--maxdays sets the number of days for which the password is valid. --warndays sets how many days before that expiry the user is warned. These values are relative to the password's last-change day, so do not assume the account will expire 90 days from the moment you run the command.

The command normally prints nothing on success. Check the stored values immediately:

$ sudo chage -l LOGIN
Password expires                         : <date about 90 days after the last change>
Maximum number of days between password change : 90
Number of days of warning before password expires : 14

There is no universal date to paste into the expected output because the last-change date belongs to the target account. Verify the numeric policy values and the calculated date instead.

Do not use --maxdays 0 as a synonym for 'never expires'. The manual defines zero as a valid maximum interval, so it can make the password immediately due. To remove maximum-age checking, pass -1 deliberately:

$ sudo chage --maxdays -1 LOGIN

Re-list the account and confirm that the maximum interval is -1. This is a security-sensitive relaxation, so record why the exception exists.

3. Force a password change at the next login

To make a user replace a temporary or exposed password at the next login, set the last-change day to zero:

$ sudo chage --lastday 0 LOGIN

The manual defines 0 as 'change the password on the next log on'. This does not itself set a new password and does not require a service restart. It changes account state in /etc/shadow, so do not run it against a production account until you have confirmed the login name.

Verify the marker:

$ sudo chage -l LOGIN
Last password change                    : password must be changed

The precise wording can differ by shadow-utils build, but it should indicate that a change is required. If you set this by mistake, there is no generic 'undo' value that safely recreates the original last-change date. Use the saved pre-change listing and your account-management procedure, then set the known date explicitly with --lastday YYYY-MM-DD or the documented day count.

4. Set an account expiry separately

Password expiry and account expiry are different controls. Password expiry asks for a new password. Account expiry makes the account inaccessible after its expiry date. To expire the account on a known calendar date, use ISO format:

$ sudo chage --expiredate 2026-12-31 LOGIN
$ sudo chage --list --iso8601 LOGIN

The second command asks for YYYY-MM-DD when printing dates, which makes output easier to compare in scripts. Confirm the account expiry field before communicating the change:

Account expires                          : 2026-12-31

To remove the account expiry date, the manual specifies -1:

$ sudo chage --expiredate -1 LOGIN

That removes one access boundary. Treat it as an exception, not as a harmless cleanup. A locked account may still require administrator intervention even after an expiry date is corrected.

5. Add a grace period after password expiry

Password inactivity controls what happens after the password has expired. For example, this allows seven days of inactivity before the account is locked:

$ sudo chage --inactive 7 LOGIN

Remove the inactivity limit with the documented value -1:

$ sudo chage --inactive -1 LOGIN

Do not confuse this with --warndays. Warning days happen before password expiry and give the user time to act. Inactivity days happen after expiry and determine when access is locked. Verify both fields with sudo chage -l LOGIN.

6. Avoid interactive and privilege mistakes

Running chage LOGIN without an option starts an interactive session for all ageing fields. Each current value appears between square brackets. Enter a new value to change it, or leave the line blank to keep it. This is useful at a terminal when you have reviewed every prompt, but it is a poor fit for an unattended script because a missed prompt can leave a partial policy change.

For scripts, use explicit options, quote the login variable, and stop on errors:

#!/bin/sh
set -eu
login='LOGIN'
sudo chage --maxdays 90 --warndays 14 --inactive 7 "$login"
sudo chage --list --iso8601 "$login"

Use the account name as a single shell argument. Do not build a command by concatenating untrusted input, and do not assume a successful sudo command means the intended account was selected.

Only --list is documented as available to an unprivileged user. Other operations require root and return permission denied when the privilege is insufficient. Exit status 0 means success, 1 permission denied, 2 invalid syntax, and 15 that the shadow password file could not be found. Capture the status in automation rather than parsing human-readable dates.

Checkpoint

Run a final listing with --iso8601, compare it with the policy you intended, and keep the output with your change record.

Done means

  • The target login was confirmed before any administrative command ran.
  • The original chage -l output was recorded.
  • Maximum age, warning, inactivity, and account expiry were treated as separate settings.
  • Any value of 0 or -1 was used because its documented meaning was intended.
  • A final chage --list --iso8601 LOGIN output matches the requested policy.