Skip to content

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

  1. Lock a test account with five wrong passwords, read faillock --user, reset it.
  2. Break the order of the lines on purpose (put authfail first), 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.

Next

Test the Whole Login Path