Skip to content

PAM Basics

Root and sudo introduced PAM as the mechanism behind sudo's and su's authentication decisions, and walked through pam_faillock specifically for that one use case. PAM is bigger than sudo, though — it's the same framework behind logging in over SSH, changing a password with passwd, opening a login session on the console, and dozens of other services that need to answer "is this identity allowed to do this, right now." This article is the general-purpose primer: the architecture, the module types, and the modules worth knowing beyond the one already covered.

What You Will Learn

By the end of this article you will be able to:

  • explain why PAM exists as a layer between an application and the actual authentication check;
  • read a PAM stack and identify each line's type, control flag, and module;
  • explain what pam_unix, pam_limits, and pam_env each do;
  • test a PAM configuration change without locking yourself out;
  • know which distribution tool (pam-auth-update or authselect) manages PAM stacks for you, and when to use it instead of hand-editing.

Why PAM Exists

Before PAM, every program that needed to authenticate a user — login, passwd, ftpd — had its own hardcoded logic for checking /etc/shadow. Adding a new authentication method, like an LDAP directory or a hardware token, meant patching and recompiling every one of those programs individually. Pluggable Authentication Modules (PAM) solves this by putting a shared library between the application and the actual check: the application calls into PAM, and PAM — driven entirely by configuration files, no recompilation needed — decides which modules to run to answer the question.

ldd /usr/bin/sudo | grep pam
libpam.so.0 => /lib/x86_64-linux-gnu/libpam.so.0

This is why swapping a server's authentication backend from local /etc/shadow checks to LDAP, or adding a hardware-token requirement, is a configuration change under /etc/pam.d/, not a change to sudo, sshd, or login themselves — those programs only ever talk to PAM.

The Four Module Types

Every PAM module serves one of four purposes, and a single service's PAM stack typically uses several:

Type Question it answers Example use
auth Is this identity who it claims to be? Checking a password, a key, a fingerprint
account Is this account currently allowed to do this, independent of the password being correct? Expired account, time-of-day restriction, account locked
password How is a credential changed? Enforcing complexity rules when passwd sets a new one
session What should happen at the start and end of a session? Setting resource limits, writing a login record, mounting a home directory

A service's file under /etc/pam.d/ stacks one or more lines of each type it needs. account explicitly does not repeat the identity check auth already did — a correct password does not guarantee the account is still allowed to log in right now, and separating those two questions is precisely why PAM has both types.

Reading a Stack

cat /etc/pam.d/login
auth       requisite  pam_securetty.so
auth       required   pam_env.so
auth       required   pam_unix.so nullok
account    required   pam_unix.so
account    required   pam_nologin.so
password   required   pam_unix.so
session    required   pam_limits.so
session    required   pam_unix.so
session    optional   pam_lastlog.so

Each line follows type control module [arguments]. The control flag is the part that decides how one module's pass or fail affects the stack's overall verdict, and pinning down the difference between the four is the single most common PAM interview question:

Control flag On success On failure
required Keep evaluating the stack; the overall result succeeds only if every required module of that type passed Record the failure but keep evaluating the rest of the stack, then fail at the end
requisite Keep evaluating the stack Stop immediately and fail, without running any later modules
sufficient Stop immediately and succeed — provided no earlier required module already failed Ignore this module's result and keep evaluating
optional Keep evaluating; the result matters only if this is the sole module of its type in the stack Keep evaluating; the failure is otherwise ignored

The distinction interviewers probe most is required versus requisite: both must pass, but required deliberately keeps going after a failure so the user can't tell which check rejected them (a wrong password versus an expired account, say), whereas requisite bails out at once when there's no security value in continuing. sufficient is the "any one of these is enough" flag — the first sufficient module to succeed short-circuits the rest, which is how one stack can accept a local password or an LDAP credential without demanding both.

pam_securetty.so as requisite is a concrete example: it refuses root login from a terminal not listed in /etc/securetty, and it's marked requisite — failure stops the whole stack immediately — because there's no reason to continue evaluating a login PAM already knows must be refused.

The bracketed form: [success=1 default=ignore]

