How to Exclude Users from pam_faillock Lockout in RHEL

Deepak Prasad
Tested on Red Hat Enterprise Linux 10.2 (lab VM)
Package authselect 1.5.2
pam 1.6.1 (pam_faillock, pam_listfile)
Applies to RHEL-family systems using the authselect local profile and pam_faillock
Privilege root (authselect, PAM profiles, faillock.conf, exempt user list)
Scope On hosts with the authselect local profile, enable pam_faillock, exempt selected local users via pam_listfile and a username file, and verify lockout for normal users and no tally growth for exempt users over SSH password authentication
Related guides Prevent brute force SSH attacks
Implement a password policy
Lock accounts with pam_tally2
faillock and reset root password
SSH command in Linux

On RHEL, Rocky Linux, AlmaLinux, CentOS Stream, and Oracle Linux hosts that use the authselect local profile, pam_faillock counts failed authentication attempts and can lock local accounts after a threshold. That protects SSH and console logins, but some service or break-glass accounts should not inherit the same lockout policy. faillock.conf sets the global deny limit and unlock timer — it does not list individual users to skip. This walkthrough adds pam_listfile in the authselect-managed PAM stack so names in /etc/security/faillock-exempt-users bypass pam_faillock authfail while everyone else still accumulates failures and locks.

Do not apply this local-based custom profile on IdM, AD, or SSSD-managed hosts without separately testing a profile based on sssd — switching authselect profiles can change the authentication source.


Quick reference: exempt users from pam_faillock

Item Path or command
Global faillock policy /etc/security/faillock.conf
Exempt username list /etc/security/faillock-exempt-users (one name per line)
Active PAM stack /etc/authselect/system-auth, /etc/authselect/password-auth
Custom profile /etc/authselect/custom/faillock-exempt/
Select profile authselect select custom/faillock-exempt with-faillock
Inspect tally faillock --user username
Clear tally faillock --user username --reset

How pam_faillock user exclusion works

pam_faillock maintains per-user failure records (by default under /var/run/faillock) and enforces lockout through two auth-stack hooks:

  • preauth — prepares counters before password checking
  • authfail — records a failure when authentication did not succeed

/etc/security/faillock.conf controls shared settings such as deny, fail_interval, and unlock_time. There is no keyword in that file for “do not lock user deepak” or “service account exceptions.”

Per-user exceptions require PAM control logic. The pattern tested here:

  1. Enable with-faillock through authselect.
  2. After pam_unix verifies the password, run pam_listfile against a username file.
  3. If the user is listed, skip pam_faillock authfail with [success=1 default=ignore].
  4. Wrong passwords still fail at pam_deny; exempt users simply do not grow a faillock tally on authfail.

Exempt users are not given a free pass — they still must present a valid password. They only avoid account lockout from accumulated authfail events.


Check the current pam_faillock configuration

Start with the active authselect profile and whether faillock is enabled:

bash
authselect current
output
Profile ID: local
Enabled features:
- with-fingerprint

If with-faillock is missing, failed logins are not tracked by pam_faillock yet. Review global limits next — uncommented lines in faillock.conf override compiled defaults:

bash
grep -vE '^(#|$)' /etc/security/faillock.conf
output
deny = 3
unlock_time = 600

This lab uses three failures and a 600-second unlock window so lockout is easy to observe. Confirm where pam_faillock sits in the generated stacks SSH and su include:

bash
grep pam_faillock /etc/pam.d/system-auth /etc/pam.d/password-auth
output
/etc/pam.d/system-auth:auth        required                                     pam_faillock.so preauth silent
/etc/pam.d/system-auth:auth        required                                     pam_faillock.so authfail
/etc/pam.d/system-auth:account     required                                     pam_faillock.so
/etc/pam.d/password-auth:auth        required                                     pam_faillock.so preauth silent
/etc/pam.d/password-auth:auth        required                                     pam_faillock.so authfail
/etc/pam.d/password-auth:account     required                                     pam_faillock.so

sshd includes password-auth, so SSH logins follow the password-auth stack. Console login paths that include system-auth need the same pam_listfile line in both files.


Create the list of users to exclude

Place exempt local usernames in one file. This article uses /etc/security/faillock-exempt-users:

bash
cat <<'EOF' > /etc/security/faillock-exempt-users
deepak
serviceuser
EOF

Show the file contents before you edit the authselect profile:

bash
cat /etc/security/faillock-exempt-users
output
deepak
serviceuser

One username per line, no extra fields. pam_listfile with sense=allow treats a line match as success. Do not list root here unless you deliberately want root exempt from authfail tally growth — prefer tightening faillock.conf (even_deny_root, admin_group) for root policy instead.


Create a custom authselect profile

On RHEL, Rocky Linux, AlmaLinux, CentOS Stream, and Oracle Linux, do not hand-edit /etc/pam.d/system-auth — authselect regenerates those symlinks. This lab uses the RHEL 10 local authselect profile, so create the custom profile from local:

