How to Configure PAM on RHEL 7

Pluggable Authentication Modules (PAM) is the authentication framework that underlies almost every login mechanism on Red Hat Enterprise Linux 7. When a user runs su, logs in over SSH, unlocks a screensaver, or authenticates to any PAM-aware application, the PAM stack determines whether that authentication succeeds, what restrictions apply, and what happens to the session. Because PAM is responsible for enforcing password complexity rules, account lockout policies, resource limits, and access control, understanding how to configure it correctly is one of the most important system administration skills on RHEL 7. This tutorial covers the PAM stack model, the layout of the /etc/pam.d/ directory, control flag semantics, and how to configure four of the most commonly needed PAM modules: pam_pwquality for password complexity, pam_tally2 for brute-force lockout, pam_limits for resource constraints, and pam_access for host and user-level access control.

Prerequisites

  • RHEL 7 system with root access.
  • A basic understanding of the Linux login process.
  • A second terminal or console session open before making PAM changes — a misconfigured PAM file can lock you out of the system entirely.
  • The packages pam, libpwquality, and pam_tally2 are all included in the base RHEL 7 install.

Step 1: The PAM Stack Model

Every PAM-aware service has a configuration file under /etc/pam.d/ named after the service (for example, /etc/pam.d/sshd, /etc/pam.d/sudo, /etc/pam.d/login). Each file contains a list of rules called a stack. PAM processes the stack from top to bottom, and the combined result of all applicable rules determines whether the operation is permitted.

Each rule line has four fields:

type    control    module-path    [module-arguments]

Module Types

  • auth — Verifies the user’s identity (password, token, fingerprint). Also sets credentials.
  • account — Checks account restrictions: is the account expired? Is login allowed at this time of day? Has the password expired?
  • password — Handles password changes. Enforces complexity rules and updates the credential store.
  • session — Sets up and tears down the user session: mounting home directories, applying limits, logging.

Control Flags

  • required — The module must succeed for the overall result to be success. If it fails, processing continues through the stack (so the user does not know exactly which module failed), but the final result is failure.
  • requisite — Like required, but on failure PAM stops processing immediately and returns failure. Use for fast-fail checks.
  • sufficient — If this module succeeds and no previous required module has failed, PAM immediately returns success and stops processing. If it fails, processing continues.
  • optional — The result is only significant if it is the only module of this type in the stack. Used for modules that provide supplementary functionality (such as session logging) rather than access control.

Step 2: The /etc/pam.d/ Directory Structure

Inspect the directory to see the service-specific files:

ls /etc/pam.d/

You will see files like sshd, sudo, su, login, system-auth, and password-auth. The last two are the most important: they are shared stacks included by most other service files via the include directive, meaning a change to system-auth automatically propagates to all services that include it.

grep include /etc/pam.d/sshd
auth       include      password-auth
account    include      password-auth
password   include      password-auth
session    optional     pam_keyinit.so force revoke
session    include      password-auth

On RHEL 7, these shared files are managed by authconfig. Changes made directly to /etc/pam.d/system-auth may be overwritten if authconfig is run. The safe approach is to edit the -ac suffixed files (system-auth-ac, password-auth-ac) and then let symlinks point to them, or to use the authconfig command to make changes.

Step 3: Configure Password Complexity with pam_pwquality

pam_pwquality replaces the older pam_cracklib on RHEL 7 and enforces rules for minimum length, character classes, dictionary checks, and more. Its system-wide settings live in /etc/security/pwquality.conf.

vi /etc/security/pwquality.conf
# Minimum acceptable password length
minlen = 14

# Minimum number of required character classes (lower, upper, digit, other)
minclass = 3

# Maximum number of characters in the same character class in a row
maxclassdif = 4

# Maximum number of consecutive same characters
maxrepeat = 3

# Credit for including an uppercase letter (-1 = require at least one)
ucredit = -1

# Credit for including a lowercase letter
lcredit = -1

# Credit for including a digit
dcredit = -1

# Credit for including a special character
ocredit = -1

# Reject passwords containing the user's name
gecoscheck = 1

# Number of previous passwords to remember (used with pam_pwhistory)
# (set in pam_pwhistory, not here)

The pam_pwquality module must be in the password stack. Check /etc/pam.d/system-auth:

grep pwquality /etc/pam.d/system-auth
password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=

