Skip to content

How PAM works

Many programs on a Linux system need to answer the same questions about a user. Is this person really the user they claim to be? Is the account allowed in right now? Is the new password good enough? What needs setting up before the user's shell starts? login, sshd, sudo, su, passwd, cron and graphical display managers all ask them.

If each program answered these questions itself, every one of them would need its own code for password hashes, account expiry, lockouts, one-time codes and directory services, and adding a new method (a hardware token, say) would mean patching all of them. Pluggable Authentication Modules (PAM) removes that duplication. Programs call a small library, libpam, and the administrator decides in configuration files which modules do the actual work for each program. On every distribution covered here the implementation is Linux-PAM.

Applications, the library and modules

flowchart LR
    app["Application<br/>sshd, sudo, login, passwd"] -->|"pam_start(&quot;sshd&quot;, user)"<br/>pam_authenticate()| lib["libpam"]
    lib -->|reads| conf["/etc/pam.d/sshd<br/>and files it includes"]
    lib -->|calls in order| m1["pam_faillock.so"]
    lib --> m2["pam_unix.so"]
    lib --> m3["pam_sss.so, pam_google_authenticator.so, …"]
    m2 -->|reads| shadow["/etc/shadow"]
    lib -->|"PAM_SUCCESS or an error"| app

An application that uses PAM starts a transaction with a service name, normally its own name: sshd, sudo, login, passwd. libpam looks for the file /etc/pam.d/<service>, and falls back to /etc/pam.d/other if there is none. The application then calls PAM functions at the points where it needs a decision: pam_authenticate() to check who the user is, pam_acct_mgmt() to check that the account may be used, pam_chauthtok() to change a password, and pam_open_session() and pam_close_session() around the user's session.

The application never sees a password hash or a policy. It only learns whether the answer was success or a particular kind of failure. When a module needs to ask the user something, such as a password or a one-time code, it does so through a conversation function the application supplied, so the same module works on a text terminal, over SSH and in a graphical login screen.

Modules are shared libraries, usually named pam_<something>.so, in /usr/lib/x86_64-linux-gnu/security/ on Debian and Ubuntu, /usr/lib64/security/ on the RHEL family and Gentoo, and /usr/lib/security/ on Arch. Each does one job: pam_unix checks passwords against /etc/shadow, pam_faillock counts failures, pam_pwquality or pam_passwdqc judges new passwords, pam_limits applies resource limits, pam_systemd registers the session with systemd-logind, and so on.

Four management groups

Each line of a PAM file belongs to one of four management groups, named by its first field. The application decides which group runs, through the function it calls.

Group Runs on Answers Typical modules
auth pam_authenticate(), and pam_setcred() Is the user who they claim to be? pam_unix, pam_faillock, pam_sss
account pam_acct_mgmt() May this account be used now? (expired, locked, outside permitted hours, not in an allowed group) pam_unix, pam_access, pam_time, pam_nologin
password pam_chauthtok() Is the new authentication token acceptable, and how is it stored? pam_pwquality, pam_pwhistory, pam_unix
session pam_open_session(), pam_close_session() What has to be set up or torn down around the session? pam_limits, pam_env, pam_systemd, pam_mkhomedir, pam_loginuid

The split is what lets one program use only some of PAM. sshd with a public key, for example, authenticates the key itself and skips the auth group, but still runs account and session. That's why an expired account or a pam_access rule still refuses a key-based SSH login, and why a broken session line can break SSH logins that never touch a password. sudo runs auth (unless the sudoers entry has NOPASSWD) and account, and a session group of its own.

Stacks and control flags

Within a group, the lines form a stack that libpam runs from top to bottom. Each module returns success, failure, or "ignore me". The control field, the second field on the line, says what that result means for the stack as a whole.

The four classic keywords are a shorthand:

Control If the module succeeds If the module fails
required Record success, carry on Record failure, carry on to the end, then fail
requisite Record success, carry on Fail immediately
sufficient Succeed immediately, if nothing above has failed Ignore this result, carry on
optional Counts only if it's the only module in the stack Counts only if it's the only module in the stack

required deliberately keeps going after a failure: the user still gets asked for a password even when the account doesn't exist, so an attacker can't tell a wrong user name from a wrong password by how quickly the prompt fails. requisite is for checks after which going on would be pointless or harmful. sufficient is how alternatives are expressed: "if the local password matches, that's enough; otherwise try the directory server".

Behind the keywords is a more general form in square brackets, which maps each possible return value to an action:

