How to enforce a password policy¶
This guide makes local accounts choose passwords of at least 12 characters that use at least three character classes (lower case, upper case, digits, other characters), and sets password-ageing defaults for new accounts. It then proves the policy works by trying a weak and a strong password on a test account.
The policy applies when a user changes their own password with passwd.
When root sets a password (with passwd <USER> or chpasswd), the quality modules only print a warning; that's deliberate and this guide keeps it that way, so root can always reset a password.
Before you begin¶
- You have a shell with
sudoon the machine. - Open a second session and start a root shell in it with
sudo -i. Keep it open until you have finished the Verify section.
Changing PAM can lock you out
The steps below change only the password part of the PAM configuration, which is used when a password is changed, not when someone logs in.
A mistake there stops users changing passwords, but doesn't stop logins.
Edit only the files named here, in the way shown: a typo in the auth or account lines of the same files can refuse every login, including yours through sudo.
The root shell in your second session is your way back; each step says how to undo it.
Steps¶
-
Make sure a password quality module is in the password stack.
Install
pam_pwquality. Its package adds it to/etc/pam.d/common-passwordthroughpam-auth-update:Check that the module is now the first
passwordline:Don't edit
common-passwordby hand:pam-auth-updateregenerates it when PAM-related packages are installed or removed. To undo this step, runsudo apt-get remove libpam-pwquality.pam_pwqualityis enabled by default. Check that it's in the stacks thatauthselectgenerates:$ grep pam_pwquality /etc/pam.d/system-auth /etc/pam.d/password-auth /etc/pam.d/system-auth:password requisite pam_pwquality.so /etc/pam.d/password-auth:password requisite pam_pwquality.soDon't edit these files by hand: they belong to
authselect, which overwrites them. The policy goes inpwquality.conf.din the next step.Arch doesn't check password quality by default. Install
libpwquality, which provides the module:Keep a copy of the PAM file for the
passwdcommand, then add the module before the line that includessystem-auth:sudo cp -p /etc/pam.d/passwd /root/pam.d-passwd.bak sudo sed -i '/^password[[:space:]]\+include[[:space:]]\+system-auth/i password\trequisite\tpam_pwquality.so retry=3' /etc/pam.d/passwdThe file should now read:
$ cat /etc/pam.d/passwd #%PAM-1.0 auth include system-auth account include system-auth password requisite pam_pwquality.so retry=3 password include system-authThe control must be
requisite, so that a refused password stops the stack. Withrequired,pam_unixinsystem-authstill runs after the refusal, asks for a new password itself, and stores it.Changing
/etc/pam.d/passwdaffects only thepasswdcommand; logins don't use it. To undo this step, runsudo cp -p /root/pam.d-passwd.bak /etc/pam.d/passwd.Gentoo's
pambasechecks passwords withpam_passwdqcby default (USE flagpasswdqc). Check that it's insystem-auth:$ grep pam_passwdqc /etc/pam.d/system-auth password required pam_passwdqc.so config=/etc/security/passwdqc.confIf the line is missing,
sys-auth/pambasewas built without thepasswdqcUSE flag; enable it and rebuildpambaserather than editingsystem-auth, which is a package-managed file. -
Set the policy.
Create the file
/etc/security/pwquality.conf.d/50-local.confwith this content (the directory doesn't exist by default, but the module reads it):Create the file
/etc/security/pwquality.conf.d/50-local.confwith this content:Create the file
/etc/security/pwquality.conf.d/50-local.confwith this content:pam_passwdqcreads/etc/security/passwdqc.conf. Keep a copy, then set the minimum lengths and make the policy refuse weak passwords for ordinary users:sudo cp -p /etc/security/passwdqc.conf /root/passwdqc.conf.bak sudo sed -i -e 's/^min=.*/min=disabled,disabled,12,12,12/' -e 's/^enforce=.*/enforce=users/' /etc/security/passwdqc.confThe five values of
min=are the minimum lengths for passwords made of one character class, two classes, passphrases of several words, three classes and four classes;disabledrefuses that kind outright. Gentoo's default isenforce=none, which only warns;enforce=usersrefuses weak passwords chosen by users and still lets root set any password. To undo this step, runsudo cp -p /root/passwdqc.conf.bak /etc/security/passwdqc.conf.In
pwquality.confterms,minlenis the minimum length (exactly, as long as the*creditsettings keep their default of 0),minclassthe number of classes required, andretrythe number of attemptspasswdallows. The module also refuses dictionary words and passwords containing the user name by default. To undo this step on Debian, Ubuntu, the RHEL family and Arch, delete/etc/security/pwquality.conf.d/50-local.conf. -
Set password-ageing defaults for accounts created from now on, in
/etc/login.defs: a password must be changed after 365 days, and its owner is warned 14 days before it expires.sudo cp -p /etc/login.defs /root/login.defs.bak for setting in 'PASS_MAX_DAYS 365' 'PASS_WARN_AGE 14'; do key=${setting%% *} if sudo grep -q "^${key}[[:space:]]" /etc/login.defs; then sudo sed -i "s/^${key}[[:space:]].*/${setting}/" /etc/login.defs else echo "$setting" | sudo tee -a /etc/login.defs >/dev/null fi done grep -E '^PASS_(MAX_DAYS|WARN_AGE)' /etc/login.defsDebian, Ubuntu and the RHEL family have these settings in
login.defsalready, and the loop changes them; on Arch and Gentoo they are missing, and it adds them.useraddcopies them into each new account; existing accounts keep their current values, which you change per account, for example withsudo chage --maxdays 365 --warndays 14 <USER>, where<USER>is the account's name.PASS_MIN_LENinlogin.defsisn't used for this policy: on these systems, password length is checked by the PAM module configured above. To undo this step, runsudo cp -p /root/login.defs.bak /etc/login.defs; accounts created in the meantime keep the maximum age they were given, so check them withsudo chage -l <USER>.Don't expire accounts that have no password
When an account's password expires, PAM demands a new password before the account can log in or use
sudo, even when it logs in with an SSH key. An account without a usable password, such as an administrator who only logs in with a key and uses passwordlesssudo, can then no longer get in. The new default applies to every account created from now on, including key-only administrators and the accounts thatcloud-initcreates, and the lockout only shows a year later. Check an account withsudo chage -l <USER>before giving it a maximum age, and remove the maximum from each new account that won't use a password, as below. -
For every new account that won't use a password, remove the maximum age right after creating it. For example, for a key-only account called
deploy:$ sudo useradd -m <USER> $ sudo chage -M -1 <USER> $ sudo chage -l <USER> Last password change : Nov 07, 2026 Password expires : never Password inactive : never Account expires : never Minimum number of days between password change : 0 Maximum number of days between password change : -1 Number of days of warning before password expires : 14Password expires: neverconfirms that the account can't be locked out by ageing. Accounts created by automation (cloud-init, configuration management) need the same treatment, in the automation itself.
Verify¶
Test the policy on a throwaway account, never on your own.
-
Create a test account and give it a starting password, as root:
-
Check that the new account got the ageing defaults from step 3:
The fields are the account, its password status (
Pfor a usable password), the date of the last change, and the minimum age, maximum age, warning period and inactivity period in days. The maximum age should be 365 and the warning period 14. On Arch and Gentoo the minimum age is shown as-1(not set) instead of0; both mean that the password can be changed at any time. -
As
pwtest, try to change the password toKx9#mPq2, which is eight characters long. It's long enough for the distributions' defaults, but not for the new policy:$ printf '%s\n' 'Start-Pass-2026-xyz' 'Kx9#mPq2' 'Kx9#mPq2' | sudo su -s /bin/sh pwtest -c passwd Changing password for pwtest. Current password: New password: BAD PASSWORD: The password is shorter than 12 characters New password: BAD PASSWORD: The password is shorter than 12 characters New password: Password change has been aborted. passwd: Authentication token manipulation error passwd: password unchangedpasswdreads the current password, then the new one twice, from its standard input here, so the test runs without typing. The policy refuses the password, sopasswdtakes the repeated line as a second attempt, refuses that too, gives up at the end of its input and exits with an error. On Gentoo,pam_passwdqcprints its own explanation of the rules andWeak password: too short.instead. -
Try a strong one, which the policy accepts:
-
Delete the test account:
You can now close the root shell in your second session.
Troubleshooting¶
A weak password set by root is accepted, with only a BAD PASSWORD warning.
That's the default: root is warned, not refused, so it can always reset a password.
Test as the user, as in the Verify section.
If you need root to be held to the policy too, add enforce_for_root to the pwquality.conf.d file, or use enforce=everyone in passwdqc.conf on Gentoo; scripts that set passwords as root then fail on weak ones.
The password is changed even though the policy refused it.
The quality module's line has required instead of requisite, or the pam_unix line after it lacks use_authtok or try_first_pass, so pam_unix asks for a password itself and stores it.
On Arch, check the line added to /etc/pam.d/passwd in step 1.
The policy file seems to be ignored.
Check the file name ends in .conf, and that no argument on the PAM line overrides it: options given in /etc/pam.d/ win over the configuration files.
On Gentoo, check that system-auth passes config=/etc/security/passwdqc.conf.
passwd: Authentication token manipulation error without any explanation.
A module in the password stack failed or couldn't be loaded; the reason is in the system log.
Use sudo journalctl -t passwd on systemd distributions, or, on Gentoo with OpenRC, in the files of the system logger you installed (with sysklogd, /var/log/auth.log).
Related¶
- Reference: PAM configuration
- Explanation: How PAM works
- How-to guide: How to grant a user sudo access
- How-to guide: How to harden the SSH server