Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

PAM (Pluggable Authentication Modules)

Introduction

PAM (Pluggable Authentication Modules) is a framework for system authentication and session management on Linux and other Unix-like operating systems. Originally developed by Sun Microsystems in 1995 and standardized by the Open Group, PAM decouples authentication logic from applications — programs like login, sshd, sudo, and passwd don’t need to know how a user is authenticated. They just ask PAM, and PAM uses its configured modules to handle the details.

This separation is powerful: you can change authentication from local passwords to LDAP, add two-factor authentication, enforce password complexity, or integrate with Active Directory — all without recompiling a single application. You just change the PAM configuration.

Architecture

flowchart TB
    subgraph "Applications"
        LOGIN[login]
        SSHD[sshd]
        SUDO[sudo]
        PASSWD[passwd]
        SU[su]
        DM[Display Managers]
    end

    subgraph "PAM Library (libpam)"
        API[PAM API]
        CONF["Configuration<br>/etc/pam.d/"]
    end

    subgraph "PAM Modules"
        UNIX["pam_unix.so<br>Local passwords"]
        LDAP["pam_ldap.so<br>LDAP/AD auth"]
        TOTP["pam_google_authenticator.so<br>2FA/TOTP"]
        PWQUAL["pam_pwquality.so<br>Password strength"]
        LIMITS["pam_limits.so<br>Resource limits"]
        ENV["pam_env.so<br>Environment vars"]
        SYSTEMD["pam_systemd.so<br>Login sessions"]
        FAIL["pam_faillock.so<br>Account lockout"]
        SECURE["pam_securetty.so<br>Root login restrictions"]
    end

    subgraph "Backends"
        SHADOW["/etc/shadow"]
        LDAP_SRV[LDAP Server]
        AD[Active Directory]
        TOTP_KEY["~/.google_authenticator"]
    end

    LOGIN --> API
    SSHD --> API
    SUDO --> API
    PASSWD --> API
    SU --> API
    DM --> API

    API --> CONF
    CONF --> UNIX
    CONF --> LDAP
    CONF --> TOTP
    CONF --> PWQUAL
    CONF --> LIMITS
    CONF --> ENV
    CONF --> SYSTEMD
    CONF --> FAIL
    CONF --> SECURE

    UNIX --> SHADOW
    LDAP --> LDAP_SRV
    LDAP --> AD
    TOTP --> TOTP_KEY

PAM Management Groups

PAM organizes its work into four management groups, each handling a different aspect of the authentication lifecycle:

sequenceDiagram
    participant App
    participant PAM
    participant Modules

    App->>PAM: pam_authenticate()
    Note over PAM: auth group
    PAM->>Modules: Check credentials
    Modules-->>PAM: OK / FAIL
    PAM-->>App: Result

    App->>PAM: pam_acct_mgmt()
    Note over PAM: account group
    PAM->>Modules: Check account validity
    Modules-->>PAM: OK / FAIL
    PAM-->>App: Result

    App->>PAM: pam_open_session()
    Note over PAM: session group
    PAM->>Modules: Setup session
    Modules-->>PAM: OK / FAIL
    PAM-->>App: Result

    App->>PAM: pam_chauthtok()
    Note over PAM: password group
    PAM->>Modules: Change authentication token
    Modules-->>PAM: OK / FAIL
    PAM-->>App: Result
GroupPurposeTypical Modules
authVerify user identitypam_unix, pam_ldap, pam_google_authenticator
accountCheck account validity (expired, allowed hours)pam_unix, pam_ldap, pam_time
passwordChange authentication tokenspam_unix, pam_pwquality, pam_ldap
sessionSetup/teardown user sessionspam_limits, pam_systemd, pam_env, pam_mkhomedir

Configuration Files

File Locations

# Main PAM configuration file (used as fallback)
/etc/pam.conf          # Rarely used on modern systems

# Per-application configuration (preferred)
/etc/pam.d/            # Directory with per-service files
/etc/pam.d/login       # Configuration for login
/etc/pam.d/sshd        # Configuration for sshd
/etc/pam.d/sudo        # Configuration for sudo
/etc/pam.d/common-*    # Shared include files (Debian/Ubuntu)

