PAM Explained: How Linux Actually Decides Who Gets In

PAM Explained: How Linux Actually Decides Who Gets In

When you type a password, the program asking for it does not check it. It calls PAM, which runs a configured list of modules and reports back. That indirection is why you can add two-factor authentication to ssh without recompiling ssh.

The problem it solves

Before PAM, every program that authenticated users implemented the checking itself. Adding a new method meant patching login, su, sudo, sshd, the display manager and everything else.

Pluggable Authentication Modules moves the decision out of the programs. They ask PAM; PAM consults a config file and runs the modules it names.

ls /etc/pam.d/
# sshd  sudo  login  su  passwd  common-auth  common-account ...

# is a program PAM aware
ldd $(which sudo) | grep libpam

One file per service. The name matches what the program passes to PAM, which is usually but not always its own name.

The four types

Each line begins with a type. These are four independent stacks, and a rule in one has no bearing on the others.

TypeQuestion it answers
authAre you who you claim to be
accountIs this account allowed in right now
passwordHow do credentials get changed
sessionWhat is set up before and torn down after

The account and auth split is the one people miss. A correct password satisfies auth. An expired account, a login outside permitted hours, or a group restriction fails account, and the result is a rejection that looks identical to a wrong password unless you read the logs.

session is why your home directory can be mounted on demand, why limits from /etc/security/limits.conf apply, and why systemd knows you have a session at all. Our ulimit guide covers the limits side.

Control flags, which are the actual logic

auth    required     pam_unix.so
auth    sufficient   pam_sss.so
auth    requisite    pam_deny.so
auth    optional     pam_cap.so

required must succeed. If it fails, the failure is recorded and the rest of the stack still runs, then an error is returned.

requisite must succeed, and failure returns immediately.

sufficient succeeding ends the stack successfully, provided no earlier required module failed. Failure is ignored and processing continues.

optional affects the result only if it is the sole module in the stack.

The reason required is the common choice over requisite is subtle and deliberate: continuing through the whole stack means the time taken and the error returned do not reveal which check failed. requisite short-circuits, which leaks that information.

There is also a richer bracket syntax for cases the four keywords cannot express:

auth [success=1 default=ignore] pam_succeed_if.so uid >= 1000 quiet

The numbers are how many subsequent lines to skip. It is dense, it is used heavily in distribution-generated files, and it is not something to write by hand unless you have a specific reason.

Order is the policy

# this works
auth  sufficient  pam_ssh_agent_auth.so
auth  required    pam_unix.so

# this never reaches the first line
auth  required    pam_unix.so
auth  sufficient  pam_ssh_agent_auth.so

PAM stacks run top to bottom. Moving a line changes the policy, and a rule placed after the one that succeeds is never consulted.

This is the single most common source of PAM configurations that do not do what their author intended.

Reading a real file

cat /etc/pam.d/sudo
#%PAM-1.0
auth       sufficient pam_unix.so try_first_pass
auth       required   pam_deny.so
account    required   pam_unix.so
session    required   pam_limits.so

On Debian and Ubuntu you will more often see includes:

@include common-auth
@include common-account
@include common-session-noninteractive

@include pulls in a shared stack, which is how a change to password policy applies to every service at once. The trade-off is that a mistake in common-auth affects everything.

Modules worth knowing

ModuleWhat it does
pam_unix.soTraditional /etc/shadow passwords
pam_pwquality.soPassword strength rules
pam_faillock.soLock an account after repeated failures
pam_limits.soApplies limits.conf
pam_wheel.soRestricts su to a group
pam_time.soTime-of-day restrictions
pam_exec.soRuns an external command
pam_env.soSets environment variables
pam_mkhomedir.soCreates a home directory on first login
pam_google_authenticator.soTOTP two-factor
# what is installed
ls /lib/x86_64-linux-gnu/security/
ls /usr/lib64/security/

# documentation for one
man pam_faillock

Every module has a man page, and they are unusually good. man pam.conf covers the syntax.

