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".

Steps to create a user systemd service unit:

  1. Open a terminal as the account that should own the service.

    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.

  2. Create the helper script that the user service will run.
    $ 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.

  3. Make the helper script executable.
    $ 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.

  4. Create the user unit directory and save the service unit file.
    $ 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.

  5. Verify that the unit parses cleanly before reloading the user manager.
    $ 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.

  6. Reload the user manager so it sees the new unit definition.
    $ systemctl --user daemon-reload

    This rereads user unit files and rebuilds the user dependency tree without needing a logout or reboot.

  7. Enable the user service for future user-manager starts and launch it immediately.
    $ 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.

  8. Confirm that the unit is loaded from the user path and is now running.
    $ 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.

  9. Check that the workload is actually doing its job instead of only staying alive.
    $ 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.

  10. Enable lingering when the user service must survive logout or start before the first login after boot.
    $ 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.