How to Run a cron Job Every Two Weeks / Months / Days

We may want to run some jobs for every two weeks/months/days… under some situation such as backing up for every other week. In addition, we may add more complex rules for running jobs, e.g. run a command when the load of the server is higher than a certain level. With the help of the shell programming language we can easily achieve this goal.

Basics of the crontab

Crontab file controls how and what to run by cron. The crontab line has a format as follows:

.---------------- minute (0-59)
|  .------------- hour (0-23)
|  |  .---------- day of month (1-31)
|  |  |  .------- month (1-12) OR jan,feb,mar,apr ...
|  |  |  |  .---- day of week (0-6) (Sunday=0 or 7) OR sun,mon,tue ...
|  |  |  |  |
*  *  *  *  *  command to be executed

To add a cron job for your current user, run

$ crontab -e

And edit the crontab file by adding/deleting lines. Check the crontab man page for more details.

The cron job is run as a piece of shell code. /bin/sh is the shell by default. You may change it to some other shells like bash by adding a like like SHELL=/bin/bash at the beginning of the crontab file.

Method 1: use crontab extensions

crontab provides some extensions to support this: ranges can include “steps” like /2. 1-9/2 is the same as 1,3,5,7,9. This can be the easiest method if the job is intended to be run every two N in a range such as N is “day” and the range is “days in a month”. Check the crontab man page for more details about ranges and steps of crontab.

An example.

To run the command every two months at 1:00 on the first day of the month, the crontab is

0 1 1 */2 * command to be executed

But please be aware that the every two N here is not exactly every two N. For days, it is every two days in a month. For months, it is every two months in a year. If you need to make sure your cron job is run exactly every two N, check the other methods.

Method 2: use a test statement to control whether to really run the command

The command executed for a cron job is a piece of shell code. So you can make the command run every day/week/month while use a test statement to control whether to really run the command.

Let’s take an example to illustrate the idea. Now, we want to run a job every 2 days. As there are months having 31 days, some jobs are not run exactly every 2 days if we use method 1 above. We can have a cron job as follows to make sure it runs exactly every 2 days (86400 seconds) at 1:00.

0 1 * * * < $((($(date +%s) / 86400) % 2)) == 0 > && /path/to/your/command

Here, the shell script < $((($(date +%s) / 86400) % 2)) == 0 > && /path/to/your/command is run every day. But your command is run only when $((($(date +%s) / 86400) % 2)) == 0. That is when the number of days since 1970-01-01 00:00:00 UTC is divisible by 2.

With the help of date, we can make make test statements. Let’s take another example, run a job exactly every 5 months starting from March of 2016. The cron tab entry can be

0 1 1 * * < $(((($(date +%Y) - 2016) * 12 + $(date +%m) - 3) % 5)) == 0 > && /path/to/your/command

Only when the number of months since March 2016 is divisible by 5, the command is run.

Method 3: use a shell script with its state saved on disk

The idea is to invoke a shell script every day/week/month and the shell judges whether to run the job. The key idea here is that the shell uses a file to mark its state.

The shell code:

#!/bin/bash

# a file marking the state on disk
mark_file=$HOME/.job-run-marker-1

# check whether the job run last time it is invoked
if [ -e $mark_file ] ; then
    rm -f $mark_file
else
    touch $mark_file
    exit 0
fi

# job command is here

The script will not find $mark_file on the first run, so it will create it and exit. On the second run the script will remove $mark_file and then proceed to execute the job command. For the third run, it is the same as the first run. So if this script is run weekly by cron, the job command will run every two weeks.

You can extend the shell with more complex logic like storing the last run date/time in the $mark_file or check more system information like the CPU/mem/disk load.

An example is as follows.

We want to back up Xen DomU VMs for every two weeks. We create a script /home/share/bin/xen-bak. The beginning part of this script is like what we list above.

The line for the job is as follows.

0 2 * * 2 /home/share/bin/xen-bak

The backup command will run at 2:00 for every other Tuesday.

Eric Ma

Eric is a systems guy. Eric is interested in building high-performance and scalable distributed systems and related technologies. The views or opinions expressed here are solely Eric's own and do not necessarily represent those of any third parties.

