Pluggable Authentication Modules (PAM) is the authentication framework at the heart of every RHEL 8 login, password change, and session management operation. PAM allows system administrators to configure authentication policies without modifying individual applications — by editing stack files in /etc/pam.d/, you can enforce account lockout after failed attempts, require strong passwords, limit resource usage, and control session behavior uniformly across all services. Understanding the PAM stack is essential for hardening any RHEL 8 system. This tutorial covers the four PAM module types and the most impactful security modules available.

Prerequisites

  • RHEL 8 server with sudo or root access
  • Familiarity with basic Linux authentication concepts
  • Caution: Misconfigured PAM can lock all users out of the system — always keep a root terminal session open while editing PAM files, and test changes carefully
  • A snapshot or backup of the system recommended before making PAM changes

Step 1 — Understand the PAM Stack Structure

PAM configuration files in /etc/pam.d/ define a stack of modules for each service. Every line has four fields: type, control, module-path, and module-arguments. Understanding how control flags interact determines whether PAM continues, stops, or fails the entire authentication attempt.

# List PAM service configuration files
ls /etc/pam.d/ | head -20

# Examine the SSH PAM stack
cat /etc/pam.d/sshd

# PAM types:
#   auth     — verify identity (password, key, token)
#   account  — check account validity (expired, locked, time restrictions)
#   password — handle password updates and complexity
#   session  — set up and tear down session resources

# PAM control flags:
#   required   — must succeed; failure is fatal but all modules still run
#   requisite  — must succeed; failure is immediately fatal
#   sufficient — if it succeeds (and no prior required failed), skip the rest
#   optional   — result ignored unless no other module succeeds

# View the common system-auth and password-auth included by many services
cat /etc/pam.d/system-auth
cat /etc/pam.d/password-auth

Step 2 — Configure Account Lockout with pam_faillock

pam_faillock.so is the RHEL 8 replacement for the older pam_tally2. It tracks failed authentication attempts per user and locks accounts after a configurable threshold, making brute-force attacks impractical.

# pam_faillock is configured via /etc/security/faillock.conf on RHEL 8
sudo cp /etc/security/faillock.conf /etc/security/faillock.conf.bak

sudo tee /etc/security/faillock.conf > /dev/null <<'EOF'
# Lock account after 5 failed attempts within 15 minutes
deny = 5
# Lock duration: 900 seconds (15 minutes)
unlock_time = 900
# Count failures in a 15-minute window
fail_interval = 900
# Also apply lockout to root (remove this line to exempt root)
even_deny_root
# Store tally files in /var/run/faillock/
dir = /var/run/faillock
EOF

# Verify pam_faillock is enabled in system-auth
# It should appear as preauth and authfail entries in the auth stack
grep faillock /etc/pam.d/system-auth

# If not present, add it (backup first!)
sudo cp /etc/pam.d/system-auth /etc/pam.d/system-auth.bak

# Check current locked accounts
sudo faillock --list

# Reset a specific user's lockout counter
# sudo faillock --user username --reset

echo "pam_faillock configuration applied"

Step 3 — Enforce Password Quality with pam_pwquality

pam_pwquality.so validates new passwords against a configurable policy before accepting them. It integrates with the password PAM type and reads its settings from /etc/security/pwquality.conf.

# Install libpwquality if not already present
sudo dnf install -y libpwquality

# Configure password quality rules
sudo cp /etc/security/pwquality.conf /etc/security/pwquality.conf.bak

sudo tee /etc/security/pwquality.conf > /dev/null <<'EOF'
# Minimum password length
minlen = 14
# Minimum number of digits required (negative = minimum count)
dcredit = -1
# Minimum number of uppercase letters required
ucredit = -1
# Minimum number of lowercase letters required
lcredit = -1
# Minimum number of special characters required
ocredit = -1
# Number of characters that must differ from the old password
difok = 4
# Reject passwords that contain the user's name
gecoscheck = 1
# Maximum number of allowed consecutive same characters
maxrepeat = 3
# Minimum number of character classes that must be present
minclass = 3
EOF

# Verify pam_pwquality is in the password stack
grep pwquality /etc/pam.d/system-auth

