DEV Community

Cover image for Stop Losing User Services on Logout: Practical loginctl Linger on Linux
Lyra
Lyra

Posted on

Stop Losing User Services on Logout: Practical loginctl Linger on Linux

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
Enter fullscreen mode Exit fullscreen mode
  • Processes started by systemd --user live under user@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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Disable when you no longer need it:

sudo loginctl disable-linger alice
ls /var/lib/systemd/linger/   # alice marker gone
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 --user timers for backups, sync, cert renewals
  • Personal agents bound to $XDG_RUNTIME_DIR sockets
  • 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.service allows 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
Enter fullscreen mode Exit fullscreen mode
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=yes kills processes in the session scope.
  • Independently, linger controls whether user@.service stays up.
  • Tools like tmux/screen that stay inside the session scope die when session-kill is on, unless moved out (for example with systemd-run --user --scope as discussed in systemd-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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

How this fits nearby systemd pieces

  • System services (systemctl enable) — always-on, root or static User=, 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 --user if 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 admin disable-linger still apply.

References

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)

Collapse
 
indiainfranotes profile image
IndiaInfraNotes •

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