How to Run Transient Services and Isolated Background Commands in Ubuntu Using systemd-run

In modern Linux administration, executing ad-hoc background commands, long-running batch migrations, or untrusted scripts frequently presents operational challenges. When an administrator launches a background process using terminal multiplexers (such as tmux or screen) or standard shell disowning (like nohup ... &), the process remains tied to the user’s login session control group (cgroup). If the user logs out, or if the system initiates cgroup session cleanups, these processes risk premature termination. Furthermore, processes launched directly from a shell inherit unlimited access to CPU, memory, and I/O resources, potentially starving critical system services.

To execute isolated, resource-constrained tasks without writing persistent .service unit files to disk, systemd provides systemd-run. Working directly with the systemd manager daemon, systemd-run instantiates transient units—temporary service, scope, or timer units created entirely in memory. This guide demonstrates how to launch detached background services, enforce cgroup v2 resource limits, trigger one-shot timers, and manage execution logs in Ubuntu using systemd-run.

Architecture of Transient Units in systemd

When you start a service using standard systemctl start <name>.service, systemd reads static unit files from /etc/systemd/system/ or /lib/systemd/system/. In contrast, systemd-run constructs transient units on the fly:

  • Transient Service Units (*.service): Managed independently of the user’s login shell. When the invoking shell exits or disconnects, the transient service continues running unimpeded under PID 1.
  • Transient Scopes (*.scope): Groups existing processes into an isolated cgroup without managing process lifecycle initiation.
  • Transient Timers (*.timer): Schedules delayed or calendar-based one-off jobs without requiring manual cron entries.
  • Dynamic cgroup Slices: Allows placing commands into custom slices (such as system.slice, user.slice, or custom background slices) with precise CPU, memory, and I/O limits enforced immediately by the Linux kernel.

Running Detached Background Commands with systemd-run

To run an ad-hoc command as a detached background service that persists across SSH session disconnects, execute systemd-run with root privileges.

For example, to execute a long-running database export or backup script in the background:

sudo systemd-run --unit=db-backup-task /usr/local/bin/backup_database.sh

Systemd immediately confirms unit creation:

Running as unit: db-backup-task.service

To inspect the status of this transient service while it runs, query it using standard systemctl commands:

systemctl status db-backup-task.service

To follow the live standard output and standard error generated by the command, use journalctl:

journalctl -u db-backup-task.service -f

Once the script finishes execution, systemd marks the unit as finished (inactive (dead)) and retains the exit status and execution logs until the next system reload.

Enforcing cgroup v2 Resource Limits on Transient Services

One of the most powerful features of systemd-run is the ability to enforce kernel resource boundaries on ad-hoc commands using the -p (property) flag.

To execute a CPU-intensive compilation or data transformation script while strictly limiting it to a maximum of 500MB of RAM and 30% of a single CPU core, execute:

sudo systemd-run \
  --unit=heavy-compute \
  -p MemoryMax=500M \
  -p CPUQuota=30% \
  -p TasksMax=100 \
  python3 /opt/scripts/process_large_dataset.py

If the Python script attempts to allocate more than 500MB of RAM, the kernel cgroup Out-Of-Memory (OOM) killer terminates the process immediately, protecting the host system from unconstrained memory exhaustion.

To restrict storage I/O bandwidth for heavy disk operations, configure I/O weight properties:

sudo systemd-run \
  --unit=disk-audit \
  -p IOWeight=50 \
  tar -czf /var/backups/archive.tar.gz /var/www

Running Interactive Commands in Isolated Scopes

If you wish to run an interactive shell or debug session inside a resource-constrained sandbox while keeping your current terminal attached, use the --scope flag.

To launch a restricted Bash shell limited to 256MB of RAM:

sudo systemd-run --scope -p MemoryMax=256M bash

You can verify that the memory limit is actively enforced by inspecting the cgroup filesystem from within the newly launched sub-shell:

cat /sys/fs/cgroup/system.slice/run-*.scope/memory.max

Typing exit cleanly terminates the shell and tears down the transient scope.

Scheduling Delayed and One-Shot Jobs with Transient Timers

Instead of editing user crontabs or configuring complex at-job daemons, systemd-run can schedule delayed command executions using transient timer units.

To execute a cache-clearing script exactly 30 minutes from now:

sudo systemd-run --on-active="30m" --unit=delayed-cache-clear /usr/local/bin/clear_cache.sh

To schedule a one-off execution at a specific calendar timestamp (such as midnight):

sudo systemd-run --on-calendar="*-*-* 00:00:00" --unit=midnight-maintenance /usr/local/bin/nightly_cleanup.sh

To view all scheduled transient timers waiting in the systemd queue, run:

systemctl list-timers "delayed-*" "midnight-*"

Running Transient Units Under Non-Privileged User Accounts

Non-root users can also leverage systemd-run to manage their own background tasks within the user systemd manager instance.

To run a user-level transient background service without sudo, pass the --user flag:

systemd-run --user --unit=user-background-job node /home/ubuntu/app/worker.js

Query the status of the user-scoped unit:

systemctl --user status user-background-job.service

To ensure that user-scoped transient services continue running after the user disconnects their SSH session, enable user lingering on Ubuntu:

sudo loginctl enable-linger ubuntu

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.