If you’ve ever needed to run a quick background job on a Linux server, you’ve probably reached for nohup, a cron entry, or a throwaway shell script. Those work, but they’re messy. Systemd transient units give you a cleaner option. Since systemd 257, you can launch ephemeral services without touching a unit file and, in many cases, without root privileges at all.

This isn’t just a niche feature. The change landed after years of requests from DevOps teams who wanted a predictable, cgroup-tracked way to fire off one-shot jobs. It’s been blowing up on forums because it solves a real daily problem.
What are systemd transient units?
A transient unit is a systemd service, scope, or timer that exists only for the duration of the process. You create it at runtime with systemd-run, it runs under systemd’s supervision, and it vanishes when it finishes. No unit file, no systemctl enable, no cleanup.
Systemd transient units aren’t new, but systemd 257 made two big changes. First, unprivileged users can now create transient units inside their own slice without sudo. Second, resource controls like MemoryMax and CPUQuota are available to those unprivileged units.
That second point matters a lot. Before 257, you could run user-level services, but you couldn’t set hard memory limits on them without elevated access. Now you can.
Running your first transient unit
The basic syntax is straightforward. Here’s how to run a database backup script as a transient service:
systemd-run --unit=my-backup --description="Nightly DB Backup" /usr/local/bin/backup.sh
Because there’s no --user flag, this runs as root in the system slice. You’ll get a unit name like run-12345.service if you skip --unit. Check its status immediately:
systemctl status my-backup.service
journalctl -u my-backup.service -f

For unprivileged execution, add --user. This targets the user’s own systemd instance:
systemd-run --user --unit=my-sync /home/deploy/sync.sh
The job runs inside the user’s cgroup slice. It shows up in systemctl --user list-units. When it ends, it’s gone.
Setting resource limits inline
This is where systemd transient units pull ahead of every other ad-hoc solution. You set cgroup constraints directly on the command line:
systemd-run \
--unit=heavy-process \
--property=MemoryMax=512M \
--property=CPUQuota=25% \
--property=IOWeight=10 \
/opt/scripts/compress-logs.sh
The process can’t exceed 512 MB of memory. The CPU throttle is hard. If compress-logs.sh has a memory leak, the kernel kills it before it affects anything else. That’s the kind of safety net you’d otherwise need a container for.
Since systemd 257, regular users can apply MemoryMax, CPUQuota, and TasksMax without root. Check the exact version on your system:
systemd --version
Anything below 257 won’t support unprivileged resource controls. Ubuntu 26.04 and RHEL 10 ship with 257 or newer.
Transient timers for one-shot scheduling
Systemd transient units support timers too. If you want to run something once in 10 minutes without editing cron:
systemd-run --on-active=10m --unit=delayed-cleanup /usr/bin/find /tmp -mtime +7 -delete
The --on-active flag sets a monotonic delay. You can also use --on-calendar for calendar-based scheduling. Because it’s tracked by systemd, you can cancel it before it fires:
systemctl stop delayed-cleanup.timer
This is much cleaner than using at or a fragile one-line cron entry.
Systemd transient units vs nohup and screen
Old habits die hard. A lot of sysadmins still use nohup command & or screen -d -m command for background jobs. Both work, but neither gives you resource controls, automatic journal logging, or a clean exit status you can check later.
With nohup, the process is untracked. If it runs wild and eats all your RAM, you find out when SSH connections start dropping. With a transient unit, systemd kills it the moment it crosses your MemoryMax threshold and writes the event to the journal.
Screen sessions have their own problems. They survive SSH drops, which is useful, but they also leave orphaned sessions after reboots. Transient units don’t. When the system restarts, they’re gone. That predictability is exactly what a deployment pipeline needs.
The comparison isn’t even close for scheduled tasks. A timer-based transient unit beats an at job because systemd logs it, tracks its exit code, and lets you cancel it by name before it fires. You can also set resource constraints on the timer job itself, something at can’t do at all.
Security considerations
Allowing unprivileged users to create transient units raises a fair question: can a user abuse this to escape resource limits or interfere with other processes? The answer is no, because each user’s units live inside their own slice (user-UID.slice). They can’t affect processes outside that slice.
However, there’s a subtle risk. If CPUQuota and MemoryMax aren’t set by default for user slices, a user could launch unlimited transient units inside their slice and starve other users. You can prevent this by setting slice-level defaults in /etc/systemd/user.conf or by configuring per-user resource limits via loginctl. Check your system’s user slice config before enabling wide access to systemd-run --user on shared servers.
Practical patterns for DevOps teams

There are three patterns you’ll reach for constantly once you start using systemd transient units. The first is sandboxed ad-hoc scripts. Instead of running an unknown script directly, wrap it:
systemd-run \
--unit=untrusted-job \
--property=PrivateTmp=true \
--property=ProtectSystem=strict \
--property=NoNewPrivileges=true \
/opt/vendor/migration.sh
The script gets a private /tmp, can’t write to the system, and can’t gain new privileges. It’s a lightweight sandbox you can apply in seconds. For more complex workloads, compare this approach to container-based isolation in our Docker vs Podman on Linux guide.
The second pattern is running AI model inference jobs with memory caps. If you’re running local models and want to prevent them from consuming all available RAM, systemd transient units give you hard limits without Docker overhead. We covered how to run local AI models in our Install Ollama on Linux guide, and transient units pair well with that setup.
The third pattern is CI/CD step isolation. Each pipeline step runs in its own transient unit with resource quotas. When a step finishes, the unit disappears. No leftover processes, no shared state, no manual cleanup.
Checking and debugging transient units
Because transient units are ephemeral, you need to catch their output before they vanish. Set --collect to keep a failed unit around for inspection:
systemd-run --collect --unit=debug-job /opt/scripts/risky.sh
If the job fails, the unit stays in a failed state. You can then pull full logs:
journalctl -u debug-job.service --no-pager
Without --collect, the unit is removed as soon as it exits, even on failure. That’s fine in production pipelines, but makes debugging harder.
For a full list of supported properties, the official systemd-run man page is well-maintained and covers every option. The systemd 257 release notes on GitHub document exactly which unprivileged controls were added.
Conclusion
Systemd transient units have been available for years, but the systemd 257 changes make them genuinely practical for everyday sysadmin work. You can now run systemd transient units as a regular user, apply real resource limits, and clean up automatically when jobs finish. That covers most of the ad-hoc job scenarios where people still reach for cron, at, or bare shell scripts.
If your servers run Ubuntu 26.04, Fedora 41+, or RHEL 10, you already have systemd 257 available. Start with a simple systemd-run --user command and build from there. Once you use systemd transient units for a week, going back to nohup feels like a downgrade.