Description
/etc/login.defs and useradd -D define what new local accounts get:
PASS_MAX_DAYS: maximum age of a password (Debian: 99999, never expires);PASS_MIN_DAYS: minimum days between two changes (Debian: 0);PASS_WARN_AGE: days of warning before expiry (Debian: 7);ENCRYPT_METHOD: hash used bychpasswdandnewusers(Debian 12: SHA512, Debian 13: YESCRYPT);INACTIVE(useradd -D): days after password expiry before the account is disabled (Debian: never).
NIST SP 800-63B advises against forced periodic changes when passwords are long and checked
against breach lists. Set max_days to 99999 in your tailoring file if you follow that model
and rely on the quality and lockout controls instead.
Rationale
A password that is never renewed and an account that is never disabled let a leaked password, or a departed user, keep access indefinitely. A minimum age stops users from cycling through the history to get their old password back.
Impact
New accounts must change their password every {{ max_days }} days and are disabled {{ inactive_days }} days after it expires. Existing accounts are handled by CB-DEB-AUTH-015.
Setting
| Location | /etc/login.defs, /etc/default/useradd |
| Default | PASS_MAX_DAYS 99999, PASS_MIN_DAYS 0, PASS_WARN_AGE 7, INACTIVE -1 |
| Recommended | PASS_MAX_DAYS <= {{ max_days }}, PASS_MIN_DAYS >= {{ min_days }}, PASS_WARN_AGE >= 7, YESCRYPT or SHA512, INACTIVE <= {{ inactive_days }} |
Parameters
| Parameter | Core | Server | Server hardened | Desktop | Desktop hardened | Bounds |
|---|---|---|---|---|---|---|
max_daysinteger, days. Maximum password age in days. | 365 | 365 | 365 | 365 | 365 | 1 to 99999 |
min_daysinteger, days. Minimum days between password changes. | 0 | 0 | 1 | 0 | 1 | 0 to 7 |
inactive_daysinteger, days. Days after password expiry before the account is disabled. | 45 | 45 | 45 | 45 | 45 | 0 to 365 |
The value used is your override (tailoring file), else the profile's, else the one of the profile it extends, else the default. Bounds limit what an override may set.
Audit
- In
/etc/login.defs:PASS_MAX_DAYS<= {{ max_days }},PASS_MIN_DAYS>= {{ min_days }},PASS_WARN_AGE>= 7,ENCRYPT_METHODisYESCRYPTorSHA512. useradd -D | grep INACTIVEshows a value between 0 and {{ inactive_days }}.
Automated: every check must pass. Scanners report each check by its id.
login-defs
Aging and hashing settings in /etc/login.defs.
script: |
get() { sed -n "s/^[[:space:]]*$1[[:space:]]\{1,\}\([^[:space:]]*\).*/\1/p" /etc/login.defs | tail -n 1; }
max=$(get PASS_MAX_DAYS) min=$(get PASS_MIN_DAYS) warn=$(get PASS_WARN_AGE) enc=$(get ENCRYPT_METHOD)
echo "PASS_MAX_DAYS=$max PASS_MIN_DAYS=$min PASS_WARN_AGE=$warn ENCRYPT_METHOD=$enc"
[ "${max:-99999}" -le "$CB_MAX_DAYS" ] && [ "${min:-0}" -ge "$CB_MIN_DAYS" ] && [ "${warn:-0}" -ge 7 ] \
&& { [ "$enc" = YESCRYPT ] || [ "$enc" = SHA512 ]; }
inactive
Default INACTIVE of useradd.
script: |
i=$(useradd -D | sed -n 's/^INACTIVE=//p')
echo "INACTIVE=$i"
[ "${i:--1}" -ge 0 ] && [ "${i:--1}" -le "$CB_INACTIVE_DAYS" ]
Remediation
- Set in
/etc/login.defs:PASS_MAX_DAYS {{ max_days }},PASS_MIN_DAYS {{ min_days }},PASS_WARN_AGE 7and keepENCRYPT_METHODatYESCRYPT(orSHA512). - Run
useradd -D -f {{ inactive_days }}.
| Restart | none |
| Disruption | none |
Rollback
Restore /etc/login.defs and /etc/default/useradd from the backup.
defaults
f=/etc/login.defs
cb_backup "$f"
cb_backup /etc/default/useradd
set_def() {
if grep -Eq "^[[:space:]]*$1[[:space:]]" "$f"; then sed -i "s/^[[:space:]]*$1[[:space:]].*/$1\t$2/" "$f"; else printf '%s\t%s\n' "$1" "$2" >>"$f"; fi
}
set_def PASS_MAX_DAYS "$CB_MAX_DAYS"
set_def PASS_MIN_DAYS "$CB_MIN_DAYS"
w=$(sed -n 's/^[[:space:]]*PASS_WARN_AGE[[:space:]]\{1,\}\([0-9]*\).*/\1/p' "$f" | tail -n 1)
[ "${w:-0}" -ge 7 ] || set_def PASS_WARN_AGE 7
e=$(sed -n 's/^[[:space:]]*ENCRYPT_METHOD[[:space:]]\{1,\}\([A-Z0-9]*\).*/\1/p' "$f" | tail -n 1)
case $e in YESCRYPT | SHA512) ;; *) set_def ENCRYPT_METHOD YESCRYPT ;; esac
useradd -D -f "$CB_INACTIVE_DAYS"
Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.
References
- login.defs(5), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- useradd(8), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- NIST SP 800-63B - Digital Identity Guidelines, Authentication NIST, standard, accessed 2026-10-18
Mappings
| Framework | ID | Relation |
|---|---|---|
| NIST SP 800-53 Rev. 55.1.1 | IA-5(1) | partial |
| NIST SP 800-53 Rev. 55.1.1 | AC-2(3) | partial |
How this control relates to the other framework's: equivalent, stricter, looser, partial or related.
Threats mitigated
| Taxonomy | ID |
|---|---|
| MITRE ATT&CK | T1078.003 |
Related controls
- related
CB-DEB-AUTH-015Apply the password aging policy to existing accounts
People and lifecycle
| Authors |
|
| Verified | 2026-10-18 by Sn0wAlice on 12, Proxmox VM, Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64. Generated check, fix and check again for profiles core and server-hardened; state kept after reboot. |
| Verified | 2026-10-18 by Sn0wAlice on 13, Proxmox VM, Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64. Generated check, fix and check again for profiles core and server-hardened; state kept after reboot. |
| Introduced in | v0.1.0 |
| Created, updated | 2026-10-18, 2026-10-18 |
| Translations | fr |