How to Manage and Troubleshoot LaunchDaemons and LaunchAgents Using launchctl in macOS Terminal

In the macOS operating system, background automation, scheduled cron replacements, and persistent system service execution are managed entirely by Apple’s unified service framework: launchd. Whether an application installs a helper tool to perform automated software updates, a network utility synchronises corporate credentials, or a developer scripts a nightly backup pipeline, background services are defined using Property List (.plist) configuration files. Interfacing with launchd requires using the native launchctl command-line utility. However, many guides on the internet continue to teach deprecated commands (such as launchctl load and unload) that produce erratic results on modern macOS versions. Mastering the modern subcommands—including bootstrap, bootout, and print—provides complete administrative control over background processes in macOS.

Understanding LaunchDaemons vs LaunchAgents and Domain Targets

Apple’s launchd architecture categorises background jobs into two functional classes based on their execution context and user session boundaries:

  • LaunchDaemons: System-wide background services that execute with root privileges in the system context. They initialize during the boot sequence prior to user login and run continuously in the background without user interface interaction. Stored primarily in /Library/LaunchDaemons/ (for third-party daemons) and /System/Library/LaunchDaemons/ (for protected Apple system daemons).
  • LaunchAgents: User-scoped services that execute within the context of a logged-in user session with standard user privileges. Stored in /Library/LaunchAgents/ (global agents executing for all users) and ~/Library/LaunchAgents/ (executing strictly for the individual user account).

Modern launchctl commands require specifying a target domain, which tells launchd exactly where to host the job:

  • system/: The root system execution domain for LaunchDaemons.
  • gui/<UID>/: The graphical user session domain (e.g. gui/501) for interactive LaunchAgents.
  • user/<UID>/: The non-graphical background user domain for headless background tasks.

Listing Active Services and Querying Status via launchctl list

To inspect currently registered background services within your active terminal context, invoke the list verb:

launchctl list

The resulting table contains three columns:

  1. PID: The operating system process ID if the job is actively executing. If the service is idle or configured to trigger only on scheduled intervals, this column displays a hyphen (-).
  2. Status: The exit code of the last process termination. A status of 0 indicates successful execution. A non-zero positive integer indicates an application exit code (e.g. 1 for generic failure), while negative numbers indicate fatal termination by Unix signals (e.g. -9 for SIGKILL or -15 for SIGTERM).
  3. Label: The unique reverse-DNS identifier of the job (e.g. com.docker.helper or com.apple.metadata.mds).

Inspecting In-Depth Service Telemetry Using launchctl print

The modern print subcommand outputs an exhaustive diagnostic report detailing a service’s runtime environment, path configurations, environment variables, resource limits, and historical crash counts.

Inspecting a System-Level LaunchDaemon

sudo launchctl print system/com.example.systemdaemon

Inspecting a User-Level LaunchAgent

launchctl print gui/$(id -u)/com.example.useragent

Key diagnostic telemetry fields within the print output include:

  • state: Indicates whether the daemon is running, waiting, or disabled.
  • runs: Total count of times the service has executed since system boot.
  • last exit code: Precise error status returned during the most recent termination.
  • endpoints: Registered Mach ports and XPC service listener handles.

Loading and Starting Services Using the Modern bootstrap Verb

The legacy launchctl load command is officially deprecated. Modern macOS workflows utilize the bootstrap subcommand to register new configuration plists into their designated domain.

Bootstrapping a System-Wide LaunchDaemon

  1. Ensure the target plist file resides in /Library/LaunchDaemons/ and possesses strict root ownership:
    sudo chown root:wheel /Library/LaunchDaemons/com.custom.backup.plist
    sudo chmod 644 /Library/LaunchDaemons/com.custom.backup.plist
  2. Register the daemon into the system domain:
    sudo launchctl bootstrap system /Library/LaunchDaemons/com.custom.backup.plist

Bootstrapping a User Session LaunchAgent

  1. Place the plist in your user directory: ~/Library/LaunchAgents/com.custom.sync.plist.
  2. Register the agent into your graphical user domain without using sudo:
    launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.custom.sync.plist

Stopping and Unloading Services Using the bootout Verb

To safely terminate a running daemon and remove its registration from launchd, employ the bootout subcommand.

Unloading by Service Label

sudo launchctl bootout system/com.custom.backup

Unloading by Configuration File Path

launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.custom.sync.plist

The bootout command immediately dispatches a termination signal to the active process, deregisters its Mach communication ports, and purges the job from active memory.

Troubleshooting Common Service Failures and Crash Loops

When a background job repeatedly terminates with non-zero exit codes, investigate the following failure modes:

  • File Ownership Mismatch: System LaunchDaemons in /Library/LaunchDaemons/ MUST be owned by root:wheel with permissions no more permissive than 644. If permissions are set to world-writable (e.g. 777), launchd refuses to bootstrap the file for security reasons.
  • Missing WorkingDirectory: If a script relies on relative file paths, ensure the plist explicitly defines the <key>WorkingDirectory</key> tag.
  • Standard Error Logging: Always configure explicit logging paths within your plist to capture crash telemetry:
    <key>StandardOutPath</key>
    <string>/tmp/myjob.stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/myjob.stderr.log</string>

Utilising modern launchctl commands ensures robust service orchestration, reliable background automation, and clean diagnostic visibility across macOS.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.