Run One-Off Commands with systemd-run
systemd-run launches an ordinary command as a transient service, scope or timer without you writing a unit file first. You will run a short command and get a reliable exit status back, then move on to scopes and scheduled timers. Allow about 15 minutes.
The route
Jump straight to the step you need, or tick off Done means at the end.
- You need: a shell and a working systemd user manager for the unprivileged examples. System-wide units normally require administrative authorisation.
- What this touches: temporary transient units, and in the timer example, a scheduled action. Use a disposable command while learning: do not point these examples at a production service, a path with valuable data, or anything that changes system state until you have reviewed its unit properties.
1. Check the installed command
Confirm which systemd-run will execute and which manager version it belongs to.
$ command -v systemd-run
/usr/bin/systemd-run
$ systemd-run --version
systemd 255
The exact feature string and distribution suffix can differ. This guide uses the installed 255 behaviour, including the default environment expansion rules covered in step 5.
Checkpoint
Continue only if the command is present and its version is close enough to the documentation you are using.
2. Run a short command and get its status back
- Default unit type is a transient service. The manager starts it in a clean, detached environment, normally asynchronously.
- Add
--waitwhen the calling shell must stay until the command exits. - Add
--service-type=execwhen a successful start must mean the executable actually launched, not just that the manager forked a process.
$ systemd-run --user --wait --collect --service-type=exec \
/bin/sh -c 'printf "%s\n" "transient service finished"'
Running as unit: run-EXAMPLE.service
transient service finished
Finished with result: success
Main processes terminated: code=exited/status=0
The generated unit name is different every run, so the final status line is the check that matters. With --wait, systemd-run propagates the command's result. --collect tells the manager to unload the transient unit after completion, including after failure, so it does not sit around loaded for later inspection.
Tip
On a machine without a usable user manager, only drop --user if you deliberately want the system manager and have the privilege for it. The implied default is --system; do not fall into it by accident.
3. Tell scopes and services apart
A scope keeps systemd-run as the parent process: the command inherits your caller's execution environment, the manager still groups and manages the processes, but the call is synchronous by default and returns when the command finishes. Scope units have no single main process, so an unclean child exit does not by itself create a service-style failure state.
$ systemd-run --user --scope /bin/sh -c 'printf "scope-ok\n"'
Running scope as unit: run-EXAMPLE.scope
scope-ok
- Use a scope for a command already attached to your session, especially when you want it grouped for resource control without moving it into a detached service.
- Use a service when the manager should own the process start and its lifecycle.
- Use
--ptyto keep a terminal-based program attached to the calling terminal. - Use
--pipefor a pipeline; it preserves file descriptors but does not make the process a terminal controller, so the two are not interchangeable. --shellis the convenience form: an interactive shell with a pseudo-terminal, the current directory, synchronous waiting, collection, and an exec service type all bundled together.
4. Set properties without writing a unit file
Transient units accept the same property-assignment style as systemctl set-property. Give a short-lived scope a maximum runtime, for example.
$ systemd-run --user --scope \
--property=RuntimeMaxSec=30s \
/bin/sh -c 'printf "work started\n"; sleep 2; printf "work finished\n"'
RuntimeMaxSec= is a scope setting; the default is no runtime limit. If the process runs past it, the manager terminates the scope and puts it in a failure state. For a service, --property=Type=exec is often the first reliability improvement, since it makes the service fail when its executable cannot start.
Other practical options: --working-directory=/path/to/work, repeated --setenv=NAME=value, --uid=NAME and --gid=NAME.
Warning
Changing identity can remove access rather than add it. Test with a harmless command first, and do not assume a user service has the same environment as your interactive shell.
5. Avoid the environment-expansion trap
Two parsers touch your command text: your shell and systemd. In systemd 255, environment expansion is on by default for services but off by default for scopes, for backwards compatibility. A literal dollar sign meant for your shell can get expanded by systemd instead, or need protecting from both layers at once.
When the command contains dollar characters that systemd must not touch, disable its expansion explicitly.
$ systemd-run --user --wait --collect \
--expand-environment=no --service-type=exec \
/bin/sh -c 'printf "shell=%s pid=%s\n" "$SHELL" "$$"'
Single quotes protect the command text from the invoking shell. --expand-environment=no then passes the text through untouched to the shell started by the transient service. Without that option, systemd's own ${VARIABLE} expansion can change the argument before the shell ever sees it. You can instead escape a dollar sign for systemd as $$, but the explicit option is easier to review when a whole script is being passed as one argument.
6. Schedule a command with a transient timer
Timer options create a transient timer alongside the service it triggers. --on-active=5m and similar monotonic options count from timer activation; --on-calendar= uses wall-clock calendar expressions. The command does not run immediately: systemd-run starts the timer unit and reports both names.
$ systemd-run --user --on-active=5m \
--timer-property=AccuracySec=1s \
/usr/bin/logger --tag transient-demo 'scheduled check'
Running as unit: run-EXAMPLE.timer
Will run service as unit: run-EXAMPLE.service
AccuracySec= defaults to one minute. Setting it to one second narrows the scheduling window for this demonstration, but it does not override every clock or scheduler behaviour. For routine jobs, leave the default unless precision has a real operational benefit.
Record the actual timer name from your output, then inspect it.
$ systemctl --user status run-EXAMPLE.timer
$ journalctl --user -u run-EXAMPLE.service
Replace run-EXAMPLE with the name systemd-run printed. Stop an unwanted pending timer before it fires.
$ systemctl --user stop run-EXAMPLE.timer
For a recurring calendar schedule, use an expression such as --on-calendar='Mon..Fri 09:00'. If a missed calendar run must be caught up after shutdown, that is a persistent timer concern; the transient command-line shortcut is no substitute for a reviewed unit definition with Persistent=yes.
7. Diagnose failures without guessing
- Reports success but did not really start? Check whether you used the default
simpleservice type, then re-run with--service-type=exec --wait. - Fails later? Inspect the exact generated unit and its journal rather than inventing a unit name.
$ systemctl --user list-units --type=service --all 'run-*'
$ journalctl --user -u run-EXAMPLE.service --no-pager
A permission error usually means the selected manager, user, identity or working directory is wrong. A timer that appears late may be within AccuracySec= or affected by system suspend. A command that cannot find an executable may be missing the environment you expected: use an absolute path and set only the variables it needs.
Warning
Stopping a unit sends termination signals to its managed processes. Before running systemctl stop on a real job, confirm the unit name with systemctl status and make sure terminating it will not interrupt data writes or a deployment.
Done means
- Version checked: you confirmed the installed systemd version and whether you are using the user or system manager.
- Service ran cleanly: a short service ran with
--wait, reported its result, and used--service-type=execwhere start failure matters. - Scope vs service is clear: you can explain why a scope inherits the caller's context while a service is detached and manager-owned.
- Properties tested safely: properties, identity changes and runtime limits were tried on harmless work before real jobs.
- Timer cleaned up: the scheduled timer was inspected by its printed unit name and stopped if no longer wanted.