| Tested on | Red Hat Enterprise Linux 10.2 (lab VM) |
|---|---|
| Package | authselect 1.5.2pam 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 checkingauthfail— 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:
- Enable
with-faillockthrough authselect. - After
pam_unixverifies the password, runpam_listfileagainst a username file. - If the user is listed, skip
pam_faillock authfailwith[success=1 default=ignore]. - Wrong passwords still fail at
pam_deny; exempt users simply do not grow a faillock tally onauthfail.
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:
authselect currentProfile ID: local
Enabled features:
- with-fingerprintIf 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:
grep -vE '^(#|$)' /etc/security/faillock.confdeny = 3
unlock_time = 600This 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:
grep pam_faillock /etc/pam.d/system-auth /etc/pam.d/password-auth/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.sosshd 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:
cat <<'EOF' > /etc/security/faillock-exempt-users
deepak
serviceuser
EOFShow the file contents before you edit the authselect profile:
cat /etc/security/faillock-exempt-usersdeepak
serviceuserOne 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:
authselect create-profile faillock-exempt --base-on=localNew profile was created at /etc/authselect/custom/faillock-exemptThe 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:
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.soApply 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:
pam_unixdoes not return success.pam_listfilereturns ignore (name absent).pam_faillock authfailrecords the failure.pam_denyrejects the login.
Flow for an exempt user with a wrong password:
pam_unixfails.pam_listfilesucceeds → skipauthfailonly (success=1).pam_denystill rejects the bad password — no free login.- No new failure row is added by
authfail.
Flow for an exempt user with the correct password:
pam_unixsucceeds early assufficient— later modules, includingpam_listfileandauthfail, 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:
deny = 3
unlock_time = 600These 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:
authselect select custom/faillock-exempt with-faillockRegenerate the managed PAM files from the custom profile:
authselect apply-changesConfirm authselect reports the custom profile and faillock feature:
authselect currentProfile ID: custom/faillock-exempt
Enabled features:
- with-faillockConfirm the live stack contains both faillock and the listfile jump:
grep -E 'pam_faillock|pam_listfile' /etc/authselect/system-authauth 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.soRe-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:
useradd -m rahulSet a password you can use for the lockout test:
echo 'rahul:LabPass123!' | chpasswdClear any old tally, then generate failed SSH logins (repeat until you
have at least as many failures as deny):
faillock --user rahul --resetRun three deliberate wrong-password SSH attempts:
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; doneEach attempt prints Permission denied — that is expected for a wrong
password. Inspect the tally:
faillock --user rahulrahul:
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 VThe 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:
sshpass -p 'LabPass123!' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=1 rahul@127.0.0.1 hostnamerahul@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:
useradd -m deepakSet deepak's password for the exempt-user test:
echo 'deepak:LabPass123!' | chpasswdReset the tally, then exceed the deny threshold with wrong passwords:
faillock --user deepak --resetSend five failed SSH logins — more than the deny = 3 limit:
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; doneCheck whether authfail recorded any failures:
faillock --user deepakdeepak:
When Type Source ValidAn empty table means pam_faillock authfail was skipped for each bad
attempt. Log in with the correct password — it should still succeed:
sshpass -p 'LabPass123!' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no deepak@127.0.0.1 hostnamevm1.lab.exampleWrong 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:
printf 'serviceuser\n' > /etc/security/faillock-exempt-usersClear deepak's tally so the next failures reflect the updated list:
faillock --user deepak --resetThree failed logins should now populate deepak's tally because
pam_listfile no longer skips authfail:
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; doneInspect deepak's tally — failures should appear now that he is not exempt:
faillock --user deepakdeepak:
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 VPut deepak back on the exempt list and clear the lockout counter:
printf 'deepak\nserviceuser\n' > /etc/security/faillock-exempt-usersReset deepak's tally after restoring his exemption entry:
faillock --user deepak --resetAfter 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
- authselect(8) — profile selection and custom profiles
- pam_faillock(8) — failure counting and lockout
- faillock.conf(5) — global deny and unlock settings
- pam_listfile(8) — username allow and deny lists
- Red Hat — authselect and PAM — supported profile workflow

