How to manage user system services
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
%his your home directory, so the same unit works for any system user.Restart=alwayswithRestartSec=10srestarts the process 10 seconds after it stops.
Tip
StartLimitIntervalSec=0 lets systemd keep retrying every RestartSec instead of giving up after
repeated fast failures.
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
Warning
Never run more parallel workers than the host has processor cores, and keep a safe margin below that number. Background workers compete with PHP, the web server and the database for the same cores. Too many workers cause high load and can make the whole server slow or unresponsive.
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.
Note
On TurboStack, PHP binaries are at /usr/bin/php<version> - for example /usr/bin/php8.0 or
/usr/bin/php8.3. The default php command also points at the host's main version. There is no
/usr/local/phpNN path.
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.
Related
- What are user system services?
- Application file layout and permissions
- How to keep a Node.js app running
- SSH access