Stop Losing User Services on Logout: Practical loginctl Linger on Linux
You enable a rootless Podman container, a systemd --user timer, or a small agent under your account. It works while the SSH session is open. You disconnect — and everything under /run/user/$UID vanishes with it.
That is not random flakiness. It is how systemd-logind and pam_systemd are designed to behave when user lingering is off: the per-user service manager (user@.service) is tied to login sessions, and the runtime directory is removed when the last session ends.
This guide is operational. Commands and defaults come from loginctl(1), logind.conf(5), pam_systemd(8), user@.service(5), and systemd-logind.service(8).
What logind owns (and what it does not)
| Piece | Job |
|---|---|
systemd-logind.service |
Tracks users, sessions, seats; starts user@.service; handles idle/power policy |
pam_systemd |
Registers each login with logind; creates session scope + /run/user/$UID
|
user@UID.service |
Per-user service manager (systemd --user) |
user-runtime-dir@UID.service |
Creates/removes /run/user/UID
|
loginctl enable-linger |
Keep user@.service at boot and after logout |
| system units / root services | Independent of linger; use those when the workload is truly system-wide |
Sessions are login contexts (SSH, TTY, graphical). The user manager is a long-lived service instance shared across a user’s sessions. Linger decides whether that manager (and the runtime dir) outlive the last session.
The mental model in one tree
From user@.service(5) and the loginctl user-status example:
user.slice
└─user-1000.slice
├─user@1000.service # systemd --user (shared)
│ ├─init.scope
│ └─your.timer / your.service
├─session-3.scope # one SSH login
└─session-12.scope # another TTY/GUI login
- Processes started by
systemd --userlive underuser@UID.service. - Interactive login processes live under
session-N.scope. - Both sit under
user-UID.slice.
When the last session ends and linger is disabled, logind stops the user manager (after UserStopDelaySec=) and tears down /run/user/UID. Anything that needed $XDG_RUNTIME_DIR (user D-Bus, rootless containers, many socket paths) is gone.
Prerequisites
# loginctl ships with systemd on essentially every modern distro
command -v loginctl
loginctl --version
systemctl is-active systemd-logind.service
# You need a normal (non-system) user for the labs below
id -u # e.g. 1000
Privileged linger changes typically need root (or polkit authorization). Inspecting your own sessions usually does not.
Lab 1 — Inventory sessions, users, and seats
loginctl list-sessions
loginctl list-users
loginctl list-seats
# Human-readable status for the caller
loginctl session-status
loginctl user-status
# Machine-readable properties
loginctl show-user "$USER" -p Name -p UID -p Linger -p State -p Sessions -p RuntimePath
loginctl show-session self -p Id -p Name -p Class -p Type -p Active -p State -p Display -p Remote
Useful properties (names from loginctl show-*):
| Property | Meaning |
|---|---|
Linger |
yes / no — whether this user is set to linger |
State |
User/session state (active, online, closing, …) |
Sessions |
Session IDs belonging to the user |
RuntimePath |
/run/user/UID when the runtime dir exists |
Class / Type
|
Session class (user, background, …) and type (tty, wayland, …) |
JSON forms (newer systemd builds):
loginctl list-sessions --json=pretty
loginctl list-users -j
Lab 2 — Prove the “logout kills user services” failure mode
Run this from an interactive login as the target user (not via a lingering manager you already enabled).
# Start a trivial user service that just sleeps
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/linger-demo.service <<'EOF'
[Unit]
Description=Linger demo heartbeat
[Service]
Type=simple
ExecStart=/bin/bash -c 'while true; do echo "alive $(date -Is)"; sleep 30; done'
Restart=always
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now linger-demo.service
systemctl --user status linger-demo.service --no-pager
echo "XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR"
ls -ld "$XDG_RUNTIME_DIR"
In a second root shell, watch the user manager:
UID_NUM=$(id -u alice) # replace alice with the lab user
systemctl status "user@${UID_NUM}.service" --no-pager
ls /run/user/
Now fully log the user out of all sessions (close SSH, log out of desktop, etc.). With linger disabled and after UserStopDelaySec= (default 10s per logind.conf(5)):
systemctl status "user@${UID_NUM}.service" --no-pager
# expected: inactive/dead once the user fully logged out
ls /run/user/
# expected: UID directory gone
# The unit may still exist on disk under ~/.config/systemd/user,
# but nothing is running it until the user manager returns.
That is the bug report you keep getting from “my user timer stopped overnight after I closed the laptop lid / SSH’d out.”
Lab 3 — Enable linger the supported way
# As root (or authorized admin)
sudo loginctl enable-linger alice
# Verify
loginctl show-user alice -p Linger
# Linger=yes
# Persistent marker files live here (one empty file per lingering user):
ls -l /var/lib/systemd/linger/
# ... alice
What enable-linger does, per loginctl(1):
If enabled for a specific user, a user manager is spawned for the user at boot and kept around after logouts. This allows users who are not logged in to run long-running services.
Effects you should see:
# Even with zero interactive sessions:
loginctl list-users
systemctl is-active user@1000.service # active
systemctl is-active user-runtime-dir@1000.service
ls -ld /run/user/1000
# XDG_RUNTIME_DIR still present for that UID
# User services survive
sudo -u alice XDG_RUNTIME_DIR=/run/user/1000 \
systemctl --user status linger-demo.service --no-pager
Talking to another user’s bus from root (documented loginctl / systemctl machine syntax pattern):
systemctl --user --machine=alice@.host status linger-demo.service --no-pager
# or
sudo -u alice DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user list-units --type=service --state=running
Disable when you no longer need it:
sudo loginctl disable-linger alice
ls /var/lib/systemd/linger/ # alice marker gone
After disable, the next full logout (and UserStopDelaySec= expiry) returns to the non-linger teardown behavior.
Lab 4 — Boot-time user manager without an interactive login
With linger enabled, reboot (or start on a host where the user never logged in this boot):
# After boot, as root
loginctl show-user alice -p Linger -p State
systemctl status user@1000.service --no-pager
ls -ld /run/user/1000
# Enable lingering user units so they start with the user manager
sudo -u alice XDG_RUNTIME_DIR=/run/user/1000 \
systemctl --user enable --now linger-demo.service
Units linked to default.target under ~/.config/systemd/user/ start with the lingering user manager — no SSH required.
Practical workloads that usually want this:
- Rootless Podman / Quadlet user units
-
systemd --usertimers for backups, sync, cert renewals - Personal agents bound to
$XDG_RUNTIME_DIRsockets - Lingering
pipewire/ session buses on headless seats (when you intentionally run them that way)
Workloads that usually should not linger as a user service:
- Anything that must run before
systemd-user-sessions.serviceallows logins - Multi-tenant daemons that belong under system units with explicit
User= - Secrets you only want available while a human is present
Lab 5 — KillUserProcesses, UserStopDelaySec, and tmux myths
Linger is not the only dial. From logind.conf(5):
# Vendor defaults are commented in the shipped file; local drop-ins win.
man logind.conf
ls /etc/systemd/logind.conf.d/ 2>/dev/null
| Setting | Default (upstream man page) | Effect |
|---|---|---|
KillUserProcesses= |
no |
If yes, session scope processes are killed on logout |
KillOnlyUsers= / KillExcludeUsers=
|
(see man) | Overrides who gets session-kill behavior; root excluded by default when unset |
UserStopDelaySec= |
10s |
How long to keep user@.service after the last session ends (0 = immediate; infinity = never stop after first login) |
RemoveIPC= |
yes |
Remove SysV/POSIX IPC objects on full logout |
RuntimeDirectorySize= |
10% of RAM |
Safety limit for each /run/user/UID tmpfs |
Important nuance from the man page:
-
KillUserProcesses=yeskills processes in the session scope. - Independently, linger controls whether
user@.servicestays up. - Tools like
tmux/screenthat stay inside the session scope die when session-kill is on, unless moved out (for example withsystemd-run --user --scopeas discussed insystemd-run(1)/logind.conf(5)notes).
Drop-in example (only if you understand the impact on shared hosts):
sudo mkdir -p /etc/systemd/logind.conf.d
sudo tee /etc/systemd/logind.conf.d/20-lab-kill.conf <<'EOF'
[Login]
# Example only — many desktops/distros already ship an opinion here.
KillUserProcesses=yes
UserStopDelaySec=10s
EOF
sudo systemctl restart systemd-logind.service
# Caution: restarting logind can disrupt active sessions; do this in a maintenance window.
Prefer linger for user services over globally turning off process cleanup when the real goal is “keep my timer alive.”
Lab 6 — Session hygiene: lock, terminate, kill
# Inspect
loginctl list-sessions --no-legend
loginctl session-status 3 # replace with a real ID
# Request screen lock where the session supports it
loginctl lock-session 3
loginctl unlock-session 3
loginctl lock-sessions # all lock-capable sessions
# Clean teardown of one session (kills its processes, frees resources)
sudo loginctl terminate-session 3
# Signal without full teardown
sudo loginctl kill-session 3 --kill-whom=all --signal=SIGTERM
# Tear down every session for a user (does not by itself clear linger)
sudo loginctl terminate-user alice
terminate-user ends sessions and deallocates runtime resources attached to those sessions. With linger still enabled, expect user@.service to come back (or stay) according to linger policy rather than “user permanently gone.”
Lab 7 — Cap resources for lingering users
Lingering users still consume a user manager and a runtime directory. Bound them with the slice hierarchy from user@.service(5):
# All users under user.slice
sudo mkdir -p /etc/systemd/system/user-.slice.d
sudo tee /etc/systemd/system/user-.slice.d/20-limits.conf <<'EOF'
[Slice]
# Defaults already include TasksMax=33% via vendor drop-in on many systems.
MemoryHigh=2G
MemoryMax=3G
EOF
# One specific UID
sudo mkdir -p /etc/systemd/system/user-1000.slice.d
sudo tee /etc/systemd/system/user-1000.slice.d/20-limits.conf <<'EOF'
[Slice]
CPUQuota=200%
MemoryMax=4G
TasksMax=4096
EOF
sudo systemctl daemon-reload
# Apply to live slices when present:
sudo systemctl restart user-1000.slice
Session-scoped limits can also be applied via PAM data keys documented in pam_systemd(8) (systemd.memory_max, systemd.tasks_max, …). Those apply to session scopes, not to the shared user@.service tree — another reason lingering services need slice-level policy.
A minimal “rootless service that survives logout” recipe
#!/usr/bin/env bash
set -euo pipefail
USER_NAME="${1:-$USER}"
UID_NUM="$(id -u "$USER_NAME")"
sudo loginctl enable-linger "$USER_NAME"
# Wait until runtime dir exists (boot or first activation)
for _ in $(seq 1 50); do
[[ -d "/run/user/${UID_NUM}" ]] && break
sleep 0.2
done
sudo -u "$USER_NAME" XDG_RUNTIME_DIR="/run/user/${UID_NUM}" \
systemctl --user daemon-reload
sudo -u "$USER_NAME" XDG_RUNTIME_DIR="/run/user/${UID_NUM}" \
systemctl --user enable --now linger-demo.service
loginctl show-user "$USER_NAME" -p Linger -p RuntimePath -p State
systemctl status "user@${UID_NUM}.service" --no-pager
Replace linger-demo.service with your Quadlet, timer, or agent unit.
Common failure modes
| Symptom | Likely cause | Fix |
|---|---|---|
| User timer dies after SSH logout | Linger disabled; user@.service stopped |
loginctl enable-linger USER |
Failed to connect to bus as user |
No /run/user/UID / user manager |
Enable linger or stay logged in; export XDG_RUNTIME_DIR
|
| Linger set but services still stop | Units not enabled under --user; wrong WantedBy=
|
systemctl --user enable --now …; link to default.target
|
| Rootless Podman breaks after logout | Runtime dir removed; containers needed linger | Enable linger for that UID; confirm /run/user/UID
|
tmux dies on logout |
KillUserProcesses=yes and tmux still in session scope |
Move work to systemd --user / systemd-run --user --scope, or adjust kill policy carefully |
/run/user/UID grows large |
Runtime tmpfs pressure | Check RuntimeDirectorySize=; stop leaking caches into the runtime dir |
enable-linger fails |
Authorization | Run as root; check polkit / systemd-logind logs |
| User manager flap on rapid logout/login | UserStopDelaySec=0 |
Raise delay (default 10s) if appropriate |
journalctl -u systemd-logind.service -u 'user@*.service' -b --no-pager
How this fits nearby systemd pieces
-
System services (
systemctl enable) — always-on, root or staticUser=, independent of linger. -
run0/systemd-run— transient elevation or one-shot scopes; not a substitute for a lingering user manager. -
Socket activation — on-demand start is orthogonal; user socket units still need a living
systemd --userif they are user units. - homed / encrypted homes — home unlock is separate from linger; a lingering manager with an unavailable home can still surprise you at boot.
-
lingering ≠ immortality —
terminate-user, resource OOM policy, and admindisable-lingerstill apply.
References
-
loginctl(1) —
enable-linger/disable-linger, session and user commands -
logind.conf(5) —
KillUserProcesses=,UserStopDelaySec=, runtime dir limits - systemd-logind.service(8) — login manager responsibilities
-
pam_systemd(8) — session registration,
/run/user/$UID, logout teardown -
user@.service(5) — user manager +
user-runtime-dir@.service+ slice hierarchy - systemd-run(1) — transient user scopes/services
- Debian man page mirror: loginctl(1) on manpages.debian.org
If your “set it and forget it” user service only lives as long as an SSH tty, turn linger on deliberately, enable the user unit, and verify Linger=yes plus a living /run/user/$UID after logout. That single control is the difference between a rootless stack that survives disconnects and one that only works while you are watching it.
Top comments (1)
hold up. green uptime tiles are not a usage receipt.
1 cut: when the cloud invoice fight opens, can a buyer GET a signed meter tip of what ran, or only another vendor seal?
receipts > seals. #marker1028-obs