How to manage user system services

Create, start, stop, enable and inspect systemd user services on TurboStack, and keep them running after logout.

This page shows how to run a long-lived process as a systemd user service under your system user. You will create the service, start and stop it, keep it running after you log out, and read its logs. For what these services are and which applications use them, see What are user system services?.

systemd is a native alternative to tools like supervisord or PM2. It integrates with the system, gives you full logs through journalctl, and needs no root access when you run it as a user service.

Before you start

  • Secure Shell (SSH) access to the host - see SSH access. Run every command below as your system user; no root access is needed.

  • The command your service should run (for example a worker or consumer command from your application).

Create a service

Create a unit file at ~/.config/systemd/user/<name>.service, where ~ is your system user's home directory. This example runs a worker and restarts it if it stops:

[Unit]
Description=My worker
After=network-online.target
StartLimitIntervalSec=0

[Service]
Type=simple
WorkingDirectory=%h/public_html
ExecStart=%h/public_html/bin/worker
Restart=always
RestartSec=10s

[Install]
WantedBy=default.target
  • %h is your home directory, so the same unit works for any system user.
  • Restart=always with RestartSec=10s restarts the process 10 seconds after it stops.

Start, enable and check it

After creating or changing a unit file, reload systemd, then enable and start the service:

systemctl --user daemon-reload
systemctl --user enable --now my-worker.service   # enable at login + start now
systemctl --user status my-worker.service         # check it is active

Keep it running after you log out

By default, user services stop when your last session ends. Enable lingering once so they keep running after you close SSH and start again after a reboot:

loginctl enable-linger

Read the logs

Each service logs to the systemd journal. Read or follow it with:

journalctl --user -u my-worker.service              # recent logs
journalctl --user -u my-worker.service -f           # follow live
journalctl --user -u my-worker.service --since "5 minutes ago"

To scan a template unit's instances for fatal errors, match the service name with a wildcard and filter the output:

journalctl --user -u "my-worker@*" --no-pager -n 1000 | grep -i FATAL

Stop, restart or remove a service

systemctl --user restart my-worker.service        # after a code change
systemctl --user stop my-worker.service            # stop, keep it enabled
systemctl --user disable --now my-worker.service   # stop and disable at login

Run many instances from one template

When you need several copies of the same service (for example one message-queue consumer per queue), use a template unit. Add an @ to the file name and use %i for the instance value:

# ~/.config/systemd/user/my-worker@.service
ExecStart=%h/public_html/bin/worker %i

Enable one service per instance; the value after @ is passed as %i:

systemctl --user enable --now my-worker@queue1.service
systemctl --user enable --now my-worker@queue2.service

Scale throughput with more instances

A single worker processes one message at a time. Running several in parallel drains a queue faster. This helps when a backlog builds up, for example during a large import or a sales campaign.

There are two ways to add capacity:

  • More instances: run the same worker two, four or more times in parallel, one service per instance.

  • More threads: some workers take a thread or process count flag instead (for example --threads=4). The same total limit below applies.

Add capacity by enabling more instances:

systemctl --user enable --now my-worker@1.service
systemctl --user enable --now my-worker@2.service

Remove capacity by disabling the extra services when the backlog is gone, to free processor time for the site:

systemctl --user disable --now my-worker@2.service

Check how many cores the host has:

nproc

Start with one or two workers. Measure how fast the queue drains and watch the host load on the Health tab, then add one worker at a time. On a 4-core host, keep the total at or below 2 to 3 background workers so the site keeps enough processor time. If the queue still cannot keep up with a safe number of workers, scale up the host instead of adding more.

Application examples

The examples below are template units (note the @ in the file name and %i/%I for the instance value). Adjust WorkingDirectory and the PHP binary version to match your setup.

Laravel queue worker - ~/.config/systemd/user/laravel-queue@.service:

[Unit]
Description=Laravel queue worker (#%i)
After=network-online.target
StartLimitIntervalSec=0

[Service]
Type=simple
WorkingDirectory=%h/public_html
ExecStart=/usr/bin/php8.3 %h/public_html/artisan queue:work --env=prod
Restart=always
RestartSec=10s

[Install]
WantedBy=default.target

CraftCMS queue runner - ~/.config/systemd/user/craftcms-queue@.service:

[Unit]
Description=CraftCMS queue runner (#%i)
After=network-online.target
StartLimitIntervalSec=0

[Service]
Type=simple
WorkingDirectory=%h/public_html
ExecStart=/usr/bin/php8.3 %h/public_html/craft queue/listen
ExecReload=/bin/kill -SIGABRT $MAINPID
Restart=always
RestartSec=10s

[Install]
WantedBy=default.target

For CraftCMS, also disable runQueueAutomatically in the general config so the queue is not run inside web requests.

Magento message-queue consumer - ~/.config/systemd/user/magento-consumer@.service:

[Unit]
Description=Magento consumer (%i)
After=network-online.target
StartLimitIntervalSec=0

[Service]
Type=simple
WorkingDirectory=%h/public_html
ExecStart=/usr/bin/php8.3 %h/public_html/bin/magento queue:consumers:start %I --single-thread --max-messages=10000
Restart=always
RestartSec=10s

[Install]
WantedBy=default.target

Here %I is the consumer name, so one template serves every queue. --single-thread runs one thread per process and --max-messages=10000 restarts the process periodically to keep memory in check. Enable one service per consumer:

systemctl --user enable --now magento-consumer@product_action_attribute.update.service
systemctl --user status  magento-consumer@sales.rule.update.coupon.usage.service

For a worked example, see the Magento message-queue consumers in Magento best practices.