Modern stacks (this article's common-auth output below is one) often replace the simple keyword with a bracketed form like [success=1 default=ignore]. It's the same idea generalized: rather than one of four fixed behaviors, it names an action (ok, ignore, die, done, bad, or a number — how many following lines of the same type to skip) for each possible module return value. success=1 means "on success, skip the next 1 line"; default=ignore means "for any other result, act as though this module wasn't there." The four simple keywords are just common presets of this mechanism — sufficient, for instance, is equivalent to [success=done new_authtok_reqd=done default=ignore]. Root and sudo walks through pam_faillock as a worked auth-stack example applying these flags — that explanation applies to any service's stack, not only sudo's.

pam_unix: the Traditional Check

pam_unix.so is the module that actually reads /etc/passwd and /etc/shadow — the default local-account authentication almost every stack includes somewhere:

auth    required   pam_unix.so nullok

The nullok argument, when present, allows an account with an empty password field in /etc/shadow to authenticate with no password at all. It's rarely wanted on a production server, and its presence is worth checking for explicitly:

grep pam_unix /etc/pam.d/common-auth
auth  [success=1 default=ignore]  pam_unix.so nullok_secure

nullok_secure is the safer variant of the two: it still permits an empty password, but only from a terminal listed in /etc/securetty — meaning a local console, not a network login. Removing nullok/nullok_secure entirely (so an empty shadow password field always fails) is the safer default on any server exposed to a network.

pam_limits: Resource Limits Per Session

pam_limits.so, in the session phase, applies the resource limits defined in /etc/security/limits.conf — the PAM-level equivalent of ulimit, applied automatically at login rather than requiring each shell to set it manually:

sudo nano /etc/security/limits.conf
@developers   soft   nofile   4096
@developers   hard   nofile   8192
postgres      soft   nproc    2048
  • soft is the default in effect at login, which the user can raise up to the hard limit themselves within the same session;
  • hard is the ceiling only root can raise;
  • a name prefixed with @ applies to a group rather than a single account.
su - postgres -c 'ulimit -n'
1024

If the expected limit isn't in effect, the most common cause is that the session that's checking it didn't actually go through PAM's session phase with pam_limits.so loaded — a su - login shell does, but a cron job or a systemd service typically does not, and needs its limits set through a different mechanism (a systemd unit's LimitNOFILE=, for instance) instead.

pam_env: Setting Environment Variables at Login

pam_env.so, also in session, sets environment variables from /etc/security/pam_env.conf before the user's shell starts — useful for variables that need to exist even for a non-interactive or non-login session that never reads .bashrc:

TZ  DEFAULT=Europe/Tashkent

Testing a PAM Change Without Locking Yourself Out

PAM stacks control login itself, which makes a mistake here uniquely dangerous: a broken auth line in common-auth or sshd's PAM file can lock out every account on the machine, including root, in the same way a broken sudoers file can — but potentially harder to recover from, since even direct console login can be affected.

Never edit a live PAM file without a fallback session already open

Before changing anything under /etc/pam.d/ or in a file it includes:

  1. keep your current, already-authenticated terminal session open — do not close it until the change is verified;
  2. back up the file first: sudo cp /etc/pam.d/sshd /etc/pam.d/sshd.bak-$(date +%F);
  3. make the change, then test authentication in a second, new session, without closing the first;
  4. if the new session fails, restore the backup immediately from the still-open first session;
  5. on a remote server, keep an out-of-band console (a hosting provider's web console, for instance) available as a last resort, independent of SSH.

Letting the Distribution Manage the Stack

Hand-editing /etc/pam.d/common-auth directly works, but on Debian/Ubuntu it's usually better to go through pam-auth-update, which manages a set of standard "profiles" (Unix authentication, pam_faillock, and others) and keeps the generated files consistent:

sudo pam-auth-update
┌─────────────────┤ Configure Linux PAM ├──────────────────┐
│ Select the authentication profiles that should be enabled│
│ [*] Unix authentication                                  │
│ [ ] Faillock (lock out after failed attempts)            │
└────────────────────────────────────────────────────────────┘

On the RHEL family, the equivalent tool is authselect:

sudo authselect current
Profile ID: sssd
Enabled features: with-faillock

Both tools exist specifically because hand-edited PAM stacks are easy to get subtly wrong and hard to diff meaningfully across a fleet — the same centralization argument covered for sudoers in sudo Security applies here too.

Common Mistakes

  • Editing a PAM file with no fallback session open. As covered above, this is the single most dangerous mistake possible with PAM — a syntax error or an over-restrictive requisite line can lock out every account at once.
  • Leaving nullok/nullok_secure in pam_unix.so on a network-facing service without a specific reason. This allows any account with an empty shadow password field to authenticate without one.
  • Hand-editing common-auth on Debian/Ubuntu instead of using pam-auth-update. Direct edits get silently overwritten the next time pam-auth-update runs (for example, after installing a package that registers its own profile), and the change is lost without an obvious reason.
  • Expecting pam_limits to apply to a cron job or systemd service. Session-phase PAM modules only run for sessions that actually go through PAM's session handling — a login shell or SSH session does, a cron job or systemd unit typically does not.

Exercises

  1. Read /etc/pam.d/sshd on a lab system (or a real one you have access to) and identify one line of each of the four PAM types.
  2. Check whether nullok or nullok_secure appears anywhere in your system's pam_unix.so lines, and explain what removing it would change.
  3. Set a nofile soft limit for a test group in /etc/security/limits.conf, log in as a member of that group, and verify the limit with ulimit -n.
  4. Explain why a broken PAM stack is arguably more dangerous to fix remotely than a broken sudoers file, and what precaution neutralizes that risk.

Summary

PAM is the shared authentication framework behind sudo, su, login, sshd, and more — a stack of auth, account, password, and session modules, each controlled by a flag that decides how its result affects the overall outcome. pam_unix provides the traditional shadow-file check, pam_limits applies resource ceilings per session, and pam_env sets environment variables at login; pam_faillock, covered in depth in Root and sudo, locks out accounts after repeated failures. Because PAM controls login itself, every change needs a fallback session open before it's made. The next article, Password Policies, builds directly on this foundation — pam_pwquality and pam_pwhistory are PAM modules of the password type.

References