Fine-grained task scheduling with cron.d

If you need more control over scheduling jobs than using the standard intervals like hourly and monthly, you can put a file in cron.d and tell it exactly how often to run.

Fine-grained control: cron.d

We've talked about using basic cron directories to run tasks on a standard schedule, but what if you want a schedule that's a bit less standard? What if you want to run a script every five minutes, or only run it on Tuesdays and Thursdays at 3am? You can do that (and more) with a bit of work.

Instead of using one of the preset schedules you can set your own schedule for a script by adding a specially-formatted file to the /etc/cron.d directory.

Checking the time

Before you set up those more specific schedules it helps to know what time your VPS thinks it is. Your VPS is most likely using UTC as its time zone, but since it's possible to change that let's look at a simple way to check the time:

$ date
Thu Jul 22 10:29:37 UTC 2010

The basic "date" command will return the current date and time on your VPS along with its time zone. Compare the time when you run the "date" command to your local time, and you can take that time difference into account when creating your own schedules.

If it's 6:29am when you run "date" and the VPS reports that it's 10:29am, then you know your VPS time runs four hours ahead of your local time. If you want to set a script to run every night at midnight, then you'll know that when it's midnight your time it will be 4am on your VPS.

The cron.d format

When setting up a schedule using cron.d the file needs to be in a specific format. An entry in a cron.d file will look something like:

42 4 1 * * root run-parts /etc/cron.monthly

You can have several of these lines in each file but it's usually a good idea to split distinct tasks into different files to keep things organized.

The cron.d entry above can be broken down into three parts: the schedule (the first part, "42 4 1 * *"), the user ("root"), and the command ("run-parts /etc/cron.monthly").

Let's look at each of those three components in more detail.


The first part (those first five figures) is where the schedule is set. Those numbers and asterisks represent units of time. For the example above, this part:

42 4 2 * *

can be read as:

42: The 42nd minute
4:  Of the 4th hour
2:  of the 2nd day
*:  of any month
*:  on any day of the week

Or, more briefly: The second day of every month at 4:42am.

Schedule components

When you have the order down the entry gets easier to read. That order, from left to right, is:

The minute on the clock (0-59)
The hour on a 24-hour clock (0-23)
The day of the month (1-31)
The month of the year (1-12)
The day of the week (0-7), where 0 is Sunday, 6 is Saturday, and 7 is Sunday again

An asterisk (the "*" character) tells cron to match any possible entry for that field. So an asterisk in the "month" field means that the scheduled command will run no matter what month it is. Think of the asterisk as meaning "every", so an asterisk in the "month" field means "every month".

So looking at that order again, setting up a schedule that will run on the 20th of every month at 3:55pm would look like:

55 15 20 * *

The minute, then the hour, then the day of the month are specified, and then asterisks for the month and the day of the week mean the schedule will run no matter what the month or the day of the week may be.

Getting specific

When cron checks a schedule it compares the current time and date to the values in the schedule. Every part of the time and date have to match before the schedule will be run (with one exception, noted below), so pay careful attention to how specific you get in a schedule. For example, this:

15 13 * * *

means "run at 1:15pm every day", but this:

15 13 * * 2

means "run at 1:15pm every Tuesday", and this:

15 13 29 2 *

means "run at 1:15pm on the 29th of February".

Getting less specific

You run into a special case when you specify both "day of month" and "day of week". When both the day of the month and the day of the week are specified the schedule will match if either is true, whereas when you compare any other scheduling fields they only match if both are true. So this schedule:

15 13 29 2 2

means "run at 1:15pm on the 29th of February as well as every Tuesday that falls in February".

In other words, the two "day" fields complement each other. Take out "day of month" or "day of week" and every entry narrows down the window when the schedule will run, but if you add the other "day" field as well you actually wind up adding to that window. It's a useful exception, but still potentially confusing if you don't watch out for it.

(Thanks to Scott Gilbert for pointing out this exception.)

Using ranges