34 comments:

  1. Wouldn’t 0 2 * * 2 just run it 2am EVERY Tuesday?
    I think what your looking for is:
    0 2 * * 2/2
    or
    0 2 * * Tue/2

    1. You are right. The most updated instruction is at the beginning of the post.

      The “poor man’s” solution here works actually — the invoked script is invoked every Tue but it records its state and does nothing every next time it is invoked.

      1. Your explanation defeats the purpose of CRON. I could implement my own logic in code (every two weeks) and I only need cron to run every minute … So please correct your script.

      2. Hi Eric, I have a request to make the job run in each alternative monday usually twice a month….

        What’s the instruction for that??

  2. This is easy to do in your crontab file with no external scripting:

    0 2 * * tue [ `expr $(date ‘+\%U’`) % 2` -eq ‘0’ ] && /home/share/bin/real-xen-bak

    This will only work if your “date” command understands the “%U” format, which gives the week-of-the-year. The “date” command on most *NIX systems will understand this, as “date” uses the C-library’s “strftime” function which specifies “%U” as the format specifier for week-of-year.

  3. I was wondering if there was a way to schedule something, say every 5 months, that changes the month when it crosses a year boundary. For example I have the following cron expression :

    0 1 1 15 3/5 ? *

    Which, when I run it starting at the beginning of the year gives me these fire times :
    1. Sunday, March 15, 2015 1:01 AM
    2. Saturday, August 15, 2015 1:01 AM
    3. Tuesday, March 15, 2016 1:01 AM
    4. Monday, August 15, 2016 1:01 AM
    5. Wednesday, March 15, 2017 1:01 AM

    Notice that if it were truly scheduling every 5 months, starting on the 3rd month, I would expect it to schedule in March 2015, August 2015, January 2016, June 2016, etc

    Is this not the correct cron expression, or is there something I’m missing?

    1. Hi Quenten, that’s a good question. The ‘*/5’ for months in crontab means every 5 months in a year.

      For your purpose, you may try:

      0 1 1 * * [[ $(((($(date +%Y) - 2016) * 12 + $(date +%m) - 3) % 5)) == 0 ]] && /path/to/your/command
      

      I also updated the post with more details. Please check out.

    1. You may use

      0 8 * * 2 [[ $((($(date +%s) / 604800) % 2)) == 0 ]] && /path/to/your/cmd

      It is invoked every Tuesday at 0800. However, your command will be executed only if the number of weeks since epoch is even.

  4. Brilliant. Too bad we are tie with epoch and hv to be restrict to even, so if there is 5 Tuesday in a month, it will only run on the 2nd and 4th week. But its good enough at the moment :)

    This will work with all Centos or only certain version? we are running on version 7.

    p/s – how do you get the the 604800? its too small for minutes and too little if for seconds

    thanks,

    1. A more complex test condition may be used if you want to run it on 2nd and 4th Tuesdays in a month.

      I believe this works on modern cron including those from CentOS 7. But please test it before deploying it into production.

      604800 is 60 * 60 * 24 * 7. It is the number of seconds in a week.

  5. Everyone,

    I also have a script to run every 2nd and 4th Tuesday of each month for 2017. I use the following entries (starting June 13, 2017 to Dec 26, 2017). I know it is long, but it is easier to read and without the need for any date calculation.
    Note: make sure that you do not specify the day of week (2); otherwise, cron will run on
    BOTH on a specify day of the month AND on Tuesday of EVERY week.

    I use this URL to validate
    this URL to calculate: http://cron.schlitt.info/index.php?cron=30+3+13+6+*&iterations=10&test=Test

    30 3 13 6 * myscript.sh
    30 3 27 6 * myscript.sh
    30 3 11 7 * myscript.sh
    30 3 25 7 * myscript.sh
    30 3 8 8 * myscript.sh
    30 3 22 8 * myscript.sh
    30 3 12 9 * myscript.sh
    30 3 26 9 * myscript.sh
    30 3 10 10 * myscript.sh
    30 3 24 10 * myscript.sh
    30 3 14 11 * myscript.sh
    30 3 28 11 * myscript.sh
    30 3 12 12 * myscript.sh
    30 3 26 12 * myscript.sh

    I also found another method for the same time period June 13 to Dec 26 of 2017, but prefer the 1st method as it is more clear to me. The 2nd method below requires calculate whether a Tues will be on even or odd week of the month.

    30 3 * 6-8,11,12 2 [[ $(expr `date +\%W` \% 2) = 0 ]] && myscript.sh
    30 3 * 9,10 2 [[ $(expr `date +\%W` \% 2) = 1 ]] && myscript.sh

    1. Interesting. But it seems the crontab entries need to be refreshed each year if Tuesday is a hard requirement.

    1. You may consider a crontab to be invoked every alternate day at 12am with a “test statement” to check whether it is after 5th day.

  6. * */3 * * tue runSomething.here

    To run some thing here every 3 days always on tue* day, is it correct?

    1. Commands are executed by cron(8) when the ‘minute’, ‘hour’, and ‘month of the year’ fields match the current time, and at least one of the two ‘day’ fields (‘day of month’, or ‘day of week’) match the current time (see “Note” below).

      as from crontab manual.

      So, a rule like

      `0 0 */3 * 2 your_command`

      *should* run at 0 o’clock every 3 days if it is Tuesday from the rule.

      But the implementation may be different. I did not test it.

    1. A cron job like this may work: 0 0 1,15 * * /path/to/your/command

      Every day 1 and day 15 of a month, the command will run at 00:00.

      1. @ Eric ma,

        I would like to run the cron every 15days interval I tried as below
        00 21 */15 * *
        But the result didn’t not satisfying the 15days interval condition.

        Nxt at 2020-07-16 21:00:00
        then at 2020-07-31 21:00:00
        then at 2020-08-01 21:00:00
        then at 2020-08-16 21:00:00
        then at 2020-08-31 21:00:00
        This can work for one month but in next Month it again triggers from 1st day of the month. Can you help to provide the condition to satisfy the requirement?

  7. Sir, I have to write crontab:
    Latest two day data must be present remaining past data will be clean the temp
    How I have to do that

  8. I would like to run the cron every 15days interval I tried as below
    00 21 */15 * *
    But the result didn’t not satisfying the 15days interval condition.

    Nxt at 2020-07-16 21:00:00
    then at 2020-07-31 21:00:00
    then at 2020-08-01 21:00:00
    then at 2020-08-16 21:00:00
    then at 2020-08-31 21:00:00
    This can work for one month but in next Month it again triggers from 1st day of the month. Can you help to provide the condition to satisfy the requirement?

  9. I think a test statement is an easy way to implement.

    Example: Let’s say a command needs to be executed every Thursday.

    0 0 1-7,15-21 * * [ `date +\%u` = 4 ] && /path/to/command

  10. Trying to understand the syntax. If I wanted this to run every 85 days, is it just as simple as changing the 2 to 85?

    0 1 * * * && /path/to/your/command

Leave a Reply

Your email address will not be published. Required fields are marked *