How to List Linux Password Expiry Info for All Users: A Sysadmin’s Guide


4 views

Recently encountered a scenario where multiple users on a Debian server suddenly couldn't authenticate via SFTP, despite having no shell access. The auth logs revealed failed attempts due to password expiration - a detail I'd overlooked during initial setup.

Here's the most thorough command to check password expiration for all users:

for user in $(cut -d: -f1 /etc/passwd); do 
    echo "=== $user ==="
    chage -l $user 2>/dev/null || echo "No password expiration information"
done

To extract just the critical information and sort by expiration date:

getent passwd | cut -d: -f1 | xargs -I {} sh -c '
    echo "User: {}"
    chage -l {} 2>/dev/null | grep "Password expires"
    echo' | awk '/User:/ {user=$2} /Password expires/ {print user ": " $0}' | 
    sort -k5

Create a monitoring script that runs weekly via cron:

#!/bin/bash
EXPIRING_USERS=$(getent passwd | cut -d: -f1 | xargs -I {} sh -c '
    expiry=$(chage -l {} 2>/dev/null | grep "Password expires")
    if [[ "$expiry" != *"never"* ]]; then
        echo "{}: $expiry"
    fi
')

if [ -n "$EXPIRING_USERS" ]; then
    echo "The following users have password expiration set:"
    echo "$EXPIRING_USERS"
    # Add mail/slack notification logic here
fi

To disable password expiration for SFTP-only users:

for user in sftpuser1 sftpuser2 webuser3; do
    chage -m 0 -M 99999 -I -1 -E -1 $user
done

For systems without the chage command:

awk -F: '$1!="nobody"{system("passwd -S "$1)}' /etc/passwd | 
    awk '{print $1,$3,$5,$7}'

Generate a CSV report for spreadsheet analysis:

echo "Username,Last Change,Min Days,Max Days,Warn Days,Expiration Date" > expiry_report.csv
getent passwd | cut -d: -f1 | while read user; do
    chage -l $user 2>/dev/null | awk -v user="$user" '
        /Last password change/ {last=$NF}
        /Minimum/ {min=$NF}
        /Maximum/ {max=$NF}
        /warning/ {warn=$NF}
        /Password expires/ {expires=$0; sub(/.*: /,"",expires)}
        END {print user "," last "," min "," max "," warn "," expires}
    '
done >> expiry_report.csv

Recently on a Debian server deployment, I faced an unexpected issue where multiple users suddenly couldn't authenticate via SFTP. The auth logs revealed failed login attempts due to expired passwords - a classic case of overlooking default password policies.

What made this particularly problematic was that these were webspace users without shell access, so they:

  • Received no expiry warnings
  • Couldn't change passwords themselves
  • Only saw generic authentication errors

Here are the most useful commands to check password expiration information:


# Check a single user's expiry info
chage -l username

# Batch check all users (GNU/Linux systems)
for user in $(cut -d: -f1 /etc/passwd); do 
    echo "=== $user ==="; 
    chage -l $user 2>/dev/null || echo "No expiry information"; 
done

# Alternative using getent (more portable)
getent passwd | cut -d: -f1 | xargs -I {} sh -c 'echo "== {} =="; chage -l {} 2>/dev/null || echo "No expiry info"'

The chage command outputs several important dates:


Last password change                                    : Jan 01, 2023
Password expires                                        : Jul 01, 2023
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 180
Number of days of warning before password expires       : 7

For regular monitoring, I created this script that emails when passwords will expire soon:


#!/bin/bash
WARN_DAYS=14
ADMIN_EMAIL="admin@example.com"

getent passwd | while IFS=: read -r username _ _ _ _ _ _; do
    expiry=$(chage -l "$username" 2>/dev/null | awk -F: '/Password expires/{print $2}')
    if [[ "$expiry" != *"never"* ]]; then
        expiry_epoch=$(date -d "$expiry" +%s)
        today_epoch=$(date +%s)
        diff_days=$(( (expiry_epoch - today_epoch) / 86400 ))
        
        if [ "$diff_days" -lt "$WARN_DAYS" ] && [ "$diff_days" -ge 0 ]; then
            echo "User $username password expires in $diff_days days" | mail -s "Password Expiry Warning" "$ADMIN_EMAIL"
        fi
    fi
done

To prevent future issues, I adjusted the defaults in /etc/login.defs:


PASS_MAX_DAYS   99999
PASS_MIN_DAYS   0
PASS_WARN_AGE   14

And set existing users to never expire:


# Set all users to never expire
getent passwd | cut -d: -f1 | xargs -I {} chage -M 99999 {}