How to Configure PAM (Pluggable Authentication Modules) on Fedora

PAM on Fedora is configured through service-specific files in /etc/pam.d/, where each file defines an ordered stack of modules controlling authentication, account, session, and password management.

You changed a password policy and lost access

You edited a PAM configuration file to enforce stronger passwords or add two-factor authentication. The next login attempt hangs. Your terminal drops to a black screen with a Login incorrect message. You still have a second terminal open, but you know the next reboot could leave you locked out entirely. PAM configuration is unforgiving. A single misplaced flag stops the authentication chain dead.

What's actually happening

PAM sits between every login service and the actual credential check. When you run sudo, connect via SSH, or log in at the console, the service does not check /etc/shadow directly. It asks PAM. PAM reads a configuration file in /etc/pam.d/ that matches the service name. Each line in that file tells PAM which shared library to load, what to check, and how to react if the check fails. Think of it as a series of security checkpoints. The service is the traveler. PAM is the border control desk. Each module is an inspector with a specific rulebook. If one inspector says stop, the traveler never reaches the next desk.

Every PAM line follows a strict four-field format. The first field is the management group: auth, account, session, or password. The second field is the control flag: required, requisite, sufficient, or optional. The third field is the module name. The fourth field holds module-specific arguments. The control flag determines the flow. requisite fails immediately and returns to the calling service. required continues checking other modules but fails at the end. sufficient grants access immediately if it passes and no previous required module failed. optional only matters if it is the only module in the stack.

Fedora centralizes most policies in /etc/pam.d/system-auth and /etc/pam.d/password-auth. Individual services include these files instead of duplicating rules. Edit the central files to apply changes system-wide. Leave the service-specific files alone unless you are overriding a single daemon. system-auth handles login verification and account status. password-auth handles password changes and complexity checks. Most desktop and server services include both.

Files in /etc/pam.d/ are user-modified. The base templates ship in /usr/lib/pam.d/. Always edit /etc/. Never touch /usr/lib/. Package updates will overwrite /usr/lib/ and silently revert your changes.

Enforce password complexity

Fedora uses pam_pwquality to evaluate password strength. The module lives in /etc/security/pwquality.conf. Most services already include system-auth or password-auth, which reference the module. You only need to adjust the policy file.

Here's how to set a minimum length and require mixed character classes.

# /etc/security/pwquality.conf
# Minimum password length before other checks apply
minlen = 12
# Require at least one digit, one uppercase, one lowercase, one special
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
# Allow up to three retries during password change
retry = 3

The dcredit, ucredit, lcredit, and ocredit values use negative numbers to mandate character types. A value of -1 means at least one character from that class must appear. Positive values would limit how many characters from that class are allowed. Save the file and test with passwd. The system will reject weak passwords immediately.

Run passwd in a separate terminal before closing your current session. A bad policy can lock you out of your own account.

Add two-factor authentication for SSH

Two-factor authentication requires a PAM module that generates and verifies time-based one-time passwords. The google-authenticator-libpam package provides this functionality. You run the setup tool as the target user, then tell SSH to ask PAM for the second factor.

Here's how to install the module and generate the user's secret key.

sudo dnf install -y google-authenticator-libpam # Install the PAM module and CLI tool
su - targetuser # Switch to the user who will authenticate
google-authenticator # Run the interactive setup wizard

The wizard asks a series of questions. Accept the defaults for time-based tokens, limit login attempts to three, and disable rate limiting if you plan to use it across multiple devices. The tool prints a QR code and a secret key. Scan the code with an authenticator app. Save the emergency scratch codes in a secure location.

Next, you need to tell the SSH daemon to request the second factor. PAM handles the verification, but SSH must be configured to allow challenge-response authentication.

Here's how to enable the PAM module in the SSH configuration.

sudo sed -i '/#auth.*pam_google_authenticator.so/s/^#//' /etc/pam.d/sshd # Uncomment the module line if present
sudo grep -q 'pam_google_authenticator.so' /etc/pam.d/sshd || echo "auth required pam_google_authenticator.so nullok" | sudo tee -a /etc/pam.d/sshd # Add it if missing
sudo sed -i 's/^#ChallengeResponseAuthentication no/ChallengeResponseAuthentication yes/' /etc/ssh/sshd_config # Enable challenge-response in SSH
sudo systemctl restart sshd # Apply the new SSH configuration

The nullok argument allows users without a configured authenticator to log in with just a password. Remove nullok once every required user has completed the setup. Test the connection from a new terminal window. The prompt will ask for your password first, then the six-digit code.

Keep a second SSH session open until you verify the flow. A misconfigured PAM stack drops the connection before you can type a fix.