Configuration File Format

Each line in a PAM configuration file follows this format:

type  control  module  [arguments...]
# Example: /etc/pam.d/login
# Type    Control     Module                    Arguments
auth      required    pam_securetty.so
auth      required    pam_unix.so               nullok
auth      required    pam_nologin.so
account   required    pam_unix.so
password  required    pam_unix.so               nullok obscure min=4 max=8
session   required    pam_unix.so
session   required    pam_loginuid.so
session   optional    pam_lastlog.so

Control Flags

The control field determines how a module’s result affects the overall outcome:

FlagMeaning
requiredMust succeed. Continues checking other modules before returning failure.
requisiteMust succeed. Returns failure immediately if this module fails.
sufficientIf this succeeds AND no prior required module failed, the group succeeds immediately. If it fails, the result is ignored.
optionalOnly matters if it’s the only module in the group.
includeInclude the contents of another file.
substackLike include, but module failures don’t propagate to the parent.
flowchart TD
    A["Module 1: required"] -->|Success| B["Module 2: required"]
    A -->|Failure| F[Continue checking, but final result = FAIL]
    B -->|Success| C["Module 3: sufficient"]
    B -->|Failure| F
    C -->|Success| S["Final result = SUCCESS<br>Skip remaining modules"]
    C -->|Failure| D["Module 4: optional"]
    D -->|Either| E[Final result = determined by required modules]

    style S fill:#90EE90
    style F fill:#FF6347

Advanced Control Syntax

For fine-grained control, use the bracket syntax:

# Example: return success if module succeeds, ignore if it fails
auth [success=ok ignore=ignore default=bad] pam_unix.so

# Example: skip 1 module on success, 2 on ignore
auth [success=1 ignore=2 default=bad] pam_ldap.so
auth required pam_unix.so

# Common return values: ok, done, bad, die, ignore, reset

Core PAM Modules

pam_unix — Local Authentication

The fundamental module for traditional Unix authentication using /etc/passwd and /etc/shadow.

# /etc/pam.d/common-auth (Debian/Ubuntu)
auth    [success=1 default=ignore]      pam_unix.so nullok_secure
auth    requisite                       pam_deny.so
auth    required                        pam_permit.so

# /etc/pam.d/common-account
account [success=1 new_authtok_reqd=done default=ignore]  pam_unix.so
account requisite                       pam_deny.so
account required                        pam_permit.so

# /etc/pam.d/common-password
password [success=1 default=ignore]     pam_unix.so obscure use_authtok try_first_pass sha512
password requisite                      pam_deny.so
password required                       pam_permit.so

# /etc/pam.d/common-session
session [default=1]                     pam_unix.so
session requisite                       pam_deny.so
session required                        pam_permit.so
# Arguments for pam_unix:
# nullok         — Allow blank passwords
# nullok_secure  — Allow blank passwords only from secure ttys
# try_first_pass — Use password from previous module
# use_authtok    — Use password from previous module (don't prompt)
# shadow         — Use shadow passwords (default)
# md5/sha256/sha512 — Password hashing algorithm
# obscure        — Check for trivial passwords
# min=N          — Minimum password length
# max=N          — Maximum password length
# rounds=N       — Number of hash rounds

pam_ldap — LDAP Authentication

# /etc/pam.d/common-auth
auth    sufficient    pam_ldap.so use_first_pass
auth    required      pam_unix.so nullok_secure try_first_pass

# /etc/pam.d/common-account
account sufficient    pam_ldap.so
account required      pam_unix.so

# /etc/pam.d/common-password
password sufficient   pam_ldap.so
password required     pam_unix.so nullok obscure min=8

# /etc/pam.d/common-session
session optional      pam_ldap.so
session required      pam_unix.so
# Install LDAP PAM module
sudo apt install libpam-ldap        # Debian/Ubuntu
sudo dnf install nss-pam-ldapd      # RHEL/Fedora