Entries can also be stated as a range, as in "1-5" for 1 through 5. They can also be a list of numbers separated by commas (but no spaces), as in "1,3,5".

All these ranges and things mean that you can set up any kind of schedule you can think of, so long as you can figure out how to specify it. If you want a command to run every half hour between midnight and 5am on weekdays, that schedule would look like:

0,30 0-5 * * 1-5

The first entry, "0,30" will be a match every time the clock hits the hour or the half hour. The "0-5" specifies midnight (0) through 5am (5). The next two asterisks tell the schedule to run regardless of what day of the month or what month of the year it is. And then the final "1-5" tells the schedule to run only on days 1 (Monday) through 5 (Friday) — weekdays.


With a better idea of how to read the cron schedule, there's one more scheduling trick we'll cover: stepping.

*/5 * * * *

When you specify a slash then a number after a cron schedule entry, that tells cron to only consider it a match when the value in that field is divisible by the number (the "step") after the slash. To put it a little plainer, "*/5" means "any number that can be divided evenly by 5". To state it even more simply, "every 5 minutes".

Another way to write that would be:

0,5,10,15,20,25,30,35,40,45,50,55 * * * *

Not as pretty as "*/5", eh?

You can combine ranges and steps too:

30 9-18/2 * * 1-5

That schedule would run at the thirty-minute mark, but only every other hour between 9am and 6pm on weekdays. The hours that would match would be 10, 12, 14, 16, and 18.

Once you have the scheduling down you've taken most of the mystery out of cron.d. The rest of it is much easier.


The next entry in a cron.d line is the user that will own the process when it runs. So for the line:

42 4 1 * * root run-parts /etc/cron.monthly

The "root" entry that follows the schedule tells cron that the command will be run as root.


And finally, the rest of the cron.d line will be the command that will be run, along with its options. So in the example above, cron runs "run-parts /etc/cron.monthly".

As it happens, that's the command that runs the scripts in /etc/cron.monthly every month. If you hunt that cron.d entry down on your system (usually in /etc/crontab) you can edit it to change on what time of day, or day of the month, cron will run its monthly scripts. The entries for the daily and hourly cron directories are similar.


A whole new world of task scheduling has opened to you. So long as you can get the numbers and asterisks right, the rest should fall into place. You can also now look at the cron.d files that software packages have installed and tell what kinds of background tasks are being run by those programs, and when.

There's one other way to approach task scheduling with cron, and that's the crontab. It's similar to cron.d but the access is a bit different. It can be handy to know about because there are still some software packages that set up crontab entries, and because crontab makes it easier to handle multi-user task scheduling. We'll talk about the crontab in the next article in this series.

  • -- Jered

Article Comments:

Scott Gilbert commented Sat Aug 07 17:37:03 UTC 2010:

"Find Grained Task Scheduling with Cron" is a very nicely written introduction to cron. However, I think there is an error in it. In the article, it states the following:

15 13 29 2 2

means "run at 1:15pm on the 29th of February so long as it's Tuesday".

I believe this is incorrect. While this may be version specific, every version of cron that I've ever used actually "ORs" the day-of-week and day-of-month specifications. So the actual meaning would be "run every Tuesday and on the 29th of February regardless of what day of the week it falls on."

This behavior is documented in the crontab.5 man page as follows:

Note: The day of a command’s execution can be specified by two fields — day of month, and day of week. If both fields are restricted (i.e., aren’t *), the command will be run when either field matches the current time. For example, ‘‘30 4 1,15 * 5’’ would cause a command to be run at 4:30 am on the 1st and 15th of each month, plus every Friday.

Jered commented Mon Aug 09 16:15:05 UTC 2010:

You are absolutely right, and I thank you for the correction. The article has been updated accordingly.

Nick commented Tue Aug 31 15:53:37 UTC 2010:

Thanks very much - it's succinct, easy to follow and a great quick reference!

Much appreciated!

Want to comment?

(not made public)


(use plain text or Markdown syntax)