Lock accounts after failed attempts

Brute-force protection relies on pam_faillock. It tracks failed attempts per user and enforces a lockout window. The configuration lives in /etc/security/faillock.conf. You also need to add the module to the auth stack in system-auth so it actually runs before the password check.

Here's how to define the lockout thresholds.

# /etc/security/faillock.conf
# Number of failed attempts before lockout triggers
deny = 5
# Window in seconds to count failed attempts
fail_interval = 900
# Duration in seconds the account stays locked
unlock_time = 600
# Track failures even if the user does not exist
even_deny_root = yes

The fail_interval defines the rolling window. Five failures within fifteen minutes triggers the lock. The unlock_time of six hundred seconds means the account automatically unlocks after ten minutes. Adjust these values based on your threat model. Tighter windows reduce the attack surface but increase false positives for legitimate users.

Here's how to wire the module into the authentication stack.

sudo sed -i '/auth.*pam_faillock.so.*/d' /etc/pam.d/system-auth # Remove any existing faillock lines to prevent duplicates
sudo sed -i '/auth.*pam_unix.so/i auth required pam_faillock.so preauth silent audit deny=5 fail_interval=900' /etc/pam.d/system-auth # Add preauth check before password verification
sudo sed -i '/auth.*pam_unix.so/i auth [default=die] pam_faillock.so authfail audit deny=5' /etc/pam.d/system-auth # Add postauth check to record failures

The preauth line runs before the password check. It records the attempt and checks if the user is already locked. The authfail line runs after a failed password check. It increments the failure counter. The audit flag logs the event to the journal. The silent flag prevents duplicate error messages.

Run faillock --user root to view the current failure counter. The counter resets automatically after the unlock window expires.

Check the counter before you test. A locked root account requires a live rescue environment to reset.

Verify it worked

Test every change in a separate session. Use journalctl -xe to watch the authentication chain in real time. The x flag adds explanatory text to journal entries. The e flag jumps to the end of the log. Most sysadmins type journalctl -xeu sshd muscle-memory style to isolate daemon logs.

Here's how to monitor the authentication flow during a test login.

sudo journalctl -xeu sshd -f # Follow SSH logs with explanatory context
ssh user@localhost # Attempt a login from a second terminal

Look for Accepted password or Accepted publickey followed by pam_unix(sshd:session): session opened. If you see pam_faillock(sshd:auth): Authentication failure, the lockout module caught the attempt. If you see pam_pwquality(sshd:chauthtok): password quality check failed, the complexity policy rejected the change.

Run journalctl -xe first. Read the actual error before guessing.

Common pitfalls and what the error looks like

PAM errors rarely print clear messages to the user. The service usually returns a generic failure. You need to check the journal and audit logs to find the real cause.

sshd[1234]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.1.5 user=testuser
sshd[1234]: Failed password for testuser from 192.168.1.5 port 52413 ssh2

This output means the password check failed. It does not mean PAM is broken. Check /var/log/secure or journalctl -u sshd for module-specific messages. If you see pam_google_authenticator: user testuser not found, the user has not run the setup wizard yet. If you see pam_faillock: user testuser locked out, the account hit the failure threshold.

SELinux denials also block PAM modules. A mislabeled configuration file or a missing policy module stops authentication silently. Check the audit log for AVC denials.

sudo ausearch -m avc -ts recent | grep pam # Search for recent SELinux denials related to PAM
sudo restorecon -Rv /etc/pam.d/ # Restore default security contexts if they drifted

The restorecon command fixes context drift without changing file permissions. Run it after moving or copying PAM files. Never disable SELinux to fix an authentication issue. The denial message tells you exactly which process needs which permission.

Syntax errors in /etc/pam.d/ files cause immediate service failures. A missing space between fields or a typo in a module name breaks the entire stack. Always validate files before restarting services.

sudo grep -nE '^\s*(auth|account|session|password)\s+' /etc/pam.d/system-auth # Verify line structure matches PAM syntax
sudo systemctl status sshd # Check if the daemon is still running after config changes

Keep a second terminal open until you verify the flow. A misconfigured PAM stack drops the connection before you can type a fix.

When to use this vs alternatives

Use pam_pwquality when you need to enforce password complexity across all local login methods. Use pam_faillock when you want automatic account lockout after repeated failed attempts. Use pam_google_authenticator when you require time-based two-factor authentication for SSH or local logins. Use SSH key authentication when you want passwordless, cryptographically verified access without PAM overhead. Stay on the default Fedora PAM stack if your system only needs basic local authentication and standard sudo policies.

Where to go next