Launchd is Apple’s replacement in OS X for several Unix process management utilities, most notably cron. I use it to run several scripts at scheduled times or fixed intervals. Every day my computer is set to download my twitter statuses and check my library card for overdue books. Every morning my computer resets its volume to medium. Every week it backs up the WordPress database of my “family pictures” blog. It syncs my work files between my computer and the university file server.
Almost anything you can do with cron you can do with launchd, but with more power and flexibility. Unlike cron, launchd does not assume that your computer is always running. So if your computer happens to be sleeping at the time a job is scheduled, it will run the job when it wakes up. This is probably the best feature of launchd, because it allows me to run scripts on my iMac while still letting it sleep when I’m not using it.
I have pieced together what I know about using launchd to schedule jobs from tutorials across the internet, trial and error, and the manuals. This is my attempt to gather all my knowledge about this in one place. If there is something you think I should add or fix, let me know.
Because this article is longer than usual.
- Quick start
- Launchd basics
- Tools to manage your agents
- Format of the plist file
- Random thoughts
The best way to get started is to buy Lingon 3 by Peter Borg from the Mac App Store. This will allow you to schedule basic jobs without ever looking at a plist file. Just fill out the fields and you are ready to go:
Even if you have more complicated needs, Lingon gives you a great place to start. Instead of copying and pasting a plist you found on the internet, you can use Lingon to give you something to start with.
Lingon doesn’t have to stay running in the background. It has no menubar icon. It just sets up the job and gets out of the way, letting OS X do the rest. And since OS X already uses launchd to run just about everything, from Spotlight to ssh-agent to the bezel notifications that appear when you change the volume, scheduling jobs will not add any overhead.
Each launchd agent is stored in an xml plist file. The file contains
information about what program to run, when to run it, which arguments to use,
and other options. Although technically you can make things work no matter
where the plist file is saved, it is best to put it in
~/Library/LaunchAgents, because plists in this folder are automatically
loaded into launchd when the computer restarts.
Each agent has a label, which must be unique. Apple uses reverse domain syntax
com.apple.whatever, but it doesn’t matter. The plist filename can be
anything, but you would be crazy to use anything other than the label, e.g.
com.apple.whatever.plist. Sometimes agents are referred to by the file, and
sometimes by the label.
Warning: At some point you will use one of your plists as a template to create another. You will of course give them different filenames, but you will forget to change the label, which means only one of them will work. Hopefully this warning will then enter your mind and you will solve the problem.
Apple uses the terms load and unload to mean that an agent is in the
system, ready to go, and start or stop to talk about running or killing the
actual process. So all agents in your
LaunchAgents folder are loaded when the
computer starts up. Then it pays attention to when they are scheduled and
starts them at the appropriate time. If you create a new plist file you need to
load it manually. If you change a plist file, you need to unload it and load it
Tools to manage your agents
- Launchctl is Apple’s tool, which gives you most control at the expense of complexity.
- Lingon is easy, but has some issues.
- Lunchy is like launchctl, but slightly more convenient.
Apple provides launchctl to manage your agents. The main commands you need are
launchctl load ~/Library/LaunchAgents/net.nathangrigg.archive-tweets.plist launchctl unload ~/Library/LaunchAgents/net.nathangrigg.archive-tweets.plist launchctl start net.nathangrigg.archive-tweets launchctl stop net.nathangrigg.archive-tweets launchctl list
unload require the filename, while
require the label. The
start command will manually run the job, even if it
isn’t the right time. This can be useful for testing. The
stop command just
kills the process, but is convenient because you don’t need to know the pid.
list command shows all loaded agents, with the pid if they are currently
running and the exit code returned the last time they ran.
I mentioned Lingon earlier, and it is a great tool for creating an managing launchd agents. It is the easiest way to create a basic plist. When you use Lingon to edit an agent, pressing the Save button automatically loads and unloads the job.
There are some issues with Lingon that you should be aware of. Mainly, Lingon does not support all the possible agent options and is not always careful to preserve changes you make manually. If you manually edit a plist and later edit it using Lingon, you may lose your manual edits without warning.
One other useful tool is Lunchy by Mike Perham. This is a ruby script
that speeds up the loading and unloading of agents by allowing you to refer to
a plist by any unique substring of the filename. My only issue is that it uses
terminology that conflicts with Apple’s. Lunchy uses
start to mean
stop to mean
unload. It mostly compensates by providing very useful
restart to unload and then load and
edit to edit the file in your
Install it using
gem install lunchy and then edit the file using
lunchy edit archive-tweets
Now reload it using
lunchy restart archive-tweets
lunchy allow you to disable an agent using the
stop. I do not recommend this. An agent that has been disabled
will not load when the computer restarts and cannot be loaded using
start without using the
-w flag again. You will probably just be
confused later about why an agent is not loading even though it is in the right
place. Information about which agents have been disabled in this manner is
stored in a separate file. In Lion, this is the file
NNN is your user id number (find it using
Format of the plist file
Here is a very basic plist file to run a script every 86,400 seconds.
xml: 1: <?xml version="1.0" encoding="UTF-8"?> 2: <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3: <plist version="1.0"> 4: <dict> 5: <key>Label</key> 6: <string>net.nathangrigg.archive-tweets</string> 7: <key>ProgramArguments</key> 8: <array> 9: <string>/Users/grigg/bin/archive-tweets.py</string> 10: </array> 11: <key>StartInterval</key> 12: <integer>86400</integer> 13: </dict> 14: </plist>
There are several other keys you can insert between lines 4 and 13 to activate other options. If you want to see what is available, read the launchd.plist manual.
Note that I have provided the full path to the script, since globs aren’t
expanded by default. If you want to expand globs, you can include an
EnableGlobbing key followed by
If your script requires arguments, you would supply these in extra
tags after line 9.
If you download a script from the internet or write one yourself, make sure it is executable, or this might not work.
By default, anything written to standard out or standard error ends up in the
system log. If you would like it to be somewhere else, you can use the
KeepAlive key allows a script to be run multiple times, depending on
certain conditions. If you set it to
<true/>, then the script will be run
over and over again forever. The following snippet will rerun the script if it
returns a nonzero exit code. Read the xml as “Keep the process alive as long as
it doesn’t successfully exit”.
xml: <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict>
In most cases, the system will wait 10 seconds between runs of the script to
save system resources. You can adjust this with a
ThrottleInterval key, which
takes an integer argument, and should be outside the
You can also set the agent to stay alive depending on the (non)existence of an
internet connection using
NetworkState or of a file using
In older versions of OS X, there was an
OnDemand key which was required. It
is now obsolete and has been replaced by
KeepAlive, which is optional. Many
of the other examples on the internet still have an
OnDemand key, but you
don’t need it.
Your launchd agents are loaded when you log in, but not unloaded when you log
out. So the only time your agents aren’t loaded is during the time between a
restart and when you log in. If you have multiple users and need something to
run no matter who is logged in, you should look into putting it in
If your computer sleeps often, it will be asleep when jobs should run, which
means it will run them right when it wakes up, possibly before it connects to
the internet. I have experimented with
NetworkState to get a
job to repeat itself until there is a network connection. You could also use
SuccessfulExit write the script so that it only returns a nonzero code to
mean “run again in 10 seconds.” Either method would have the script running
(and presumably failing) every 10 seconds when you have no internet connection.
A better idea would be to just sleep 5 seconds at the beginning of your script.
Or you could double the run frequency and hope for the best.
Here are updates to the original post. Thanks to readers who have sent in helpful comments.
For security reasons, launchd will not run LaunchAgents whose plist files have
the wrong permissions. For example, they must not be writable by anyone other
than the owner. Root LaunchAgents stored in
/Library/LaunchAgents must be
owned by the root user.