Skip to main content
25.04.2026

Systemd User Units Explained: Services That Run Without Root

head-image

Systemd user units are one of the most useful Linux features that many teams still overlook. They let you run services, timers, and sockets inside a specific user session instead of under the system-wide init context.

That sounds small, but it solves a real operational problem. Not every long-running process should be a root-managed system service. CI runners, developer tunnels, local agents, per-user exporters, desktop sync jobs, and personal automation tasks often work better as user units.

In this guide, we will cover what systemd user units are, how they differ from system units, how to make them survive logout, and the exact commands to deploy them cleanly.

Quick Answer

Use a systemd user unit when a service should run with a specific user identity and should not require root-owned configuration.

# Create user service directory
mkdir -p ~/.config/systemd/user

# Add your unit
cat > ~/.config/systemd/user/myapp.service <<'EOF'
[Unit]
Description=My per-user background worker
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=%h/bin/myapp --serve
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target
EOF

# Reload and enable it
systemctl --user daemon-reload
systemctl --user enable --now myapp.service

# Check logs
journalctl --user -u myapp.service -f

If the service stops when the user logs out, enable lingering:

sudo loginctl enable-linger $USER

What Are systemd User Units?

Systemd supports two service managers:

  • System manager: started as PID 1 and responsible for system-wide units
  • User manager: started per user and responsible for units in that user's context

User units live in locations such as:

  • ~/.config/systemd/user/
  • /etc/systemd/user/
  • /usr/lib/systemd/user/ or /lib/systemd/user/

The most common place for custom per-user services is:

~/.config/systemd/user/

Once a unit exists there, you manage it with systemctl --user instead of plain systemctl.

User Units vs System Units

This is the key distinction:

Type Scope Typical command Runs as
System unit Whole machine systemctl status nginx root or configured service user
User unit Single user systemctl --user status myapp the logged-in user

Use a system unit when:

  • the service must start during boot for the whole machine
  • it needs privileged ports or privileged operations
  • it is shared infrastructure such as databases, ingress, or node exporters

Use a user unit when:

  • the service belongs to one human or app account
  • you want user-level isolation without root-owned service files
  • the process depends on that user's home directory, SSH config, tokens, or desktop/session environment
  • you want safer defaults for developer tooling, agents, sync jobs, or personal automation

For many SRE teams, user units are a cleaner alternative to ad hoc nohup, screen, or background shell scripts.

Why User Units Matter in Real Operations

User units are not just a desktop Linux feature. They are useful on servers too.

1. Reduced privilege surface

A service that only needs one user's files should not usually run as root. User units make the least-privilege path the easy path.

2. Cleaner ownership boundaries

If several engineers or automation accounts share a host, each user can manage their own services independently without editing /etc/systemd/system.

3. Better persistence than shell hacks

Instead of nohup ./script & or a tmux pane nobody remembers, you get restart policies, dependency handling, logging, and predictable service state.

4. Better fit for AI agents and automation workers

Per-user agent processes, local tunnels, indexing daemons, or repo-specific workers often need home directory access but not root. User units fit that model well.

How to Create a systemd User Service

Let us say you want to run a small API worker from your home directory.

Create the file:

mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/my-worker.service

Example unit:

[Unit]
Description=My Worker API
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=%h/apps/my-worker
ExecStart=%h/apps/my-worker/bin/start
Restart=always
RestartSec=3
Environment=APP_ENV=production
Environment=PORT=8080

[Install]
WantedBy=default.target

Then apply it:

systemctl --user daemon-reload
systemctl --user enable --now my-worker.service
systemctl --user status my-worker.service

How to View Logs for User Units

Use the user journal:

journalctl --user -u my-worker.service

For live logs:

journalctl --user -u my-worker.service -f

This is much easier to operate than chasing redirected stdout files across home directories.

The Logout Problem and Linger

This is the part that trips people up.

By default, the user service manager is tied to login sessions. On many systems, user services stop when the user logs out unless lingering is enabled.

Check your current session state:

loginctl user-status $USER

Enable lingering so the user manager stays active after logout:

sudo loginctl enable-linger $USER

Verify:

loginctl show-user $USER | grep Linger

Expected output:

Linger=yes

This is essential for server-side automation, remote tunnels, self-hosted agents, and timers that must continue running after SSH disconnects.

systemd User Timers

User units are not limited to services. You can also run timers without touching root-owned cron configuration.

Example timer pair:

~/.config/systemd/user/cache-refresh.service

[Unit]
Description=Refresh application cache

[Service]
Type=oneshot
ExecStart=%h/bin/refresh-cache

~/.config/systemd/user/cache-refresh.timer

[Unit]
Description=Run cache refresh every 15 minutes

[Timer]
OnBootSec=2m
OnUnitActiveSec=15m
Persistent=true

[Install]
WantedBy=timers.target

Enable it:

systemctl --user daemon-reload
systemctl --user enable --now cache-refresh.timer
systemctl --user list-timers

For personal automation or account-scoped maintenance, this is often better than cron because you get dependency awareness and journal logs.

Common Pitfalls

Environment variables are missing

User units do not always inherit the same shell environment you see in an interactive terminal. Avoid relying on .bashrc side effects.

Better options:

  • define Environment= entries directly in the unit
  • use EnvironmentFile= for managed config
  • use absolute paths in ExecStart=

systemctl --user fails over SSH

This usually means the user manager is not active, DBus session variables are missing, or lingering is not enabled.

Start by checking:

systemctl --user status
loginctl user-status $USER

On headless hosts, enabling linger usually solves the persistence issue.

Relative paths break

Always prefer explicit paths and set WorkingDirectory= when the process expects a specific cwd.

Unit starts manually but not on reboot

If you want startup after boot without an interactive login, lingering must be enabled and the unit must be enabled with:

systemctl --user enable my-worker.service

Security Notes for SRE Teams

User units are convenient, but they should still be treated as production-managed services when they matter to uptime.

A few practical rules:

  • keep unit files in version control when possible
  • use dedicated service accounts for automation instead of personal logins
  • avoid placing secrets directly in unit files
  • prefer EnvironmentFile= with tight permissions or a secret manager
  • monitor user-scoped services just like system services if they are operationally important

If a workload is business-critical, a root-managed system service under a dedicated non-root account may still be the better long-term pattern. User units shine when ownership is per-user and privilege boundaries matter.

When You Should Use systemd User Units

User units are a strong fit for:

  • self-hosted developer tools
  • local AI agents and coding assistants
  • Git sync workers
  • SSH reverse tunnels
  • personal exporters and dashboards
  • scheduled maintenance scripts for a single app account
  • desktop apps that need reliable restart behavior

If your current pattern is a README that says "open a tmux session and run this command," that is usually a sign the process should become a systemd unit.

Conclusion

Systemd user units give you a practical middle ground between one-off shell processes and full system-wide services. They improve reliability, reduce unnecessary privilege, and make per-user automation much easier to operate.

For SRE teams, they are especially useful for agent processes, support tooling, tunnels, and maintenance jobs that belong to one account rather than the whole machine.

If you are building operational automation around Linux services, Akmatori helps you track health, reduce incident noise, and keep critical workflows observable as they scale.

Automate incident response and prevent on-call burnout with AI-driven agents!