Skip to content

systemd-run: Run Services Without Unit Files

Sometimes you need to run a process under systemd’s control without writing a unit file — maybe you’re in a container without systemd, on someone else’s machine, or just need a quick one-off. That’s where systemd-run comes in.

Why systemd-run

The tool creates a transient unit — a unit that exists only in systemd’s memory, with no file on disk. This is useful when you need:

  • Resource control (cgroup) over an ad-hoc process;
  • The process to survive terminal closure;
  • Isolation in a separate slice.

In practice, it’s a wrapper around systemctl start for units nobody persists.

Basic Syntax

systemd-run [OPTIONS] COMMAND [ARGUMENTS...]

Simple run:

systemd-run /bin/bash -c "while true; do :; done"

The process lands in user.slice, runs in the background, and is manageable via systemctl. Verify:

systemctl list-units --type=service

You’ll see something like run-u1234.service in the output.

Key Flags

FlagPurpose
--scopeCreate a scope unit instead of service (parent process stays in scope)
--unit=NAMEAssign a custom unit name instead of auto-generated
--uid=USERRun as the specified user
--gid=GROUPRun as the specified group
--nice=NNice value (-20 to 19)
--property=KEY=VALUEPass a unit property (CPUAccounting, MemoryMax, etc.)
--chdir=PATHWorking directory
--setenv=VAR=VALUEEnvironment variable
--tmpfs=PATH:OPTIONSMount a tmpfs at the specified path

Flags combine freely. Example with resource limits:

systemd-run \
  --uid=appuser \
  --gid=appgroup \
  --nice=10 \
  --property=MemoryMax=512M \
  --property=CPUAccounting=true \
  --property=CPUWeight=256 \
  -- /opt/myapp/bin/server

Isolation: CPU, RAM, Root via cgroup

The main value is resource control through cgroup v2.

Memory limit:

systemd-run \
  --property=MemoryMax=1G \
  --property=MemoryHigh=800M \
  /bin/memory-heavy-task

If the process exceeds the limit, systemd kills it (OOM via cgroup).

CPU limit:

systemd-run \
  --property=CPUQuota=50% \
  /bin/calculator

50% of one CPU core. On multi-core systems, calculate percentages against total units.

Isolation with alternate root:

systemd-run \
  --property=RootDirectory=/opt/jail \
  --property=RootImage=overlayfs \
  /bin/sh -c "whoami"

RootDirectory requires a correct directory structure inside. Without it, the process fails to start with “Failed to pivot root”.

Mount a tmpfs:

systemd-run \
  --tmpfs=/tmp/isolated:rw,noexec,size=256M \
  -- /bin/sh

Useful for processing temporary files with a size constraint.

Combined example:

systemd-run \
  --uid=nobody \
  --gid=nogroup \
  --nice=5 \
  --chdir=/var/data \
  --property=MemoryMax=256M \
  --property=CPUQuota=25% \
  --property=IOAccounting=true \
  -- /usr/local/bin/worker --queue=default

All process resources are tracked in cgroup, visible via systemd-cgtop and /sys/fs/cgroup.

Comparison with nohup, setsid, chroot

ToolResourcesIsolationSurvives logoutManagement
nohupNoNoYesNo
setsidNoNoYesNo
chrootNoFilesystem onlyDependsNo
systemd-runcgroupCPU, RAM, I/O, userYessystemctl

nohup and setsid work fine for background scripts. But if you need memory limits or CPU priority — cgroup is non-negotiable.

chroot solves only filesystem isolation. You can’t do systemd-run --chroot, but you can combine them:

systemd-run \
  --uid=nobody \
  --property=MemoryMax=100M \
  --chdir=/tmp \
  /usr/sbin/chroot --userspec=nobody /opt/jail /bin/daemon

Here chroot handles filesystem isolation, systemd-run limits resources.

Pitfalls: scope vs service

By default, systemd-run creates a service unit. The difference:

  • service — full unit, registered with systemd, has dependencies, managed normally.
  • scope — tied to the parent process. Specified with --scope. Parent dies — scope dies.

In day-to-day practice, service works for long-lived processes:

systemd-run --unit=my-daemon /usr/local/bin/daemon

Scope is useful for grouping related processes:

systemd-run --scope --uid=user1 /bin/bash -c 'spawn-worker-1 & spawn-worker-2 & wait'

If you run with --scope and close the terminal — processes die. Without --scope — they survive.

Transient Unit Limitations

Transient units don’t persist across reboots. Obvious, but there are other gotchas:

  • No dependencies. Units lack After=, Wants=, Requires=. The process starts immediately. If you need sequencing — chain commands in a script with sleep.
  • Limited property set. Not all unit file fields are available via --property. For example, Restart=always is not supported in transient units (tested on systemd 254).
  • Harder to debug. No file — no systemctl edit. All parameters are only in the command line.

For long-running services with dependencies and restart policies — a unit file remains the right choice. systemd-run is for one-off tasks, prototyping, and resource limiting on the fly.