Back to Mise

systemd

docs/bootstrap/systemd.md

2026.7.75.8 KB
Original Source

systemd

mise can declare Linux systemd user services and timers in [bootstrap.linux.systemd.units] and apply them with mise bootstrap linux systemd-units apply or as part of mise bootstrap:

toml
[bootstrap.linux.systemd.units.my-sync]
description = "sync files"
exec_start = "~/.local/bin/my-sync --watch"
after = ["network-online.target"]
wants = ["network-online.target"]
environment = { PATH = "/usr/local/bin:/usr/bin:/bin" }
working_directory = "~"
restart = "on-failure"
restart_sec = "5s"
standard_output = "append:%h/.local/state/my-sync.log"
standard_error = "journal"

Oneshot and hardened services can use additional service directives:

toml
[bootstrap.linux.systemd.units.daemon-lifecycle]
type = "oneshot"
remain_after_exit = true
exec_start = "~/.local/bin/daemon start"
exec_stop = "~/.local/bin/daemon stop"
timeout_start_sec = "120"
timeout_stop_sec = "30"
no_new_privileges = true
private_tmp = true

An entry containing a timer key is rendered as a .timer instead of a .service. For example:

toml
[bootstrap.linux.systemd.units.healthcheck-timer]
description = "periodically check daemon health"
on_boot_sec = "2min"
on_unit_inactive_sec = "5min"
randomized_delay_sec = "30s"
persistent = true
unit = "dev.mise.healthcheck.service"

A timer must set at least one of on_boot_sec, on_unit_active_sec, on_unit_inactive_sec, or on_calendar. Service-only keys such as exec_start, environment, and restart are rejected on timer entries; use a separate service entry for the unit triggered by the timer.

Each unit is written to ~/.config/systemd/user/dev.mise.<name>.service or ~/.config/systemd/user/dev.mise.<name>.timer and managed with systemctl --user. Unit names may contain letters, numbers, ., _, -, and @. mise owns only the unit files it creates with the dev.mise. prefix.

Supported keys

TOML keysystemd key
descriptionDescription
afterAfter
wantsWants
exec_startExecStart
typeType
remain_after_exitRemainAfterExit
exec_stopExecStop
timeout_start_secTimeoutStartSec
timeout_stop_secTimeoutStopSec
no_new_privilegesNoNewPrivileges
private_tmpPrivateTmp
environmentEnvironment
working_directoryWorkingDirectory
restartRestart
restart_secRestartSec
standard_outputStandardOutput
standard_errorStandardError
on_boot_secOnBootSec
on_unit_active_secOnUnitActiveSec
on_unit_inactive_secOnUnitInactiveSec
on_calendarOnCalendar
randomized_delay_secRandomizedDelaySec
accuracy_secAccuracySec
persistentPersistent
unitUnit
wanted_byWantedBy
startrun systemctl --user restart

exec_start and working_directory expand bare ~ and ~/ to the current user's home directory before writing the service file. wanted_by defaults to ["default.target"] for services and ["timers.target"] for timers; set wanted_by = [] to write the unit and disable any previous enablement. start defaults to true; set start = false to write and enable without keeping the unit running.

Semantics

  • Declarative and additive — unit names merge across the config hierarchy (global → project). A more local config replaces the full declaration for the same unit name. When an entry changes between a service and timer, mise stops, disables, and removes the stale sibling unit.
  • Linux-only — on other platforms the section is inert: mise bootstrap linux systemd-units status lists entries as skipped and mise bootstrap linux systemd-units apply ignores them.
  • User units only — mise writes to ~/.config/systemd/user and uses systemctl --user. System services in /etc/systemd/system are not supported.
  • Target user only — run mise as the user who owns the services, with a reachable systemd user manager. sudo mise is skipped because systemctl --user would target the wrong user manager.
  • Manual application only — mise never writes or starts systemd units implicitly; only mise bootstrap linux systemd-units apply and mise bootstrap do.

Commands

sh
mise bootstrap linux systemd-units status            # shows systemd user service state
mise bootstrap linux systemd-units status --json     # machine-readable
mise bootstrap linux systemd-units status --missing  # exit 1 if any unit is missing, changed, or inactive

mise bootstrap linux systemd-units apply           # write and start missing/changed units
mise bootstrap linux systemd-units apply --dry-run # print the commands without running them
mise bootstrap linux systemd-units apply --yes     # skip the confirmation prompt

status reports each unit as active, inactive, differs, or missing. apply rewrites changed unit files, runs systemctl --user daemon-reload, enables units with wanted_by, disables units with wanted_by = [], and restarts them when start = true or stops them when start = false.