auth  [success=1 default=ignore]  pam_unix.so nullok
auth  requisite                   pam_deny.so
auth  required                    pam_permit.so

Here success=1 means "on success, skip the next one line". A good password jumps over pam_deny and lands on pam_permit; a bad one falls through to pam_deny, which always fails. Debian and Ubuntu generate their common-* files in this style, which is why they look more cryptic than the keyword style used elsewhere. The other actions are ok, done, bad, die, ignore and reset; the PAM configuration reference lists them.

Shared stacks

Most services need the same password checks, so distributions keep them in a few shared files and have each service file pull them in:

Family Shared files Pulled in by
Debian, Ubuntu common-auth, common-account, common-password, common-session @include common-auth
RHEL, AlmaLinux, Rocky Linux system-auth, password-auth, generated by authselect auth include system-auth, or substack
Arch, Gentoo system-auth, with system-login, system-local-login and system-remote-login layered on top auth include system-login

A shared file changes every service that includes it. That's the point: a lockout policy added to system-auth applies to the console, SSH, su and sudo alike. It's also the danger.

Some of these files are generated, and editing them by hand is a mistake that comes back later. On Debian and Ubuntu, pam-auth-update rewrites the common-* files from profiles in /usr/share/pam-configs/ whenever a package that ships a profile is installed or removed. On the RHEL family, authselect owns system-auth and password-auth and replaces them when you select or change a profile; local changes belong in a custom authselect profile or in the modules' own configuration files. Arch and Gentoo ship their shared files as ordinary configuration files (from the pambase package), which the package manager won't overwrite silently.

Configuration outside the stack

Many modules read their settings from their own files under /etc/security/ rather than only from arguments on the PAM line: pam_pwquality from pwquality.conf, pam_faillock from faillock.conf, pam_limits from limits.conf and limits.d/, pam_access from access.conf. Changing a value there, such as the minimum password length, doesn't touch the stack at all, which makes it the safer place to tune a policy. Arguments on the PAM line override the file.

Settings for the shadow tools themselves, such as password ageing defaults for new accounts and the hash method, live in /etc/login.defs. On a PAM system login.defs doesn't control what PAM does at login; it's read by useradd, passwd and the other tools of the shadow suite. That split is a frequent source of confusion: changing PASS_MIN_LEN in login.defs has no effect on a system where pam_pwquality decides password quality.

Why mistakes lock people out

PAM fails closed. If a service file is missing, other is used: on the RHEL family, Arch and Gentoo it denies everything, and on Debian and Ubuntu it applies the shared common-* stacks. If a line names a module that doesn't exist, libpam can't load it and treats the line as a failure, so a typo in a module name on a required line refuses every login through that stack. A misplaced requisite or a sufficient in the wrong position changes the outcome of the whole stack without any error message.

Because the shared stacks are shared, one bad edit reaches all the ways in at once: the console, SSH, su and sudo. sudo without a working auth stack can't authenticate you, so you can't use it to repair the file that broke it. Several common misconfigurations look harmless until the next login attempt:

  • Turning on pam_faillock with deny=3 and no unlock_time limit, so three mistyped passwords (or three attempts by a scanner) lock an account until an administrator clears it. With even_deny_root, that administrator can be root itself.
  • Editing a generated file by hand, then installing a package: pam-auth-update or authselect overwrites the change, or refuses to manage the file at all.
  • Copying a stack from another distribution, where the module paths, the shared file names or the module set differ.
  • Testing a stricter password policy as root: when root sets a user's password, the quality modules only warn by default, so the policy seems not to work, and the next edit goes too far.

Changes take effect on the next PAM transaction, with no reload, and sessions that are already open aren't affected. That gives you a reliable safety net: keep a root shell open in one session while you change PAM in another, and test a new login from a third before you close anything. The how-to guides in this cluster follow that pattern.

Design decisions and trade-offs

The strength of PAM is that authentication policy is the administrator's decision, not each program's. The cost is that the policy is spread across many small files, written in a terse language whose meaning depends on line order and on what each module returns, with failures reported only to the system log. Distributions answer that complexity differently: Debian with generated files built from package-provided profiles, the RHEL family with authselect profiles and feature switches, Arch and Gentoo by shipping plain files and leaving the rest to the administrator.

PAM also only covers what passes through it. A public key accepted by sshd, a Kerberos ticket, or a NOPASSWD sudoers rule don't run the auth stack, so a password policy or lockout configured there doesn't apply to them. Hardening authentication means looking at those paths too.

Further reading