Verify Linux Login Sessions with pam_systemd and loginctl
You will finish with a repeatable check that a Linux login is registered by pam_systemd, has a usable runtime directory, and belongs to the expected systemd scope. The examples were checked against systemd 255.4, installed here through libpam-systemd:amd64 package version 255.4-1ubuntu8.17.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell and an existing local or remote session. Most commands are ordinary, read-only checks. This guide does not edit PAM files, restart systemd-logind, terminate sessions or enable lingering.
1. Check the installed module and systemd version
Start by confirming that the package and module are present. These commands do not need elevated privileges:
$ dpkg-query -W -f='${Package} ${Version}\n' libpam-systemd:amd64
libpam-systemd 255.4-1ubuntu8.17
$ dpkg -L libpam-systemd:amd64 | grep '/pam_systemd\.so$'
/lib/x86_64-linux-gnu/security/pam_systemd.so
$ loginctl --version
systemd 255 (255.4-1ubuntu8.17)
The exact library directory depends on the architecture. The useful result is a path ending in pam_systemd.so and a systemd version matching the installed package.
2. Check that PAM calls the module
On this Ubuntu installation, the common session stack contains pam_systemd.so as an optional module:
$ grep -n 'pam_systemd\.so' /etc/pam.d/common-session
31:session optional pam_systemd.so
Read the file rather than copying this line into another PAM service. PAM syntax and include files vary between distributions. The module provides only the session type, so an auth line is not a substitute.
Warning
PAM changes are security-sensitive and can lock users out if the stack is malformed. Do not edit /etc/pam.d/ during a remote-only maintenance session unless you have console access and a tested recovery route. This guide needs no PAM change.
3. Find the current session
Ask loginctl for sessions known to systemd-logind:
$ loginctl list-sessions --no-legend
1 1004 andy - - closing no -
30323 1004 andy - pts/4 active no -
Your IDs, user and state will differ. Copy an ID from the first column instead of guessing. A session can be listed as closing while it is disappearing; use an active or otherwise current session for the next checks.
Checkpoint: store the ID only for this shell, with no system change:
$ SESSION_ID=30323
$ loginctl show-session "$SESSION_ID" -p Id -p User -p Type -p Class -p Remote -p State
Id=30323
User=1004
Type=tty
Class=user
Remote=yes
State=active
pam_systemd uses session metadata such as type and class when it registers the session. A remote SSH session may show Type=tty; that does not mean it is a local virtual terminal.
4. Verify the user runtime directory
On login, the module and systemd-logind arrange a user-private runtime directory at /run/user/$UID. Check the path associated with the user manager:
$ loginctl show-user andy -p RuntimePath -p Slice -p Linger
RuntimePath=/run/user/1004
Slice=user-1004.slice
Linger=no
Use your own login name or numeric UID. RuntimePath is temporary session-related storage for sockets, FIFOs and similar runtime objects. Do not use it as a home directory or assume that files survive the final logout. Concurrent sessions share the same directory, and its contents can disappear between logins.
Confirm ownership and permissions without changing them:
$ stat -c 'path=%n owner=%U mode=%a' /run/user/1004
path=/run/user/1004 owner=andy mode=700
The normal directory is private to the user. A missing path, wrong owner or unexpectedly broad mode deserves investigation before applications place private sockets there. Do not repair it with chmod or chown while a session is active; first determine why logind did not create it correctly.
5. Inspect the session scope
Each registered session is placed in a systemd scope. Show the scope and its parent slice:
$ loginctl show-session "$SESSION_ID" -p Scope -p Leader -p ControlGroup
Scope=session-30323.scope
Leader=30323
ControlGroup=/user.slice/user-1004.slice/session-30323.scope
The values are host-specific. The important relationship is a session scope below the per-user slice, which itself is below user.slice. The first concurrent session also starts the per-user [email protected] manager. A scope is not the same thing as a login shell process, so do not infer that only the shell is controlled by the scope.
For a compact human-readable view, use:
$ loginctl session-status "$SESSION_ID"
30323 - andy (1004)
Since Thu 2026-09-25 ...
State: active
Jobs: 0 queued
...
Output includes host-specific processes and timestamps. The useful checks are that the command succeeds, the selected ID is shown, and the state matches the session you meant to inspect.
6. Check the environment from inside the session
The module initialises XDG_SESSION_ID and XDG_RUNTIME_DIR for applications in the session. Print them from the session itself:
$ printf 'session=%s\nruntime=%s\n' "$XDG_SESSION_ID" "$XDG_RUNTIME_DIR"
session=30323
runtime=/run/user/1004
Run this in the terminal or SSH shell whose row you selected. A different terminal can legitimately have a different session ID while still sharing the same runtime directory. If a process was started outside a PAM session, these variables may be absent; adding them by hand does not register that process with logind.
7. Diagnose the common failure modes
An empty result from loginctl list-sessions means logind has no registered sessions to show, not that PAM has registered an invisible one. Check that systemd-logind.service is running and inspect recent messages:
$ systemctl is-active systemd-logind.service
active
$ journalctl -u systemd-logind.service -b --no-pager -n 40
Reading the service state and journal may require elevated privileges on a hardened system. If necessary, use sudo for those two commands only. Do not restart logind as a first diagnostic step: it can disrupt session tracking and is unnecessary for a read-only check.
If this system was not booted with systemd as its init system, pam_systemd returns PAM_SUCCESS without doing the registration work described here. Also distinguish a missing environment variable from a missing logind record: check both the process environment and loginctl output before changing configuration.
Done means
libpam-systemdandpam_systemd.soare installed, with the expected systemd 255.4 package version.- The active PAM session stack contains the module as a
sessionentry. loginctlidentifies the intended session and shows its state and metadata.- The user has a private
/run/user/$UIDruntime directory with the expected owner and mode. - The session has a systemd scope below the user's slice.
- You have not edited PAM, restarted logind, enabled lingering or terminated a session.