Handbook sections

Hardening PAM

Last reviewed

PAM is the gatekeeper every login passes through. Tune its modules so weak passwords, password reuse and brute-force never reach a shell, and lock accounts on repeated failure.

The threat: one permissive stack and every login is open

PAM (Pluggable Authentication Modules) is the shared authentication layer that login, sshd, sudo, su, cron and the display manager all call. Every credential check flows through the stacks in /etc/pam.d/, which makes it the place where authentication policy is enforced or silently absent. The threats it must blunt are timeless: weak passwords that fall to a dictionary in seconds, online brute-force that simply tries until it wins, password reuse where a user rotates back to a known-leaked secret, and fast hashing that lets a stolen /etc/shadow be cracked offline at scale. A default PAM stack opposes almost none of this: no complexity floor, no lockout, no history.

Why harden it

PAM is the single choke point where you impose policy on all services at once, and the defaults are deliberately permissive. Hardening converts authentication from a yes/no match into a layered gate: the secret must be strong enough (pam_pwquality), it must not repeat a recent one (pam_pwhistory), repeated failures must lock the account (pam_faillock), and the stored hash must be expensive to crack (pam_unix rounds=, yescrypt/SHA-512). Each layer raises attacker cost; together they turn a guessable password into a non-event. It maps to ANSSI R31 (passwords), R67 (PAM authentication) and R68 (password storage).

Order is the whole game

PAM is a stack, evaluated top to bottom, and a module's control decides the flow:

Control Effect
required must pass; failure is remembered but the stack continues (no early hint to the attacker)
requisite must pass; failure returns immediately
sufficient success short-circuits the rest of the stack
optional result ignored unless it is the only module

A strong module placed after a permissive sufficient line never runs. So pam_pwhistory must come before pam_unix and carry use_authtok, otherwise it checks the wrong token. The effective behaviour is the merged result of every line, not any single directive.

What Pavois audits: the effective PAM stacks

Pavois audits the effective PAM stacks under /etc/pam.d/, the files actually consulted at authentication time, including the distro common-* (Debian) / *-auth (RHEL) includes that service files pull in, rather than trusting one snippet. Its rules confirm the right modules are present and enabled (pam_pwquality, pam_faillock, pam_pwhistory, pam_unix) and then check their options: even_deny_root on faillock so the lockout covers root, the audit option so lockouts are logged, use_authtok on pwhistory so the new token is enforced, a non-default rounds= on pam_unix, and that history is not delegated to the deprecated remember= on pam_unix. Each control reads the live stack and matches only active, uncommented lines, so a directive behind a # or shadowed by ordering is reported non-compliant.

How do you configure and verify it?

The knobs live in dedicated files, but enabling the module differs by distribution, which is the central trap:

  • Debian / Ubuntu: the stack is in /etc/pam.d/common-auth and common-password, managed by pam-auth-update; pam_faillock must be referenced in preauth and authfail. Settings go in /etc/security/faillock.conf and /etc/security/pwquality.conf.
  • RHEL / AlmaLinux / Rocky: never edit system-auth / password-auth by hand, they are regenerated by authselect and your edits are overwritten. Enable cleanly: authselect enable-feature with-faillock && authselect apply-changes.

Verify the live state: faillock --user alice shows the failure count, faillock --user alice --reset unlocks. A typical policy: deny=3, unlock_time=600 (faillock); minlen=12, minclass=3 (pwquality); remember=5 (pwhistory, history in /etc/security/opasswd).

Modern password doctrine (NIST 800-63B, ANSSI 2024)

NIST SP 800-63B and ANSSI now advise against forced periodic rotation and imposed complexity, which push users toward weak, predictable passwords. The priority is length, a banlist of known-bad passwords, and rate-limiting (pam_faillock). Forced rotation is reserved for privileged accounts or a compliance requirement: some CIS profiles still mandate complexity credits, which you treat as an audit requirement, not a security ideal.

Pitfalls

  • A syntax error in a PAM file blocks all authentication at once, sudo and SSH included. Back up the file, keep a root session open, and test login in a second session before committing. Recovery is via rescue mode.
  • pam_tally2 is gone: removed from Linux-PAM since 1.5.0 (2020); a pam_tally2.so line now causes a PAM load error. Use pam_faillock.
  • even_deny_root can lock root itself; if you use it, add root_unlock_time=60.
  • Lock counters are not persistent by default (/var/run/faillock, cleared on reboot); add dir=/var/log/faillock to keep them.

FAQ

Why does Pavois read all the common-* / *-auth includes? Because the effective stack is the merge of the service file and its includes; a module enabled only in an include is live, and Pavois audits what PAM actually evaluates.

Should I force password rotation? Not for ordinary users (NIST/ANSSI advise against it). Prioritize length, a banlist and lockout; reserve rotation for privileged accounts or a compliance mandate.

My pam_tally2 line breaks login. Why? It was removed in Linux-PAM 1.5.0. Replace it with pam_faillock.