935 words
5 min read

Run a Script When macOS Boots: launchd First, cron as the Fallback

By · Docker Captain · CNCF Ambassador · IBM Champion
Cover image for the post 'Auto-run Scripts when macOS Boots'

The 2021 version of this guide put the script into root’s crontab with @reboot. That still works on macOS 26, and it is still the wrong tool: cron gives you no log, a bare PATH, and no say in whether the job runs before or after login. launchd is what starts every service on macOS, and it answers all three. This version shows launchd first and keeps cron at the end, screenshots and all, for the cases where it is enough.

Two decisions before you type anything. A LaunchDaemon lives in /Library/LaunchDaemons, runs as root, and starts at boot, with nobody logged in. A LaunchAgent lives in ~/Library/LaunchAgents, runs as you, and starts when you log in. “When macOS boots” usually means the first one; a script that needs your keychain, your mounted volumes or a window means the second.

Step 1: Write the script#

We go into the system under an account with administrator rights and start the terminal emulator.

NOTE

The screenshots are from the original run in iTerm2. Any terminal does the same.

Create the script that will hold the commands you want to run at boot:

Terminal window
vim /Users/valdemar/Documents/Scripts/run-service.sh
NOTE

In this tutorial, a script called “run-service.sh” is created in the user’s home directory. You can create a script in any convenient place for permanent storage. For a LaunchDaemon, a folder root can read is enough; it does not have to be your home.

Auto-run Scripts when macOS Boots - Step 1

Auto-run Scripts when macOS Boots - Step 2

Hit the “i” button to go into edit mode, then add the commands you need to execute when macOS boots. Start the file with a shebang line, because launchd runs the file directly:

#!/bin/sh
# commands to run at boot go below

Auto-run Scripts when macOS Boots - Step 3

Now press the “Esc” button to exit edit mode, then type "" and press the “Enter” button to save your changes and exit the editor.

Auto-run Scripts when macOS Boots - Step 4

Let’s enable the execution of the file “run-service.sh” using the command:

Terminal window
chmod +x /Users/valdemar/Documents/Scripts/run-service.sh

Auto-run Scripts when macOS Boots - Step 5

Step 2: Describe the job to launchd#

launchd reads a property list. Two keys are required, Label and ProgramArguments; the rest below is what makes the job useful. Create the file:

Terminal window
sudo vim /Library/LaunchDaemons/com.heyvaldemar.run-service.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.heyvaldemar.run-service</string>
<key>ProgramArguments</key>
<array>
<string>/Users/valdemar/Documents/Scripts/run-service.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/var/log/run-service.log</string>
<key>StandardErrorPath</key>
<string>/var/log/run-service.log</string>
</dict>
</plist>

Label is the job’s name, unique on the machine; reverse-DNS is the convention. ProgramArguments is the command and its arguments, one string each. RunAtLoad runs the job once when launchd loads it, which at boot means right after launchd itself; the man page warns against using it for jobs that could start on demand instead, and a boot script is the case where “on demand” is boot. StandardOutPath and StandardErrorPath send the script’s output to a file; without them, output goes nowhere and a failing script fails in silence. Add WorkingDirectory if the script expects to start in a particular folder.

Step 3: Set the ownership, then load it#

Apple’s guide for launchd jobs says daemons “must be owned by the root user” and “must not be group writable or world writable”. Apple’s own daemons in /System/Library/LaunchDaemons are root with mode 644; launchd refuses a file that is writable by anyone else.

Terminal window
sudo chown root:wheel /Library/LaunchDaemons/com.heyvaldemar.run-service.plist
sudo chmod 644 /Library/LaunchDaemons/com.heyvaldemar.run-service.plist

Load the job into the system domain. This runs it now, because of RunAtLoad, and launchd will load it again at every boot:

Terminal window
sudo launchctl bootstrap system /Library/LaunchDaemons/com.heyvaldemar.run-service.plist

Check that launchd knows the job and see its last exit status:

Terminal window
sudo launchctl print system/com.heyvaldemar.run-service

Read the output of the script:

Terminal window
cat /var/log/run-service.log

To stop the job and remove it from launchd, the opposite of bootstrap is bootout:

Terminal window
sudo launchctl bootout system /Library/LaunchDaemons/com.heyvaldemar.run-service.plist

Delete the plist afterwards, or launchd loads it again at the next boot.

NOTE

launchctl load and launchctl unload still work, but Apple’s launchctl man page lists them under the legacy subcommands. bootstrap and bootout are the current forms, and they take a domain: system for daemons, gui/<uid> for agents.