bash
authselect create-profile faillock-exempt --base-on=local
output
New profile was created at /etc/authselect/custom/faillock-exempt

The new tree lives under /etc/authselect/custom/faillock-exempt/. Edit system-auth and password-auth so the password-authentication paths covered by this guide run the exemption check before pam_faillock authfail.


Add the pam_listfile exclusion rule

Open the custom system-auth template and locate the pam_unix sufficient line in the auth stack. Insert pam_listfile immediately after pam_unix and before pam_faillock authfail:

text
auth        required      pam_faillock.so preauth silent                         {include if "with-faillock"}
auth        sufficient    pam_unix.so {if not "without-nullok":nullok}
auth        [success=1 default=ignore] pam_listfile.so item=user sense=allow file=/etc/security/faillock-exempt-users onerr=fail
auth        required      pam_faillock.so authfail                               {include if "with-faillock"}
auth        required      pam_deny.so

Apply the same pam_listfile line in password-auth at the same position relative to pam_unix and pam_faillock authfail.

What [success=1 default=ignore] does

Control Meaning in this stack
sense=allow pam_listfile returns success when the username appears in the file
default=ignore Users not in the file do not fail here — PAM continues to authfail
success=1 On success, skip one following module — pam_faillock authfail

Flow for a non-exempt user with a wrong password:

  1. pam_unix does not return success.
  2. pam_listfile returns ignore (name absent).
  3. pam_faillock authfail records the failure.
  4. pam_deny rejects the login.

Flow for an exempt user with a wrong password:

  1. pam_unix fails.
  2. pam_listfile succeeds → skip authfail only (success=1).
  3. pam_deny still rejects the bad password — no free login.
  4. No new failure row is added by authfail.

Flow for an exempt user with the correct password:

  1. pam_unix succeeds early as sufficient — later modules, including pam_listfile and authfail, are not evaluated.

Use success=1, not success=2, on RHEL 10 stacks where pam_deny follows authfail immediately. Skipping two modules can jump past pam_deny and break the intended deny path. Older examples with extra modules between authfail and pam_deny needed a different jump count — always count modules on your generated stack.


Apply the authselect profile

Set the test lockout policy in /etc/security/faillock.conf:

text
deny = 3
unlock_time = 600

These values make lockout easy to verify in the lab. Adjust deny and unlock_time to your production policy before you roll this out. faillock.conf holds the lockout limits; authselect's with-faillock feature controls whether pam_faillock modules appear in the PAM stack.

Select the custom profile with faillock enabled and apply the changes:

bash
authselect select custom/faillock-exempt with-faillock

Regenerate the managed PAM files from the custom profile:

bash
authselect apply-changes

Confirm authselect reports the custom profile and faillock feature:

bash
authselect current
output
Profile ID: custom/faillock-exempt
Enabled features:
- with-faillock

Confirm the live stack contains both faillock and the listfile jump:

bash
grep -E 'pam_faillock|pam_listfile' /etc/authselect/system-auth
output
auth        required                                     pam_faillock.so preauth silent
auth        [success=1 default=ignore]                   pam_listfile.so item=user sense=allow file=/etc/security/faillock-exempt-users onerr=fail
auth        required                                     pam_faillock.so authfail
account     required                                     pam_faillock.so

Re-run authselect apply-changes after any edit inside /etc/authselect/custom/faillock-exempt/.


Verify normal users are locked

Create a non-exempt test user and set a known password:

bash
useradd -m rahul

Set a password you can use for the lockout test:

bash
echo 'rahul:LabPass123!' | chpasswd

Clear any old tally, then generate failed SSH logins (repeat until you have at least as many failures as deny):

bash
faillock --user rahul --reset

Run three deliberate wrong-password SSH attempts:

bash
for i in 1 2 3; do sshpass -p 'WrongPass!' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=1 rahul@127.0.0.1 true; done

Each attempt prints Permission denied — that is expected for a wrong password. Inspect the tally:

bash
faillock --user rahul
output
rahul:
When                Type  Source                                           Valid
2026-08-15 17:44:25 RHOST 127.0.0.1                                            V
2026-08-15 17:44:27 RHOST 127.0.0.1                                            V
2026-08-15 17:44:30 RHOST 127.0.0.1                                            V

The V column marks failures that are still valid for lockout. After deny = 3, even the correct password is rejected until unlock time expires or you reset the tally:

bash
sshpass -p 'LabPass123!' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=1 rahul@127.0.0.1 hostname
output
rahul@127.0.0.1: Permission denied (publickey,password).

That behavior confirms lockout for users not in the exempt file.


Verify excluded users are not locked

Create an exempt user, or use one already listed in /etc/security/faillock-exempt-users:

bash
useradd -m deepak

