Turn tmux into a systemd service so your long‑running processes keep running after logout

When you fire up a heavy build, a data‑scraping job, or a local web server inside a tmux session, the usual pattern is to detach and log out. tmux will happily keep the session alive as long as your login shell stays around, but as soon as you hit logout or PAM times you out, the whole tree dies. Wrapping tmux in a systemd unit makes it immune to those surprises and gives you the same tooling you already use for other services—logging, restart policies, limits, and isolation.


Why not just detach?

Detaching is as easy as:

tmux new-session -d -s build 'make -j$(nproc)'

But that trick only works while your shell is alive. If the user logs out, the session closes. nohup or screen -dm can help, but they’re clunky and lack the fine‑grained control that systemd offers. With a unit you can:

  • Set a Restart= policy (e.g. on-failure).
  • Log everything straight to journalctl.
  • Run the session as a specific user or group.
  • Enforce limits (MemoryMax=, CPUQuota=).
  • Use socket activation to start tmux on demand.

Choosing the right unit type

There are two common ways to run tmux under systemd:

  1. System‑wide unit (/etc/systemd/system/tmux-build.service).
    Runs as root or a dedicated system user. Good for services that must start at boot or be shared across users.

  2. User unit (~/.config/systemd/user/tmux-build.service).
    Runs in the user’s session, inherits that user’s environment, and stops automatically when the user logs out. This is usually the simplest way to keep a single user’s long‑running job alive.

For most home‑lab or dev‑ops setups, a user unit does the job. If you need the job to start before anyone logs in, use a system unit and systemd‑run to bind it to a specific user.


Building a user unit

Create ~/.config/systemd/user/tmux-build.service:

[Unit]
Description=tmux session for long‑running build
After=network-online.target

[Service]
Type=exec
User=%i
Environment=TERM=xterm-256color
ExecStart=/usr/bin/tmux new-session -d -s %i 'make -j$(nproc)'
ExecStop=/usr/bin/tmux kill-session -t %i
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
# Optional limits
# MemoryMax=2G
# CPUQuota=50%

[Install]
WantedBy=default.target

Key directives, broken down

DirectiveWhy it matters
Type=execTells systemd that the command will replace the ExecStart process; tmux becomes the main PID.
ExecStart=Starts tmux detached (-d) with a named session (-s %i). The %i placeholder expands to the instance name you’ll use when launching the unit.
ExecStop=Kills the tmux session cleanly when the unit stops.
Restart=Keeps the session alive if it crashes.
StandardOutput/StandardError=journalSends all tmux output straight to the journal, reachable via journalctl -u tmux-build@user.
MemoryMax= / CPUQuota=Optional cgroup limits to keep runaway jobs from eating the host.

Start it up

systemctl --user daemon-reload
systemctl --user enable --now tmux-build@alice.service

Swap alice for whatever user you’re targeting. The @ syntax lets you spin up multiple instances for different users or projects.

Check the status

systemctl --user status tmux-build@alice.service
journalctl -u tmux-build@alice.service -f

You’ll see the tmux session in ps and the build logs in the journal.


Using systemd‑run for ad‑hoc jobs

If you only need a one‑off long‑running task, systemd-run can spin a transient unit that runs tmux:

systemd-run --user --unit=tmp-build --scope \
  /usr/bin/tmux new-session -d -s tmp-build 'python3 long_script.py'

--scope creates a transient scope unit that inherits the user’s environment but doesn’t leave a permanent unit file. Handy for quick experiments.


Security considerations

RiskMitigation
Privilege escalationRun the unit as a dedicated low‑privilege user. Avoid User=root.
Untrusted inputDon’t inject shell‑expanded variables directly into ExecStart=. Use -- to separate options from the command.
Resource abuseSet MemoryMax= and CPUQuota= to cap runaway jobs.
Logging sensitive dataIf the job outputs secrets, redirect output to a protected file or use StandardOutput=null.
Session hijackingRun inside the user’s cgroup hierarchy; systemd‑user isolation limits what the session can see.

Because tmux inherits the user’s environment, be careful with env vars that may contain secrets (e.g. AWS_ACCESS_KEY_ID). If you need credentials, use EnvironmentFile= pointing to a file owned by the user with 0600 permissions.


Trade‑offs compared to plain tmux

Featuretmux alonetmux + systemd
Automatic restartNoYes (configurable)
Centralized loggingManual tee or scriptjournalctl
Resource limitsManual ulimitcgroup limits
Startup on bootNoYes (system unit)
User‑session isolationNoYes (user unit)
ComplexityLowModerate (unit file + daemon‑reload)

If you’re only running a handful of long‑running jobs, the extra setup may feel like overkill. For production or shared environments, the benefits usually outweigh the modest added complexity.


Common pitfalls and troubleshooting

SymptomLikely causeFix
Unit fails to start: “Failed to start tmux build”tmux not in $PATH for the user unitUse absolute path (/usr/bin/tmux) or set Environment=PATH=/usr/bin:/usr/local/bin
Logs missingStandardOutput=journal not setAdd the directive or use StandardOutput=syslog
Session dies on logoutUsing a system unit without User=Switch to a user unit or add RemainAfterExit=yes
Resource limits not appliedcgroup limits require systemd 247+Verify systemd version (systemd --version)
systemctl --user commands failUser systemd not enabledRun loginctl enable-linger <user> or log in once to start the user manager

When to use a system unit instead of a user unit

  • The job must start before any user logs in (e.g. a nightly backup).
  • The job needs to run as a specific system user that isn’t tied to a login shell.
  • You

See also