The same job as a LaunchAgent#

If the script should run when you log in, as you, put the same plist in ~/Library/LaunchAgents, owned by you, and point the log at your home:

Terminal window
mkdir -p ~/Library/LaunchAgents
vim ~/Library/LaunchAgents/com.heyvaldemar.run-service.plist

Change the two log paths to /Users/valdemar/Library/Logs/run-service.log, then load it into your own GUI domain. id -u supplies your user ID:

Terminal window
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.heyvaldemar.run-service.plist
launchctl print gui/$(id -u)/com.heyvaldemar.run-service

No sudo anywhere: the agent runs with your rights and nothing more.

The fallback: cron and @reboot#

This is the 2021 recipe. It is three commands, and it is fine for a script that only needs the system to be up: no user folders, no keychain, no window.

Open root’s crontab using the command:

Terminal window
sudo crontab -e

Auto-run Scripts when macOS Boots - Step 6

Hit the “i” button to switch to edit mode, then add a line with the “@reboot” parameter and specify the full path to the previously created script:

Terminal window
@reboot /Users/valdemar/Documents/Scripts/run-service.sh

Auto-run Scripts when macOS Boots - Step 7

Now press the “Esc” button to exit edit mode, then type "" and press the “Enter” button to save your changes and exit the editor.

Auto-run Scripts when macOS Boots - Step 8

The script will now run when macOS boots, before any user logs in. What you give up: cron writes nothing to a log unless the script does it itself, it runs with a minimal PATH, so call programs by full path, and on current macOS a cron job cannot read the folders the privacy controls protect, such as Desktop, Documents and Downloads, until you add /usr/sbin/cron to Full Disk Access by hand. The launchd version has none of those surprises, which is why it comes first.


Vladimir Mikhalev

Docker Captain  ·  CNCF Ambassador  ·  IBM Champion

The Verdict — production-tested analysis on YouTube.

The Verdict

Inconvenient truths about shipping in the AI era

Container security, platform engineering, and the agentic shift — tested in production, argued without the hype. The verdict reaches your inbox the moment there's one worth sending.

Related Posts

Same category
  1. 1
    Recovering a Corrupt Exchange Database with Stellar Repair — Real-World Lab Test
    SysAdmin & IT Pro · We deliberately broke an Exchange 2019 server to test Stellar Repair on a dirty-shutdown EDB file. The recovery process, lab setup, and lessons learned.
  2. 2
    Terminal, Shell, Command Line, Prompt: Four Words, Four Things
    SysAdmin & IT Pro · The terminal is the window, the shell runs your commands, the prompt says it is ready, the command line is the idea. Plain definitions, examples, a cheat sheet.
  3. 3
    Install Ubuntu Server 22.04 LTS
    SysAdmin & IT Pro · Step-by-step guide to install Ubuntu Server 22.04 LTS. Covers language, disk setup, OpenSSH, user creation, and system configuration for beginners.
  4. 4
    Install OTRS on Ubuntu Server
    SysAdmin & IT Pro · Guide to installing OTRS Community Edition on Ubuntu Server — configure PostgreSQL, Apache, and SSL with Let's Encrypt, and launch OTRS securely.

Random Posts

Random
  1. 1
    Docker Scout is the Game-Changer in Container Security
    DevOps & Cloud · Docker Scout simplifies container security, giving a unified view of direct and transitive dependencies across every image layer.
  2. 2
    Configure Exchange Server 2016
    SysAdmin & IT Pro · Step-by-step guide to configure Exchange Server 2019 — mailbox databases, public folders, DNS, certificates, and send/receive connectors.
  3. 3
    Install Ubuntu Server 22.04 LTS
    SysAdmin & IT Pro · Step-by-step guide to install Ubuntu Server 22.04 LTS. Covers language, disk setup, OpenSSH, user creation, and system configuration for beginners.
  4. 4
    Recovering a Corrupt Exchange Database with Stellar Repair — Real-World Lab Test
    SysAdmin & IT Pro · We deliberately broke an Exchange 2019 server to test Stellar Repair on a dirty-shutdown EDB file. The recovery process, lab setup, and lessons learned.
Run a Script When macOS Boots: launchd First, cron as the Fallback
https://heyvaldemar.com/auto-run-scripts-when-macos-boots/
Author
Vladimir Mikhalev
Published
2021-02-09
Updated
2026-11-05
License
CC BY-NC-SA 4.0