Set Login Environment Variables with pam_env
pam_env can set a system-wide environment variable for every PAM session, but only if the right file supplies it. This guide checks which service calls the module, picks the right config file, and tests from a genuinely fresh session. Allow about fifteen minutes.
The route
Jump straight to the step you need, or tick off Done means at the end.
The examples use Linux-PAM 1.5.3, installed here as Debian package libpam-modules:amd64 version 1.5.3-5ubuntu5.7. You need root to change system configuration and a second login or session to test it in. Keep an existing root shell open while editing authentication configuration, and do not use a personal environment file to route around an administrator's policy.
1. Check how the target service calls pam_env
pam_env is a PAM module, not a shell command that changes your current terminal. It provides auth and session module types, and local services usually call it in their session stack. Inspect the service you care about, such as SSH:
grep -n 'pam_env' /etc/pam.d/sshd
ls -l /lib/x86_64-linux-gnu/security/pam_env.so
On a stock system SSH calls the module twice: once with the default files, and again with envfile=/etc/default/locale. That matters, because a setting can be read more than once and different services can use different environment files. The module manual also says it should come last in the stack, since the variables it sets can affect later modules.
Checkpoint
Write down the exact service file and every pam_env.so line it contains. Do not add a second line just because a variable did not show up in your current shell.
2. Choose the right input file
The module reads up to three sources, in order:
/etc/security/pam_env.conffirst. Its rules give a variable name plus optionalDEFAULT=andOVERRIDE=values./etc/environmentnext. SimpleKEY=VALUEpairs, one per line.$HOME/.pam_environmentlast. Only ifuser_readenv=1is set; it is off by default and deprecated since Linux-PAM 1.5.0.
Use pam_env.conf when a default should only apply if the variable is not already set, or when you need PAM substitutions. Use /etc/environment for a plain system-wide pair. Do not put shell commands, substitutions or shell syntax in either file: an export word is tolerated for Bash compatibility but ignored.
For this example, set a default pager in the administrator-controlled rules file. Back it up first, then append one rule:
sudo cp -a /etc/security/pam_env.conf /etc/security/pam_env.conf.bak
sudo sh -c 'printf "%s\n" "PAGER DEFAULT=less" >> /etc/security/pam_env.conf'
The rule supplies less only when PAGER is absent; without an OVERRIDE clause it never replaces an existing value. Check the saved line before opening a new session:
tail -n 3 /etc/security/pam_env.conf
Recovery
If the line is wrong, remove just that new line with a root editor, or restore the backup after checking for changes other administrators may have made since. Do not blindly restore an old copy of a file that is already being maintained.
3. Use substitutions only when the source is reliable
Values in pam_env.conf can use ${NAME} for an already-set environment variable and @{NAME} for a PAM item. @{HOME} and @{SHELL} specifically come from the user's passwd entry. This distinction avoids a common trap: ${HOME} may not exist yet when a PAM application calls the module, while @{HOME} is the account value from the manual's own definition.
For a per-user data directory based on the account database:
XDG_DATA_HOME DEFAULT=@{HOME}/share/
Quote values that contain spaces, and remember an escaped dollar or at-sign is literal. A literal quote cannot be escaped at all according to the configuration manual, so pick a value that avoids needing one. A line starting with # in column one is a comment; do not assume an indented comment behaves the same way.
4. Test from a genuinely fresh session
Existing processes never get an environment update just because a config file changed. Start a fresh session through the service you inspected. For SSH, open a separate terminal and connect as the account you are testing:
ssh USERNAME@HOSTNAME 'printf "%s\n" "$PAGER"'
Swap in real values. For the rule above, expect:
less
Test in the same kind of session that will actually use the variable: a local login, sudo, a cron job and an SSH session can all use different PAM files and therefore different module arguments. Empty output means check the service's pam_env.so lines, the spelling and spacing of the rule, and whether an earlier source already sets PAGER. An unexpected value means check every envfile= occurrence rather than assuming /etc/environment was used.
Common traps
Security boundary
The optional user_readenv=1 setting reads a user file, default path $HOME/.pam_environment. The manual marks it deprecated because a user-supplied PAM variable can affect later modules without the administrator's consent. Leave it disabled unless you have a documented compatibility need, understand the whole stack, and have a rollback plan.
If a service currently enables it, record that and find the controlling line:
rg -n 'pam_env|user_readenv' /etc/pam.d
Removing or changing a PAM line can disrupt logins, so treat it as a maintenance-window change: back up the specific service file, keep a privileged session open, test a new session, and restore the prior line if authentication or session setup breaks.
Done means
- Service confirmed. The target PAM service and its
pam_env.soarguments are recorded. - Right file used. The variable sits in the intended source, with a clear
DEFAULTorOVERRIDEchoice. - Fresh session tested and shows the expected value.
- User config still disabled unless a reviewed exception genuinely needs it.
- Recovery path known before touching the PAM stack again.