Failed logins and lockouts

# current state
faillock --user alice

# clear it
sudo faillock --user alice --reset
auth  required  pam_faillock.so preauth  silent deny=5 unlock_time=900
auth  [default=die]  pam_faillock.so authfail deny=5 unlock_time=900
account required  pam_faillock.so

pam_faillock needs three lines in two different stacks, which is why half-configured lockout policies are common. The preauth line checks, authfail records, and the account line enforces.

Weigh this against denial of service: an attacker who knows your usernames can lock out every account by failing repeatedly. On an internet-facing box, key-only ssh plus fail2ban is usually the better shape, as our ssh hardening guide argues.

Editing without locking yourself out

This is the part that matters more than any of the above.

Open a second terminal with a working root shell and leave it open. An existing session is not re-authenticated, so it survives a broken config and can undo it.

# terminal one, keep this alive
sudo -i

# terminal two, make the change
sudo nano /etc/pam.d/sshd

# terminal three, test without closing anything
ssh localhost

Other protections:

# back up first
sudo cp -a /etc/pam.d /root/pam.d.bak

# watch the failures as they happen
sudo journalctl -f | grep -i pam
sudo tail -f /var/log/auth.log

A PAM file with a syntax error or a missing module fails the whole stack, which means nothing authenticates. There is no partial success. On a remote machine with no console access, that is a rebuild.

On Debian and Ubuntu, prefer the vendor tooling:

sudo pam-auth-update

It regenerates the common-* files from profiles, which means your hand edits to those files get overwritten on the next update. Local changes belong in the service-specific files or the locations your distribution designates.

Adding two-factor to ssh

A concrete example, and one worth doing carefully.

sudo apt install libpam-google-authenticator
google-authenticator          # as the user, not root
# /etc/pam.d/sshd
auth required pam_google_authenticator.so nullok
# /etc/ssh/sshd_config
KbdInteractiveAuthentication yes
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive

nullok lets users who have not enrolled yet still log in, which you remove once everyone has. AuthenticationMethods requiring both means a key alone is not enough.

Test in a new session before closing the old one. Every time.

Debugging

# most modules accept this, and it is noisy
auth required pam_unix.so debug

# watch
sudo journalctl -f -u sshd
sudo journalctl -f _COMM=sudo

The log line naming the module that rejected you is usually the whole answer. Our journalctl guide covers filtering it.

Remove debug afterwards. It logs a great deal, some of it about authentication attempts you would rather not keep.

Frequently Asked Questions

What does PAM actually do?

It separates authentication policy from the programs that need it. Instead of login, sudo and sshd each implementing password checking, they call PAM, which runs a configured stack of modules. Changing the policy means editing a config file rather than patching every program.

What is the difference between required and requisite?

Both must succeed. A required module that fails records the failure and continues running the rest of the stack before returning an error. A requisite module that fails returns immediately. Required is the usual choice because stopping early can reveal which check failed.

What are the four PAM module types?

auth verifies identity, account checks whether the verified identity is currently allowed in, password handles changing credentials, and session sets up and tears down the environment around a login. They are independent stacks, so a rule in one has no effect on the others.

Why did my sudo stop working after I edited a PAM file?

A syntax error or a missing module in a PAM stack causes the whole stack to fail, which means no authentication succeeds. This is why you keep a root shell open in a second terminal before editing anything under /etc/pam.d, since that session stays alive and can undo the change.

Does editing /etc/pam.d survive a package update?

Not reliably. Distributions manage these files, and Debian and Ubuntu regenerate the common files through pam-auth-update. Put local changes in the places your distribution designates for them and use the vendor tooling where it exists, rather than editing the common files directly.

How do I add two-factor authentication to ssh with PAM?

Add a module such as pam_google_authenticator to the auth stack for sshd and set ssh to use keyboard-interactive authentication. The ordering and the ssh settings both matter, and you should test it in a second session before closing the one you configured it from.