Lockout with faillock¶
After enough wrong passwords, the account locks for a while. That turns password guessing from thousands of tries a minute into a handful, and it's the Linux equivalent of the Windows lockout policy. Ubuntu and Debian use pam_faillock, configured in /etc/security/faillock.conf and wired into PAM in /etc/pam.d/common-auth and common-account.
The values¶
/etc/security/faillock.conf. Uncomment and set.
| Setting | Value | Meaning |
|---|---|---|
deny |
3 to 8 (use 5; CIS 5.2.3.1.1 says 5 or fewer, STIG UBTU-22-411045 says 3) | Wrong attempts before lockout |
unlock_time |
600 to 1200 seconds (use 900, 15 minutes; CIS 5.2.3.1.2 says 900 or more, or 0; the STIG says 0) | How long the lock lasts. 0 means an administrator must unlock by hand with faillock --reset, which is safer but means a locked-out administrator needs the console. |
fail_interval |
300 to 900 seconds (use 900; STIG: 900) | Attempts within this window count together |
audit |
(present) | Log attempts against usernames that don't exist, which is what a guessing tool tries first |
silent |
(present) | Don't tell the person how many attempts they have left |
even_deny_root |
(present; CIS 5.2.3.1.3, Level 2) | Root locks out too (with root_unlock_time = 900 so you can't be locked out of recovery for good) |
sudo nano /etc/security/faillock.conf
Like pwquality.conf, every line ships commented. Find each setting from the table, remove the #, set the value. audit, silent, and even_deny_root are switches with no value; the line is just the word.
deny = 5
unlock_time = 900
audit
Wire it into PAM¶
The config file does nothing until PAM calls the module (CIS 5.2.2.2). On Mint and Debian the /etc/pam.d/common-* files are generated by pam-auth-update from profiles in /usr/share/pam-configs/, so the durable way is two profile files, which Mint 21 and Debian 12 don't ship:
sudo tee /usr/share/pam-configs/faillock > /dev/null <<'EOF'
Name: Enable pam_faillock to deny access
Default: yes
Priority: 0
Auth-Type: Primary
Auth:
[default=die] pam_faillock.so authfail
EOF
sudo tee /usr/share/pam-configs/faillock_notify > /dev/null <<'EOF'
Name: Notify of failed login attempts and reset count upon success
Default: yes
Priority: 1024
Auth-Type: Primary
Auth:
requisite pam_faillock.so preauth
Account-Type: Primary
Account:
required pam_faillock.so
EOF
sudo pam-auth-update --enable faillock faillock_notify
The priorities place preauth before pam_unix and authfail after it, and the account line lands in common-account. The result, and what you'd write by hand on a system without pam-auth-update, is below. Either way /etc/pam.d/common-auth needs the pam_faillock.so lines in a specific order around pam_unix.so, and common-account needs one.
/etc/pam.d/common-auth:
auth required pam_faillock.so preauth
auth [success=1 default=ignore] pam_unix.so
auth [default=die] pam_faillock.so authfail
auth sufficient pam_faillock.so authsucc
auth requisite pam_deny.so
auth required pam_permit.so
| Line | Where | What it does |
|---|---|---|
preauth |
Before pam_unix |
Checks whether the account is already locked and refuses before the password is even tried |
authfail |
After pam_unix |
Records a failure when the password was wrong |
authsucc |
After authfail |
Clears the counter when the password was right |
Order is the whole point: preauth must come before pam_unix, authfail after it, authsucc after authfail. Get it wrong and either every login fails or no failure is ever counted.
The [success=1 default=ignore] on pam_unix means "on success skip the next one line," which skips authfail and lands on authsucc. If the file has other modules between (pam_sss, pam_krb5), the skip count has to be adjusted; on a plain image there's only pam_unix.
While you're in this file, the pam_unix.so line must not carry nullok (CIS 5.2.3.4.1). That option lets an account with an empty password log in. On Mint and Debian remove it from the profile so it stays removed: sudo sed -i 's/ nullok//g' /usr/share/pam-configs/unix && sudo pam-auth-update --enable unix.
/etc/pam.d/common-account, add before the pam_unix.so account line:
account required pam_faillock.so
Which PAM packages¶
The benchmark also wants libpam-runtime, libpam-modules, and libpam-pwquality at their current versions (CIS 5.2.1.1 to 5.2.1.3) and pam_unix enabled (5.2.2.1): apt list --installed 'libpam*' and grep pam_unix /etc/pam.d/common-auth. An image that removed libpam-modules can't authenticate anyone, so this is rarely the flaw; an out-of-date one is fixed by Run Updates.
Slow down the guesser¶
The STIG adds a delay after every failed login (UBTU-22-412010, CAT III), which makes even the allowed attempts slow:
auth required pam_faildelay.so delay=4000000
That line goes at the top of /etc/pam.d/common-auth (or, the durable way, Default: yes on the faildelay profile if the image has one: sudo pam-auth-update). The delay is in microseconds; four million is four seconds.
If the image uses pam_tally2¶
Older Debian images may have pam_tally2.so instead. It takes deny=5 unlock_time=900 onerr=fail on its own line in common-auth, and onerr=fail means "if the counter file can't be read, deny" rather than fail open. Ubuntu 22.04 and later don't ship it; use faillock there.
Unlock someone¶
sudo faillock --user bob # show the failure record
sudo faillock --user bob --reset # clear it
Verify¶
grep -E '^(deny|unlock_time|fail_interval|audit|silent|even_deny_root)' /etc/security/faillock.conf
grep -nE 'faillock|pam_unix' /etc/pam.d/common-auth
grep faillock /etc/pam.d/common-account
grep nullok /etc/pam.d/common-auth # nothing
The common-auth lines come out in the order preauth, pam_unix, authfail, authsucc. Then test: su - testuser from another account and type the wrong password six times; the seventh attempt is refused before the prompt, and sudo faillock --user testuser shows the record.
Example¶
faillock.conf is all comments and common-auth has one line: auth [success=1 default=ignore] pam_unix.so nullok. So there's no lockout and blank passwords work. Add the four faillock lines around pam_unix, remove nullok, add the account line, set deny = 5, unlock_time = 900, fail_interval = 900, audit, silent.
Try it¶
- Lock a test account with five wrong passwords, read
faillock --user, reset it. - Break the order of the lines on purpose (put
authfailfirst), try to log in, read what happens, fix it. Keep a root shell open while you do.
Build it¶
Add the two faillock profiles and the faillock.conf edits to pam.sh. Never run it without the test on the next page.