launchd service setup for local LLM servers
Parent: Mac local LLMs: Serving ops and multi-model · Published reference · snapshot 2026-10-05
↓ Facts as markdownall context files
LaunchDaemons are scanned and loaded at system start; per-user agents are loaded only when that user logs in. A loaded job starts unconditionally only when RunAtLoad or KeepAlive is set.
These notes link each claim to its source. Some citations identify archived research reports that are no longer hosted on this site. A published reference means the content is available; it does not certify independent review or accuracy.Read the editorial policy and follow the sources before relying on a claim.
Facts
- LaunchDaemons are scanned and loaded at system start; per-user agents are loaded only when that user logs in. A loaded job starts unconditionally only when RunAtLoad or KeepAlive is set. [source]
- KeepAlive true restarts the job when it exits, waiting ThrottleInterval seconds between restarts. ExitTimeOut defaults to 20 s (SIGTERM, then SIGKILL). [source]
- Modern syntax: `sudo launchctl bootstrap system /Library/LaunchDaemons/x.plist`, `bootout`, and `launchctl bootstrap gui/<uid> ~/Library/LaunchAgents/x.plist`; `load`/`unload` still work but are legacy. [source]
- A daemon plist without UserName runs as root, so HOME must be set explicitly or models land in root's home. Working daemon examples set UserName/GroupName, or use a dedicated service account (hidden user `_svcuser`, shell /usr/bin/false, HOME /var/svcuser). [source]
- Env vars from a shell profile are not seen by a launchd job; they must be in the plist EnvironmentVariables dict (or in a wrapper script). [source]
- Brew-managed plist for Ollama on this box (`sh.brew.ollama`) carries EnvironmentVariables (OLLAMA_FLASH_ATTENTION, OLLAMA_KV_CACHE_TYPE), KeepAlive, RunAtLoad, one combined log file, and LimitLoadToSessionType listing Aqua, Background, LoginWindow, StandardIO, System. [source]
- Persisting a `launchctl setenv`: a LaunchAgent with ProgramArguments `/bin/launchctl setenv OLLAMA_HOST <value>` and RunAtLoad re-applies it at every login (present on this box as com.mitch.ollama-host-env.plist). It is a per-login workaround, not a boot-time setting. [source]
- A second Ollama instance runs from its own LaunchAgent with a different OLLAMA_HOST port (127.0.0.1:11435), OLLAMA_MAX_LOADED_MODELS and OLLAMA_NUM_PARALLEL in the plist (com.mitch.llmsx-ollama.plist). [source]
- oMLX as a daemon: ProgramArguments `/bin/bash -c "exec /opt/homebrew/bin/omlx serve --model-dir ... --host 127.0.0.1 --port ... --paged-ssd-cache-dir ... --hot-cache-max-size 8GB --api-key $(cat <secret file>)"`, ProcessType Interactive, KeepAlive, RunAtLoad, log paths under the service account. [source]
- oMLX via Homebrew: `brew services start omlx` (auto-restart on crash); service log `$(brew --prefix)/var/log/omlx.log`, app log `~/.omlx/logs/server.log`; `omlx start|stop|restart` wrap brew services. The formula sets OMLX_SUPERVISED=launchd so the dashboard restart button works under brew services. [source]
- oMLX evicts idle models after 5 minutes unless a model is pinned in settings. [source]
- Ollama: OLLAMA_KEEP_ALIVE in the plist keeps models loaded (24h in a WoL setup; -1 for a dedicated machine); the default unload is 5 minutes. [source]
- mlx_lm.server: the example plist launches a wrapper shell script (ProgramArguments) with RunAtLoad, KeepAlive, a PATH that includes pyenv shims and /opt/homebrew/bin, as a LaunchAgent. [source]
- LM Studio headless: llmster installs with `curl -fsSL https://lmstudio.ai/install.sh | bash`, starts with `lms daemon up`; the official startup-task doc is for Linux (systemctl). The desktop app has a "run LLM server on login" setting that minimizes it to the tray. JIT model loading and TTL auto-evict apply to both modes. [source]
- Ollama issue 2955 (Mar 2024) asked for a pre-login daemon; the thread produced a working LaunchDaemon (Apr 2024) with UserName, GroupName, EnvironmentVariables OLLAMA_HOST, ExitTimeOut 30, KeepAlive, loaded with `sudo launchctl load`. [source]
- Ollama issue 3581 (Apr 2024) shows the documented sequence: quit app, `launchctl setenv`, restart app, check ~/.ollama/logs for "Listening on [::]:11434". [source]
- A daemon launched from a script under ~/Desktop (TCC-protected folder) logged "failed with error 1" until the user logged in; moving the binary to /Applications fixed it. Commenter attributes it to the Files and Folders consent system. Keep binaries and models outside Desktop/Documents/Downloads for boot-time daemons. [source]
- `brew upgrade ollama` creates a new Cellar dir with a fresh plist, dropping hand edits; copy the plist to ~/Library/LaunchAgents instead (and stop the brew service first) or keep a backup of the EnvironmentVariables block. [source]
- metal-guard's shell guard covers interactive shells only, never launchd jobs; launchd scripts must call `metal-guard panic-gate` themselves. [source]
- lms daemon: starting the daemon binary and then calling `lms` before it is fully up spawns a second daemon; the two race for llmster-pid.lock and the loser exits 0, which looks like a restart loop under KeepAlive. A wedged auth state was fixed by moving aside ~/.lmstudio/.internal, credentials and settings.json. [source]
- A kernel panic reboot plus a plain KeepAlive plist can relaunch the same workload that caused the panic; launchd has no dependency ordering, so a sysctl daemon and the server daemon can race at boot (inferred) [asserted]. [source]
- Wake-on-LAN wakes a sleeping Mac only over Ethernet; wake takes 4-7 s, then Ollama needs 3-5 s. [source]
- Clamshell/closed-lid laptop dropped to sleep after about an hour despite `pmset sleep 0`; a dummy HDMI plug and a keep-awake app fixed it. [source]
- Pre-login daemon on Ollama: issue 2955 reporter could not get anything to run pre-login and suspected disk encryption; another commenter's LaunchDaemon "should start on boot". Both stand: boot start works, but with FileVault on nothing runs until the volume is unlocked. [source]
- Sleep prevention: one guide uses `sudo pmset -a sleep 0` plus disksleep 0, tcpkeepalive 1, womp 1, powernap 0; another uses `sudo pmset -a disablesleep 1` for lid-closed laptops. The Astropad guide uses the System Settings energy option instead of pmset. [source]
- FileVault on headless Macs: Astropad recommends FileVault off plus auto-login for home use; keep it on in shared spaces; alternatives are `fdesetup authrestart` before planned reboots, an encrypted sparsebundle for secrets, or splitting machines. [source]
- Daemon vs agent: Ollama community and the rdner guide use a LaunchDaemon with a service account; the jgoodwill, Hannecke and riclib guides use brew services or LaunchAgents (login required). [source]
- Whether a LaunchDaemon with UserName can reach the GPU/Metal pre-login on Apple Silicon is not tested in any source read. [source]
- Whether `fdesetup authrestart` survives a kernel-panic reboot (it covers only the next planned reboot per the source). [source]
- No source gives the ThrottleInterval default; launchd.info documents the key but only shows 30 as an example. The known default of 10 s is not confirmed here. [source]
- A LaunchDaemon from /Library/LaunchDaemons is loaded by the root launchd at system start; LaunchAgents load when the user logs in. [source]
- launchd starts a loaded job unconditionally only when RunAtLoad or KeepAlive is set. [source]
- KeepAlive true makes launchd wait ThrottleInterval seconds between restarts. [source]
- ExitTimeOut defaults to 20 seconds before SIGKILL. [source]
- A job's Disabled key can be overridden in /var/db/com.apple.xpc.launchd/disabled.plist; `launchctl print-disabled gui/<uid>` shows it, and bootstrap fails with "Bootstrap failed: 5: Input/output error" if the job is disabled or already loaded. [source]
- Use `launchctl bootstrap system <plist>` and `launchctl bootout system <plist>` for daemons, and the gui/<uid> domain for agents. [source]
- `launchctl kickstart` restarts a service and `launchctl debug` applies one-shot debug properties that clear after one run. [source]
- Setting OLLAMA_HOST in .zshrc does not reach a launchd service; it must go in the plist EnvironmentVariables. [source]
- In issue 2955 a commenter could not get anything to execute pre-login and attributed it to disk encryption (decryption on login). [source]
- A third-party guide runs Ollama from /Library/LaunchDaemons/com.ollama.plist (no UserName), with HOME, PATH, LANG, OLLAMA_HOST in EnvironmentVariables, logs in /var/log/ollama, owner root:wheel, mode 644, tested on macOS 15.2. [source]
- A daemon plist with no UserName runs as root, so ~/.ollama resolves to root's home unless HOME is set (inferred from the guide's explicit HOME key). [source]
- A LaunchDaemon with UserName whose binary sat in ~/Desktop failed at boot with open() error 1 until login; installing to /Applications fixed it. [source]
- A commenter attributed that failure to the Files and Folders (TCC) protection of Desktop, Documents and Downloads. [source]
- brew services keeps Ollama's plist in the Cellar; `brew upgrade ollama` replaces it and drops edits, so back up the EnvironmentVariables block. [source]
- Copying homebrew.mxcl.ollama.plist to ~/Library/LaunchAgents survives `brew upgrade`, after stopping the brew service. [source]
- `launchctl setenv` values do not survive reboot; plist EnvironmentVariables do. [source]
- On this box a LaunchAgent (com.mitch.ollama-host-env.plist) runs `/bin/launchctl setenv OLLAMA_HOST http://127.0.0.1:11434` with RunAtLoad to re-apply the variable each login. [src: local ~/Library/LaunchAgents/com.mitch.ollama-host-env.plist] [source]
- On this box the brew Ollama plist `sh.brew.ollama` sets LimitLoadToSessionType to Aqua, Background, LoginWindow, StandardIO, System, and OLLAMA_FLASH_ATTENTION=1 and OLLAMA_KV_CACHE_TYPE=q8_0. [src: local /opt/homebrew/Cellar/ollama/*/sh.brew.ollama.plist] [source]
- On this box a second Ollama LaunchAgent serves on 127.0.0.1:11435 with OLLAMA_MAX_LOADED_MODELS=1 and OLLAMA_NUM_PARALLEL=2. [src: local ~/Library/LaunchAgents/com.mitch.llmsx-ollama.plist] [source]
- On this box an unrelated server runs as a LaunchDaemon with UserName mitch, GroupName staff, explicit PATH and HOME, RunAtLoad and KeepAlive, logs in the user's home. [src: local /Library/LaunchDaemons/com.openviking.server.plist] [source]
- On this box (macOS 27.2) `pmset -g` shows sleep 0 held by sharingd, Claude, powerd, caffeinate and Amphetamine, womp 1, powernap 1, disksleep 10; `sysctl iogpu.wired_limit_mb` returns 0 and FileVault is On. [src: local pmset/sysctl/fdesetup] [source]
- A self-hosted oMLX stack runs oMLX, Docling and Open Web UI as LaunchDaemons under a hidden service account `_svcuser` (UID 451, shell /usr/bin/false, HOME /var/svcuser, created with dscl), so services start before login. [source]
- That guide's service account needs a password set ("required for launchd") and all installs run through `sudo -u _svcuser`. [source]
- The oMLX daemon plist uses `/bin/bash -c "exec omlx serve ... --api-key $(cat /var/svcuser/.config/secrets/omlx)"`, ProcessType Interactive, KeepAlive, RunAtLoad, and plists are mode 644 in /Library/LaunchDaemons, loaded with `sudo launchctl load`. [source]
- oMLX evicts models after 5 minutes idle unless the model is pinned in Model Settings. [source]
- The oMLX Homebrew formula's brew services job writes both logs to var/log/omlx.log, and since commit 6b2b490 sets OMLX_SUPERVISED=launchd so the admin restart endpoint works under launchd instead of returning 503. [source]
- `omlx start|stop|restart` delegate to brew services on Homebrew installs; defaults are ~/.omlx/models and port 8000, and `omlx serve --model-dir X` once persists settings to ~/.omlx/settings.json. [source]
- OLLAMA_KEEP_ALIVE=-1 keeps a model loaded indefinitely on a dedicated machine; 24h is used in a wake-on-LAN setup because the default 5-minute unload makes each wake pay 30-60 s model reload. [source]
- Wake-on-LAN works only over Ethernet on Macs; measured wake on an M2 Ultra Mac Studio was 4-7 s plus 3-5 s for Ollama. [source]
- An MLX server LaunchAgent plist runs a start script with RunAtLoad, KeepAlive, a PATH with pyenv shims and /opt/homebrew/bin, separate stdout and stderr log paths, loaded with `launchctl load`. [source]
- llmster (LM Studio headless daemon) installs via `curl -fsSL https://lmstudio.ai/install.sh | bash` and starts with `lms daemon up`; the official startup-task doc covers Linux systemctl only. [source]
- LM Studio's desktop-app headless mode is a login setting that keeps the server running from the tray; `lms server start` restores the last server state programmatically; JIT-loaded models auto-unload after a TTL. [source]
- With llmster, calling `lms` before the daemon has finished booting spawns a second daemon, and the two race for llmster-pid.lock; the loser exits 0 ("Exiting due to PID lock loss"), which looks like a restart loop in a supervisor. [source]
- llmster auth state wedged after daemon churn; moving aside ~/.lmstudio/.internal, credentials and settings.json fixed it while keeping models and extensions. [source]
- metal-guard's shell guard covers interactive terminals only and never launchd jobs or scripts; `metal-guard panic-gate` is the command meant for launchd scripts. [source]
- metal-guard's L10 and L12 layers handle recovery after a panic and reboot, and a persistent MLX worker subprocess respawns on crash. [source]
- With FileVault on, a headless Mac needs a password at the pre-boot screen before the network is up, so after a power flicker, update or kernel panic it is unreachable until someone types it locally. [source]
- Astropad's guidance for a home headless Mac mini is FileVault off and Automatic login on, so it recovers after a power outage. [source]
- `sudo fdesetup authrestart` before a planned reboot unlocks the disk once on next boot; the guide adds that on macOS 26 with Ethernet one can SSH in at the pre-boot stage to unlock. [source]
- The login keychain stays encrypted with the user password even with FileVault off. [source]
- Automatic login does not work with FileVault enabled. [source]
- `sudo pmset -a autorestart 1` restarts the Mac after a power failure. [source]
- A headless sleep-prevention set is `pmset -a sleep 0`, `disksleep 0`, `tcpkeepalive 1`, `womp 1`, `powernap 0`; `pmset -g` may show "sleep 0" with a note naming what holds sleep off rather than a SleepDisabled line. [source]
- A MacBook Pro in closed-lid mode still slept after about an hour with `sleep 0`; a dummy HDMI display emulator and a keep-awake app fixed it. [source]
- `sudo pmset -a disablesleep 1` prevents sleep with the lid closed; undo with `disablesleep 0`; closed-lid running needs airflow. [source]
- The Astropad guide sets "Prevent automatic sleeping when the display is off" in Energy Saver rather than pmset. [source]
- A launchd watchdog pattern for 24/7 agents: a script run every 2 minutes and at load, PID-file lock, a kill-switch file, memory-pressure check, heartbeat-staleness zombie detection. [source]
- launchd has no ordering between independent jobs, so a boot-time sysctl daemon and a server daemon can start in either order; a server wrapper should run `sysctl` itself or wait on it. [source]
- A server job under launchd after a panic reboot should call a panic gate before starting the model, otherwise KeepAlive relaunches the same workload (extends the 14-minute respawn claim). [source]
Corrections and disagreements
- CONTRADICTS: ollama-on-macos.md line 28 and 77. A working Ollama LaunchDaemon exists in issue 2955 (UserName, GroupName, OLLAMA_HOST in EnvironmentVariables, ExitTimeOut 30, KeepAlive, `/opt/homebrew/bin/ollama serve`, installed with `sudo cp ollama.plist /Library/LaunchDaemons/`) and is reported to start on boot. [source]
Children
- No children recorded.