Creating a per-user service unit lets one Linux account keep a helper, poller, or personal background worker under systemd control without installing a system-wide service. That is useful when the workload belongs to one login, should restart after a crash, and should be managed with normal unit state instead of with ad-hoc shell sessions or login scripts.
In the user instance, systemd loads custom units from the XDG user paths, starting with $XDG_CONFIG_HOME/systemd/user or ~/.config/systemd/user, and the normal pull-in target is default.target rather than multi-user.target. Current upstream systemd.unit and systemd.special documentation places per-user configuration under that home-directory path and defines default.target as the main target of the user service manager.
User services are tied to the user manager rather than the system manager, so systemctl --user commands must be run as the account that owns the unit. A user manager normally lives only while that user has an active session, which means long-running jobs that must survive logout or start before the first login usually also need sudo loginctl enable-linger "$USER".
Run the systemctl --user commands as that same user. Using sudo systemctl --user ... usually points at the wrong user manager unless you rebuild the full session environment for the target account.
$ mkdir -p ~/.local/bin ~/.local/state $ cat > ~/.local/bin/custom-user-worker.sh <<'EOF' #!/bin/sh while :; do printf "custom-user-worker tick %s\n" "$(date --iso-8601=seconds)" >> "$HOME/.local/state/custom-user-worker.log" sleep 30 done EOF
The sample workload writes a timestamped heartbeat into a user-owned state file so the service has a clear proof of work. Replace the loop with the real long-running command or wrapper script you want the user manager to supervise.
$ chmod 0755 ~/.local/bin/custom-user-worker.sh
Use an absolute or specifier-based path in the unit file. Do not rely on shell aliases, login-only environment variables, or a relative working directory.
$ mkdir -p ~/.config/systemd/user
[Unit] Description=Custom user worker demo [Service] Type=exec ExecStart=%h/.local/bin/custom-user-worker.sh Restart=on-failure RestartSec=5 [Install] WantedBy=default.target
Current upstream systemd.special documentation defines default.target as the main target of the user service manager, so it fills the same role for user units that multi-user.target often fills for system units.
The %h specifier expands to the home directory of the user running the service manager instance. Type=exec is a strong default for custom long-running commands because start failures such as a missing executable are reported more accurately than with the implicit Type=simple default.
$ systemd-analyze --user verify ~/.config/systemd/user/custom-user-worker.service
No output is the ideal result. If the command reports some other unit, read the filename in the warning carefully before assuming the new service file is wrong.
$ systemctl --user daemon-reload
This rereads user unit files and rebuilds the user dependency tree without needing a logout or reboot.
$ systemctl --user enable --now custom-user-worker.service Created symlink /home/operator/.config/systemd/user/default.target.wants/custom-user-worker.service → /home/operator/.config/systemd/user/custom-user-worker.service.
Current upstream systemctl documentation states that enable and start are separate actions, so enable --now is the direct way to both install the service into default.target and start it in the current user session. The home path in the symlink line reflects the account that owns the user unit.
$ systemctl --user status --no-pager --full custom-user-worker.service | head -n 7
● custom-user-worker.service - Custom user worker demo
Loaded: loaded (/home/operator/.config/systemd/user/custom-user-worker.service; enabled; preset: enabled)
Active: active (running) since Mon 2026-04-13 14:39:25 UTC; 4ms ago
Main PID: 138 (custom-user-wor)
Tasks: 2 (limit: 14335)
Memory: 596.0K
CPU: 1ms
The success state is a user-path Loaded: line together with Active: active (running). The PID, limits, memory use, and timestamp vary by host.
$ tail -n 3 ~/.local/state/custom-user-worker.log custom-user-worker tick 2026-04-13T14:39:25+00:00
An application-specific file, socket, API response, or queue action is better proof than status alone because it shows the service logic is running, not just that the process exists.
If the unit fails, use journalctl --user -u custom-user-worker.service -n 20 --no-pager to read the recent user-service log.
$ sudo loginctl enable-linger "$USER" $ loginctl show-user "$USER" -p Linger -p State Linger=yes State=lingering
Current upstream loginctl documentation states that lingering spawns a user manager at boot and keeps it around after logouts. Skip this step if the service should run only while that user is actively logged in.
Lingering keeps the user's service manager available in the background, so every unit enabled in that user manager becomes eligible to start even when no interactive session is open. Review timers, sockets, and resource-heavy user services before enabling it on shared or constrained hosts.