# Configure LDAP connection
sudo dpkg-reconfigure ldap-auth-config
# Or edit /etc/ldap.conf:
# uri ldap://ldap.example.com
# base dc=example,dc=com
# bind_policy soft
# pam_lookup_policy yes
# pam_password md5

pam_google_authenticator — Two-Factor Authentication

# Install
sudo apt install libpam-google-authenticator

# Set up for a user
google-authenticator
# Do you want authentication tokens to be time-based? y
# ... (QR code displayed) ...
# Your new secret key is: JBSWY3DPEHPK3PXP
# Enter code from app: 123456
# Code confirmed
# Do you want me to update your file? y
# Do you want to disallow multiple uses? y
# Do you want to increase time window? n
# Do you want to enable rate-limiting? y

# This creates ~/.google_authenticator with the secret and settings
# /etc/pam.d/sshd — Add Google Authenticator
# Place BEFORE pam_unix.so for "password + TOTP" flow:
auth    required    pam_google_authenticator.so
auth    required    pam_unix.so

# Or for "password first, then TOTP" (two separate prompts):
auth    required    pam_unix.so
auth    required    pam_google_authenticator.so

# Arguments:
# secret=/path/to/file  — Custom secret file location
# window=N              — Number of time windows to check (default: 3)
# debug                 — Enable debug logging
# nullok                — Allow users without 2FA to log in
# echo_verification_code — Show the code as user types
# sshd must also be configured:
# /etc/ssh/sshd_config
# ChallengeResponseAuthentication yes
# UsePAM yes

sudo systemctl restart sshd

pam_pwquality — Password Strength

# /etc/pam.d/common-password
password  requisite   pam_pwquality.so retry=3 minlen=12 difok=3
password  required    pam_unix.so use_authtok sha512

# /etc/security/pwquality.conf
# minlen = 12          # Minimum length
# dcredit = -1         # At least 1 digit
# ucredit = -1         # At least 1 uppercase
# lcredit = -1         # At least 1 lowercase
# ocredit = -1         # At least 1 special character
# difok = 3            # At least 3 chars different from old password
# maxrepeat = 3        # Max 3 consecutive identical chars
# reject_username      # Reject passwords containing username
# enforce_for_root     # Also enforce for root
# dictpath = /usr/share/dict/words  # Dictionary check

pam_faillock / pam_tally2 — Account Lockout

# RHEL/Fedora: pam_faillock
# /etc/pam.d/system-auth
auth        required      pam_faillock.so preauth silent deny=5 unlock_time=900
auth        required      pam_unix.so
auth        [default=die] pam_faillock.so authfail deny=5 unlock_time=900
account     required      pam_faillock.so

# Arguments:
# deny=N       — Lock after N failed attempts
# unlock_time=N — Lock for N seconds (0 = permanent until admin unlock)
# fail_interval=N — Time window for counting failures
# even_deny_root — Also lock root account
# root_unlock_time=N — Root lockout duration

# Check lockout status
sudo faillock --user user1
# user1:
# When                Type  Source             Valid
# 2026-07-21 10:00:00 TTY   /dev/pts/0         V

# Unlock a user
sudo faillock --user user1 --reset

pam_limits — Resource Limits

# /etc/pam.d/common-session
session required pam_limits.so

# /etc/security/limits.conf
# <domain>  <type>  <item>  <value>
# domain = username, @group, *, or %
# type = soft (default), hard
# item = nproc, nofile, memlock, cpu, etc.

*           soft    nproc     1024
*           hard    nproc     4096
root        soft    nproc     unlimited
@developers soft    nofile    65535
@developers hard    nofile    131072
@database   soft    memlock   unlimited
@database   hard    memlock   unlimited

pam_systemd — Login Session Management

# Automatically included in modern systemd-based systems
# Creates a login session with:
# - A unique session ID
# - A cgroup for resource management
# - A D-Bus session bus
# - A seat assignment (for multi-seat)

# /etc/pam.d/common-session
session optional pam_systemd.so

# Check sessions
loginctl list-sessions
# SESSION  UID USER   SEAT  TTY   STATE  IDLE
#      2  1000 user1  seat0 pts/0 active no

