How to Use Launchd to Schedule Run Scripts on Mac

Mac Script

Automating tasks by scheduling scripts is a powerful capability that many administrators rely on every day. Regular Mac users can also tap into this power using launchd, Apple’s preferred tool for task automation and management on macOS. From scheduling simple tasks like turning off Wi-Fi at a specific hour, to running complex system backups, launchd on Mac can help you automate your workflows, save time, and ensure that your system runs just the way you like it.

What Is Launchd?

Just like an orchestra requires a conductor to guide and harmonize the diverse instruments, macOS Ventura, with its myriad of processes and services, relies on launchd to ensure everything plays in perfect concert. As the first process launched by the macOS kernel when you boot up your computer, launchd takes center stage, orchestrating every subsequent process, service, and application, much like a conductor signaling the beginning of a symphony with the initial baton raise.

Beyond its role in system orchestration, launchd can be used to schedule scripts, a series of commands written to perform a specific task. This is done using the launchctl command, which serves as the interface for users to communicate and direct the conductor that is launchd.

Good to know: downloading torrents? Boost efficiency by downloading torrents with Terminal on your Mac.

Daemons and Agents

launchd is sometimes referred to as a daemon, a computer program that runs as a background process and typically isn’t designed to be directly controlled by a user. As far as daemons go, launchd is special, as it’s the maestro of all other macOS daemons, and it can decide when they start and stop. These subservient daemons run under the root user, so they can do just about anything.

Maxwell Demon Wiki Graphic
Image source: Wikipedia

However, as a user interested in task scheduling, running scripts under the root user isn’t always desirable or necessary. This is where agents come into play. Agents run on behalf of a logged-in user, offering a more restricted environment and ensuring that scripts or tasks are performed with the permissions and preferences of that specific user. For instance, if you would like a script to run that changes settings or accesses files within your account, you would use an agent.

Tip: running into compatibility issues with some of your favorite longstanding Mac apps? Discover how to install 32-bit Linux on an old Mac.

Writing Scripts

To run agents or daemons through launchd, you’ll need to write some scripts. The most common scripting language is bash. If you want to learn more about bash scripting, you can check out our beginner’s guide to bash scripting.

launchd-code-scriptto

Your launchd scripts can live in two different locations, depending on whether they’re meant to be run as agents or daemons:

  • For those scripts meant to be agents, acting on behalf of the logged-in user, they should be stored in “~/Library/LaunchAgents.”
  • Conversely, scripts intended to function as daemons, operating system-wide regardless of the logged-in user, belong in “/Library/LaunchDaemons.”

Remember, agents don’t have root permissions, so they can’t perform tasks that require deep system access. Daemons, on the other hand, run with root permissions and can handle tasks that affect the entire system.

Good to know: you can gain deeper control over your Mac by enabling Root User, allowing you to access its entire file system.

Job Descriptions

launchd-scripts-job-description

Scripts in launchd are triggered by job definitions, which are .plist files stored in specific directories. These XML files give the job a name, specify the script that should be launched, and indicate when the script should be run. Once you’ve written your script, you’ll write and load a job definition that launches the script at the appropriate time. A job definition looks something like this:

<?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>local.restart</string>
        <key>Program</key>
        <string>/Users/user/Scripts/restart.sh</string>
        <key>RunAtLoad</key>
        <true/>
    </dict>
</plist>

Modify as necessary, then put it in a text file with the .plist extension before dropping it in the correct directory (see above).

There are a few key parts to the job description:

  • Label: the name of the job within launchd. Must be unique for each job. These are written in reverse domain notation, and “local” is a great domain for private agents.
  • Program: the full path of the script this job description launches.
  • RunAtLoad: describes when the script should be run. There are a few different options here:
    • RunAtLoad: run as soon as the job definition is loaded. Runs only once per load.
    • StartInterval: start the job every n seconds.
    • StartCalendarInterval: run the job at a specific time and date.

Tip: need more space for those script files? Learn how to clear the cache on your Mac to quickly free up space.

Loading jobs into launchctl

launchd-scripts-launchctl-list

Once you’ve created your scripts and saved your agent in the right place, you’ll need to load it into launchctl. This will happen automatically on logins in the future.

To see what’s currently running in laucnhctl, you can use launchctl list in the terminal. This giant list can be grepped for your script by labeling it with something like the following:

launchctl list | grep local.restart

To load a script, open Terminal, and use the following command:

launchctl load ~/Library/LaunchAgents/local.restart.plist
launchd-scripts-launchctl-load

To remove the script from the launchctl queue, use the unload command:

launchctl unload ~/Library/LaunchAgents/local.restart.plist
launchd-scripts-launchctl-unload

Loading a job puts it into the launchd queue, and the job will run at the time specified in its launch conditions. If you want to run a script immediately no matter what, you should use the “start” command:

launchctl start local.restart

This command takes the job’s label and will only work if the job has already been loaded into launchctl.

Tip: become a Mac power user by using Raycast or Alfred to quickly launch your favorite apps.

Frequently Asked Questions

How can I check if launchd has started a script?

You can use the launchctl list command in the terminal. This will display all the loaded jobs. To find a specific script or job, use grep, e.g., launchctl list | grep your_script_name.

What if launchd is using too many system resources?

If launchd is consuming excessive resources, it’s usually due to a misbehaving script or job. You should review the scripts you’ve added recently, and unload them using launchctl unload /path/to/job.plist.

What's the difference between cron and launchd?

Both cron and launchd are scheduling services, but they operate differently. cron is an older Unix-based job scheduler that runs jobs at fixed times or intervals defined in a crontab file. launchd is Apple’s newer system for macOS that can start jobs based on various triggers – not just time.

Can I use other scripting languages besides bash with launchd?

launchd can execute any script that can be run from the terminal. This includes scripts written in Python, Perl, Ruby, and other languages.

Image credit: Pexels. All screenshots by David Morelo.

Is this post useful?
Subscribe to our newsletter!

Our latest tutorials delivered straight to your inbox

David Morelo
David Morelo - Staff Writer

David Morelo is a professional content writer in the technology niche, covering everything from consumer products to emerging technologies and their cross-industry application. His interest in technology started at an early age and has only grown stronger over the years.