Add Local Password History with pam_pwhistory
Someone changes their password, then immediately changes it back, and your password policy is worthless. pam_pwhistory closes that loop by remembering recent passwords and rejecting a reuse.
The route
Jump straight to the step you need, or tick off Done means at the end.
The examples target Ubuntu's libpam-modules package, version 1.5.3-5ubuntu5.7 on the machine used for this guide. Allow about 15 minutes, including a test and a recovery check.
Warning
This is a security-sensitive change to the password stack. Keep an existing root shell or console session open before editing PAM, and make a dated backup first. A malformed PAM file can prevent authentication, even though an already logged-in shell keeps working.
1. Check the installed module and current stack
Run these read-only checks as an ordinary user:
$ dpkg-query -W -f='${Package} ${Version}\n' libpam-modules:amd64
libpam-modules:amd64 1.5.3-5ubuntu5.7
$ ls -l /lib/x86_64-linux-gnu/security/pam_pwhistory.so
$ grep -n '^[[:space:]]*password' /etc/pam.d/common-password
The module is a PAM password module, not a command you run directly. On this Ubuntu installation, common-password holds the shared password stack and currently uses pam_unix. Your service may use a different stack, so inspect whichever file actually handles the password change before editing it.
Checkpoint
Identify the existing pam_unix.so password line. The history line belongs immediately before it, so pam_pwhistory can grab the new token and pam_unix can reuse it.
2. Choose the history policy
The preferred configuration file is /etc/security/pwhistory.conf. It uses one option per line in name = value form; comments start with #, and whitespace around the equals sign is ignored. The installed defaults remember 10 passwords, prompt once, and store the history in /etc/security/opasswd.
For a five-password history, edit the file as root:
$ sudoedit /etc/security/pwhistory.conf
Set or uncomment these values, keeping the other comments if they help future maintenance:
remember = 5
retry = 1
file = /etc/security/opasswd
- Do not add debug as a routine setting. It sends module diagnostics to syslog and can expose more operational detail than a normal password policy needs.
- Add enforce_for_root only deliberately. Without it, the history check is not enforced for root; add it once you have decided root password changes must obey the same history.
Checkpoint
Verify the file as root without printing the password history itself:
$ sudo sed -n '1,160p' /etc/security/pwhistory.conf
$ sudo stat -c '%U:%G %a %n' /etc/security/opasswd
Warning
The history file contains password-history data and should stay protected. Do not copy it into a ticket, paste it into a chat, or use a temporary path such as /tmp/opasswd for a production policy.
3. Add the module before pam_unix
Back up the PAM file, then open it with elevated privileges:
$ sudo cp --preserve=all /etc/pam.d/common-password /etc/pam.d/common-password.$(date +%Y%m%d-%H%M%S).bak
$ sudoedit /etc/pam.d/common-password
Add this line immediately before the existing pam_unix.so password line:
password required pam_pwhistory.so use_authtok
For the stack installed here, the result should look like this around the password modules:
password required pam_pwhistory.so use_authtok
password [success=1 default=ignore] pam_unix.so obscure yescrypt
password requisite pam_deny.so
password required pam_permit.so
use_authtok tells the module to use the new password supplied by an earlier password module. If your stack already runs a password-quality module before pam_pwhistory, follow the local stack's own ordering and do not copy an example from another distribution wholesale. The module provides only the password type.
- No earlier module supplies a token? Do not put
use_authtokon the history line. - Keep use_authtok on the pam_unix line. Remove it from the documented two-module arrangement and the stack can ask for the new password twice.
4. Test a real password change safely
First check that the edited lines are present and the history file is still protected:
$ grep -n -A2 -B1 'pam_pwhistory' /etc/pam.d/common-password
$ sudo stat -c '%U:%G %a %n' /etc/security/opasswd
root:root 600 /etc/security/opasswd
Use a disposable non-root test account if your system policy permits it. The following changes state and requires elevated privileges:
$ sudo useradd --create-home pam-history-test
$ sudo passwd pam-history-test
Give the test account an initial password, then run sudo passwd pam-history-test again and try that same password as the new value. The second change should be rejected as a reuse. Choose a fresh test password to confirm a permitted change still succeeds.
Warning
Do not use a real user's password in a test transcript. Remove the disposable account after testing, only if you created it for this purpose:
$ sudo userdel --remove pam-history-test
This deletion removes the test account and its home directory. It does not remove the shared history entries for other accounts. If the account was not disposable, stop and do not run that command.
5. Recover if authentication behaves unexpectedly
If a password change fails unexpectedly, record the exact PAM error and inspect the system log through your normal log-management process.
- PAM_MAXTRIES means the password was rejected too often.
- PAM_AUTHTOK_ERR means no usable new password was set.
- PAM_USER_UNKNOWN means the account was not known to PAM.
- PAM_IGNORE indicates disabled history.
If the edited stack causes broader authentication trouble, use the root shell or console you kept open. Restore the backup, then check the file before closing that recovery session:
$ sudo cp --preserve=all /etc/pam.d/common-password.YYYYMMDD-HHMMSS.bak /etc/pam.d/common-password
$ sudo grep -n '^[[:space:]]*password' /etc/pam.d/common-password
Replace YYYYMMDD-HHMMSS with the actual backup suffix; do not guess if several backups exist. List them first with ls -1t /etc/pam.d/common-password.*.bak. The same restore approach applies to pwhistory.conf if a policy edit needs undoing, provided you backed it up before changing it.
Done means
- pam_pwhistory.so is installed and the relevant PAM stack has been identified.
- pwhistory.conf sets an intentional history count and a protected history path.
- The history module sits before pam_unix, with token handling that matches the surrounding stack.
- A disposable account rejected a recently used password and accepted a new one, where testing was permitted.
- A dated PAM backup remains available, and the recovery shell has not been closed until the change is trusted.