loginctl show-session 2
# Id=2
# User=1000
# Name=user1
# State=active
# Type=tty
# Display=/dev/pts/0
# Remote=no
# Service=login
# Seat=seat0
# Leader=1234
# ...

pam_env — Environment Variables

# /etc/pam.d/common-session
session required pam_env.so readenv=1

# /etc/environment (system-wide)
PATH="/usr/local/sbin:/usr/local/bin:/usr/bin"
LANG="en_US.UTF-8"

# /etc/security/pam_env.conf
# MY_VAR   DEFAULT="some_value"   OVERRIDE=${HOME}/.myenv
# TERM     DEFAULT="xterm"        OVERRIDE="${TERM}"

pam_mkhomedir — Create Home Directory

# Automatically create home directory on first login
# /etc/pam.d/common-session
session required pam_mkhomedir.so skel=/etc/skel umask=0077

# Arguments:
# skel=/etc/skel  — Copy skeleton files from here
# umask=0077      — Set permissions on new home directory

PAM Stacking: Practical Examples

SSH with LDAP + 2FA + Password Policy

# /etc/pam.d/sshd
# Authentication stack:
auth    required    pam_env.so
auth    required    pam_faillock.so preauth silent deny=5 unlock_time=900
auth    sufficient  pam_ldap.so use_first_pass
auth    required    pam_unix.so try_first_pass
auth    required    pam_google_authenticator.so nullok
auth    required    pam_faillock.so authfail deny=5 unlock_time=900

# Account stack:
account required    pam_unix.so
account required    pam_ldap.so
account required    pam_time.so

# Password stack:
password requisite  pam_pwquality.so retry=3 minlen=12
password sufficient pam_ldap.so use_authtok
password required   pam_unix.so use_authtok sha512

# Session stack:
session required    pam_limits.so
session required    pam_unix.so
session required    pam_mkhomedir.so skel=/etc/skel umask=0077
session required    pam_loginuid.so
session optional    pam_systemd.so

Sudo with 2FA

# /etc/pam.d/sudo
auth    required    pam_google_authenticator.so
auth    required    pam_unix.so try_first_pass
account required    pam_unix.so
session required    pam_unix.so

Login with Restrictive Policies

# /etc/pam.d/login
auth      required   pam_securetty.so          # Restrict root to secure ttys
auth      required   pam_nologin.so             # Deny logins if /etc/nologin exists
auth      required   pam_env.so
auth      required   pam_faillock.so preauth silent deny=3
auth      required   pam_unix.so
auth      required   pam_faillock.so authfail deny=3
account   required   pam_unix.so
account   required   pam_time.so                 # Time-based access control
password  requisite  pam_pwquality.so minlen=12
password  required   pam_unix.so sha512 shadow
session   required   pam_limits.so
session   required   pam_unix.so
session   required   pam_loginuid.so
session   required   pam_lastlog.so showfailed
session   optional   pam_mail.so

Custom PAM Module

You can write custom PAM modules in C:

/* pam_hello.c — A trivial PAM module that prints a greeting */
#include <security/pam_modules.h>
#include <security/pam_ext.h>
#include <stdio.h>

PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
                                    int argc, const char **argv) {
    const char *user;
    int ret;

    ret = pam_get_user(pamh, &user, NULL);
    if (ret != PAM_SUCCESS) {
        return ret;
    }

    pam_info(pamh, "Hello, %s! Custom PAM module says welcome.", user);

    /* Always succeed */
    return PAM_SUCCESS;
}

PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags,
                               int argc, const char **argv) {
    return PAM_SUCCESS;
}

PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
                                 int argc, const char **argv) {
    return PAM_SUCCESS;
}
# Compile
gcc -shared -fPIC -o pam_hello.so pam_hello.c -lpam

# Install
sudo cp pam_hello.so /lib/security/   # or /lib/x86_64-linux-gnu/security/

# Add to a service
echo "auth optional pam_hello.so" | sudo tee -a /etc/pam.d/login

# Test
login
# Hello, user1! Custom PAM module says welcome.

Debugging PAM