If you need to pass additional arguments, you can set them inline or in pwquality.conf. Using the config file is cleaner for multiple settings.

Step 4: Configure Account Lockout with pam_tally2

pam_tally2 maintains a per-user count of failed authentication attempts and can lock accounts after a configured threshold. This is essential protection against brute-force password attacks on local accounts.

Edit /etc/pam.d/system-auth to add pam_tally2 to both the auth and account stacks:

vi /etc/pam.d/system-auth
# Add to the auth section — BEFORE the existing pam_unix.so line:
auth        required      pam_tally2.so deny=5 unlock_time=900 onerr=fail

# The existing unix auth line stays in place:
auth        sufficient    pam_unix.so nullok try_first_pass

# Add to the account section:
account     required      pam_tally2.so

Key arguments for pam_tally2:

  • deny=5 — Lock the account after 5 failed attempts.
  • unlock_time=900 — Automatically unlock after 900 seconds (15 minutes). Use unlock_time=0 for permanent lockout requiring manual intervention.
  • onerr=fail — If the module cannot access its tally file, fail securely (deny access).
  • even_deny_root — Also apply lockout to the root account (use with caution; ensures console access first).

To view and manage tally counts:

# View failed attempt count for a specific user
pam_tally2 --user=bob

# Manually reset (unlock) a locked account
pam_tally2 --user=bob --reset

Step 5: Apply Resource Limits with pam_limits

pam_limits enforces per-user and per-group resource limits defined in /etc/security/limits.conf (and files in /etc/security/limits.d/). These limits control things like the maximum number of open files, maximum number of processes, and CPU time — critical for preventing a single user or runaway process from exhausting system resources.

vi /etc/security/limits.conf
# Format: domain    type    item    value
# domain: username, @groupname, or * (all users)
# type: soft (advisory, user can increase up to hard) or hard (ceiling)

# Prevent a normal user from creating core dumps (security best practice)
*               hard    core            0

# Limit maximum number of open file descriptors for all users
*               soft    nofile          1024
*               hard    nofile          65536

# Limit for a specific high-load service account
webapp          soft    nofile          8192
webapp          hard    nofile          65536
webapp          soft    nproc           512
webapp          hard    nproc           1024

# Prevent forks bombs by limiting processes for regular users
@users          hard    nproc           200

For services like databases or web servers that need higher limits, place overrides in /etc/security/limits.d/:

cat > /etc/security/limits.d/mysql.conf <<'EOF'
mysql           soft    nofile          65536
mysql           hard    nofile          65536
mysql           soft    nproc           2048
mysql           hard    nproc           2048
EOF

Verify the module is loaded in the session stack:

grep pam_limits /etc/pam.d/system-auth
session     required      pam_limits.so

Step 6: Restrict Login Access with pam_access

pam_access enforces an access control list defined in /etc/security/access.conf. It allows you to permit or deny logins based on username, group membership, and source hostname or IP address.

vi /etc/security/access.conf
# Format: permission : users/groups : origins
# permission: + (allow) or - (deny)
# origins: hostname, IP address, LOCAL (console), ALL (any)

# Allow root only from the console and from the management network
+ : root : LOCAL
+ : root : 192.168.10.0/24
- : root : ALL

# Allow members of the sysadmin group from anywhere on the private network
+ : @sysadmin : 10.0.0.0/8
- : @sysadmin : ALL

# Deny all other users from remote access (must log in locally)
- : ALL : ALL EXCEPT LOCAL

Enable pam_access in the account stack of /etc/pam.d/system-auth:

vi /etc/pam.d/system-auth
# Add in the account section:
account     required      pam_access.so

Test thoroughly before relying on this in production — an incorrect rule can lock out all remote access.

Conclusion

PAM is one of the most powerful and most dangerous subsystems to configure on RHEL 7: a single misplaced line can lock out all users, including root. Always keep a second terminal session open while editing PAM files, and always test changes by opening a fresh login session before closing the one you used to make the change. The combination of pam_pwquality for strong passwords, pam_tally2 for lockout, pam_limits for resource control, and pam_access for network-based access restrictions gives you a well-rounded hardening baseline that meets the requirements of most security frameworks. Because the shared system-auth and password-auth stacks propagate changes to all PAM-aware services automatically, you can enforce consistent policy across SSH, su, sudo, and local console logins from a single configuration point.