# Test the policy without changing a password (dry run)
sudo pwscore <<< "weakpassword"
sudo pwscore <<< "C0mplex!Pass#2024"

Step 4 — Set Password Aging Policies

Password aging controls force periodic password changes and prevent immediate reuse of old passwords. These settings are applied via pam_unix.so and the /etc/login.defs file, which sets defaults for new accounts.

# Configure system-wide password aging defaults in /etc/login.defs
sudo cp /etc/login.defs /etc/login.defs.bak

# Set recommended aging values
sudo sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS   90/'  /etc/login.defs
sudo sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS   1/'   /etc/login.defs
sudo sed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE   14/'  /etc/login.defs

# Verify the changes
grep -E "^PASS_(MAX|MIN|WARN)" /etc/login.defs

# Apply aging settings to an existing user account
sudo chage -M 90 -m 1 -W 14 username

# View current aging info for a user
sudo chage -l username

# Configure password history (prevent reuse of last 5 passwords)
# This is set in /etc/pam.d/system-auth via pam_unix remember= option
grep "pam_unix" /etc/pam.d/system-auth | grep password

Step 5 — Limit Resources with pam_limits

pam_limits.so enforces per-user and per-group resource limits defined in /etc/security/limits.conf and files in /etc/security/limits.d/. Resource limits prevent runaway processes from exhausting system resources and are a key defense against local denial-of-service attacks.

# View the existing limits configuration
cat /etc/security/limits.conf | grep -v "^#" | grep -v "^$"

# Create a custom limits policy
sudo tee /etc/security/limits.d/99-hardening.conf > /dev/null <<'EOF'
# Limit format:    
# domain: username, @groupname, or * (all users)
# type: soft (warning), hard (enforced maximum)

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

# Limit maximum number of processes per user
*       soft    nproc       1024
*       hard    nproc       4096

# Disable core dumps for all users (security: no memory dumps)
*       hard    core        0

# Limit maximum CPU time (seconds) per process
*       hard    cpu         3600

# Limit stack size
*       soft    stack       8192
*       hard    stack       65536
EOF

# Verify pam_limits is loaded in the session stack
grep pam_limits /etc/pam.d/system-auth

# Check current limits for a user session
su - username -c "ulimit -a" 2>/dev/null || ulimit -a

Step 6 — Configure pam_env for Secure Environment Variables

pam_env.so sets and unsets environment variables at session start. It is useful for stripping dangerous environment variables from privileged sessions and ensuring a clean, predictable environment for all users.

# Review the pam_env configuration file
cat /etc/security/pam_env.conf 2>/dev/null || echo "File not present — using defaults"

# Confirm pam_env.so is present in the session stack
grep pam_env /etc/pam.d/system-auth

# Create an environment configuration to unset potentially dangerous variables
sudo tee /etc/security/pam_env.conf > /dev/null <<'EOF'
# Unset variables that could be used for privilege escalation or path manipulation
LD_PRELOAD            DEFAULT=""
LD_LIBRARY_PATH       DEFAULT=""
# Set a safe default PATH for all users
PATH                  DEFAULT=/usr/local/bin:/usr/bin:/bin
EOF

# Verify the complete PAM stack is consistent across auth files
echo "=== system-auth ===" && grep -v "^#" /etc/pam.d/system-auth | grep -v "^$"
echo "=== password-auth ===" && grep -v "^#" /etc/pam.d/password-auth | grep -v "^$"

# Check for any PAM configuration syntax warnings
sudo authselect check && echo "PAM configuration is consistent"

Conclusion

You have configured four critical PAM modules on RHEL 8: pam_faillock to lock accounts after repeated failed logins, pam_pwquality to enforce strong password complexity, pam_limits to restrict per-user resource consumption, and pam_env to sanitize the session environment. Together these controls address authentication brute-force, weak credentials, local denial-of-service, and environment-variable injection. Always use authselect check after modifying PAM files to catch inconsistencies, and test changes with a non-root user before closing your administrative session.

Next steps: Configuring two-factor authentication with Google Authenticator PAM on RHEL 8, Centralizing PAM authentication with SSSD and LDAP on RHEL 8, and Auditing PAM events with auditd on RHEL 8.