# Enable PAM debugging (module-specific)
# Add 'debug' argument to any module:
# auth required pam_unix.so debug

# Check syslog for PAM messages
sudo journalctl -u sshd | grep PAM
# Jul 21 10:00:00 server sshd[1234]: PAM unable to dlopen(/lib/security/pam_ldap.so)
# Jul 21 10:00:00 server sshd[1234]: PAM adding faulty module: /lib/security/pam_ldap.so

# Test PAM configuration
sudo pamtester login user1 authenticate
# Password: ****
# pamtester: Successfully authenticated

# Test account validity
sudo pamtester login user1 acct_mgmt

# Test session
sudo pamtester login user1 open_session close_session

# Trace PAM calls (requires debug build)
# /etc/pam.d/sshd
# auth required pam_unix.so debug trace

# Common PAM errors:
# PAM_AUTH_ERR        — Authentication failure
# PAM_CRED_ERR        — Failed to set credentials
# PAM_ACCT_EXPIRED    — Account has expired
# PAM_PERM_DENIED     — Permission denied
# PAM_USER_UNKNOWN    — User not found
# PAM_SESSION_ERR     — Session setup failed
# PAM_AUTHTOK_ERR     — Password change failed

Security Considerations

# 1. Configuration file permissions MUST be strict
sudo chmod 644 /etc/pam.d/*
sudo chown root:root /etc/pam.d/*
# PAM may refuse to operate if permissions are wrong

# 2. Never put 'sufficient' before 'required' for critical auth
# BAD:
#   auth sufficient pam_permit.so        ← Always succeeds, bypasses everything after
#   auth required   pam_unix.so          ← Never reached!
# GOOD:
#   auth required   pam_unix.so          ← Must succeed
#   auth sufficient pam_ldap.so          ← Additional check

# 3. Use 'required' over 'optional' for security-critical modules
# 'optional' means the module's failure is ignored

# 4. Be careful with 'pam_permit.so' — it ALWAYS allows
# Only use in testing or for modules where denial doesn't apply

# 5. Keep /etc/shadow permissions tight
ls -l /etc/shadow
# -rw-r----- 1 root shadow 1234 Jul 21 10:00 /etc/shadow

# 6. Use pam_faillock to prevent brute-force attacks

# 7. Ensure proper module ordering:
#    - pam_env.so first (set environment)
#    - pam_faillock.so preauth (check lockout before auth)
#    - Actual authentication (pam_unix, pam_ldap, pam_google_authenticator)
#    - pam_faillock.so authfail (record failures after auth)
#    - Account checks (pam_unix, pam_time)
#    - Session setup (pam_limits, pam_mkhomedir, pam_systemd)

References

  • The Linux Kernel Documentation

  • LWN.net - Linux and free software news

  • GNU Project Documentation

  • GNU Manuals

  • Free Software Directory

  • Planet GNU

  • Free Software Books

  • Linux-PAM System Administrators’ Guide: https://linux-pam.org/Linux-PAM-html/Linux-PAM_SAG.html

  • Linux-PAM Module Writers’ Guide: https://linux-pam.org/Linux-PAM-html/Linux-PAM_MWG.html

  • man 5 pam.conf — PAM configuration file format

  • man 8 pam_unix — Unix PAM module

  • man 8 pam_ldap — LDAP PAM module

  • man 8 pam_google_authenticator — Google Authenticator PAM module

  • man 8 pam_pwquality — Password quality PAM module

  • man 8 pam_faillock — Account lockout PAM module

  • man 8 pam_limits — Resource limits PAM module

  • man 8 pam_systemd — systemd session PAM module

  • man 1 pamtester — PAM testing utility

  • The Linux-PAM project: https://linux-pam.org/

  • Google Authenticator PAM module: https://github.com/google/google-authenticator-libpam

  • Linux Security Overview — Where PAM fits in the security architecture
  • Security Model — Users, groups, and the authentication PAM performs
  • SELinux — MAC that operates independently of PAM authentication
  • AppArmor — MAC that complements PAM’s authentication
  • Cryptography — Password hashing algorithms used by PAM
  • Hardening — PAM configuration as part of system hardening