Set deepak's password for the exempt-user test:

bash
echo 'deepak:LabPass123!' | chpasswd

Reset the tally, then exceed the deny threshold with wrong passwords:

bash
faillock --user deepak --reset

Send five failed SSH logins — more than the deny = 3 limit:

bash
for i in 1 2 3 4 5; do sshpass -p 'WrongPass!' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=1 deepak@127.0.0.1 true; done

Check whether authfail recorded any failures:

bash
faillock --user deepak
output
deepak:
When                Type  Source                                           Valid

An empty table means pam_faillock authfail was skipped for each bad attempt. Log in with the correct password — it should still succeed:

bash
sshpass -p 'LabPass123!' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no deepak@127.0.0.1 hostname
output
vm1.lab.example

Wrong passwords still failed during the loop; the exempt user simply never accumulated a lockout tally.


Add or remove users from the exclusion list

You do not need to rewrite the PAM stack when the exception list changes. Edit /etc/security/faillock-exempt-users and retest.

Remove deepak temporarily so only serviceuser remains exempt:

bash
printf 'serviceuser\n' > /etc/security/faillock-exempt-users

Clear deepak's tally so the next failures reflect the updated list:

bash
faillock --user deepak --reset

Three failed logins should now populate deepak's tally because pam_listfile no longer skips authfail:

bash
for i in 1 2 3; do sshpass -p 'WrongPass!' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=1 deepak@127.0.0.1 true; done

Inspect deepak's tally — failures should appear now that he is not exempt:

bash
faillock --user deepak
output
deepak:
When                Type  Source                                           Valid
2026-08-15 17:45:41 RHOST 127.0.0.1                                            V
2026-08-15 17:45:45 RHOST 127.0.0.1                                            V
2026-08-15 17:45:48 RHOST 127.0.0.1                                            V

Put deepak back on the exempt list and clear the lockout counter:

bash
printf 'deepak\nserviceuser\n' > /etc/security/faillock-exempt-users

Reset deepak's tally after restoring his exemption entry:

bash
faillock --user deepak --reset

After restoration, deepak can authenticate again without waiting for unlock_time.


Troubleshooting

Symptom Likely cause Fix
Exempt user still accumulates failures pam_listfile is after authfail or missing from password-auth Move listfile immediately after pam_unix, before authfail, in every auth file SSH uses
Exempt user skips lockout but wrong passwords succeed success=N too high — stack jumps past pam_deny Use success=1 on stacks where pam_deny follows authfail directly; recount modules on your host
Manual PAM edits disappeared authselect regenerated files Put rules in /etc/authselect/custom/<profile>/ and run authselect apply-changes
Configuration looks correct but SSH behaves differently sshd uses password-auth, not system-auth directly Patch password-auth in the custom profile; confirm with grep pam_listfile /etc/pam.d/sshd chain
User locked despite exemption Stale faillock tally from earlier tests faillock --user username --reset before retesting
Auth breaks on IdM/SSSD host local-based custom profile replaced sssd This guide targets the local profile only; do not switch SSSD-managed hosts without testing --base-on=sssd

Summary

Excluding specific users from pam_faillock lockout on RHEL-family systems with the authselect local profile is a PAM ordering problem, not a faillock.conf keyword. authselect enables pam_faillock with with-faillock; a custom profile based on local adds pam_listfile between pam_unix and pam_faillock authfail so names in /etc/security/faillock-exempt-users skip failure accounting while everyone else still hits authfail and can lock after deny failures.

The critical control is [success=1 default=ignore] on the listfile line — enough to skip authfail once, not so many that failed logins bypass pam_deny. Maintain the username file for day-to-day changes; rerun faillock --user to inspect tallies and faillock --user --reset when old failures skew tests. For baseline faillock setup and SSH hardening context, see prevent brute force SSH attacks and implement a password policy.


References


Frequently Asked Questions

1. Can faillock.conf exclude specific users from lockout?

No. faillock.conf sets global deny counts, intervals, and unlock_time for pam_faillock, but it does not provide an arbitrary per-user exemption list. Use pam_listfile with a username file and place the rule in the PAM stack between pam_unix and pam_faillock authfail.

2. Why use authselect instead of editing system-auth directly?

On RHEL-family systems with the authselect local profile, authselect generates /etc/pam.d/system-auth and password-auth. Manual edits are overwritten on the next authselect apply-changes. A custom authselect profile keeps the exemption rule persistent and auditable.

3. What does success=1 mean on the pam_listfile line?

When pam_listfile returns success for an exempt user, PAM skips exactly one following module — pam_faillock authfail — so failed passwords do not increment the faillock tally. The login still fails on pam_deny when the password is wrong.

4. How do I clear an existing faillock tally before retesting?

Run faillock --user username --reset as root. An old tally can make it look like exclusion failed when the account was already locked from earlier attempts.
Omer Cakmak

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration