151

I need to create a CRON job that will run on the last day of every month. I will create it using cPanel.

Any help is appreciated. Thanks

DazBaldwin
  • 4,125
  • 3
  • 39
  • 43
Utku Dalmaz
  • 9,780
  • 28
  • 90
  • 130

22 Answers22

249

Possibly the easiest way is to simply do three separate jobs:

55 23 30 4,6,9,11        * myjob.sh
55 23 31 1,3,5,7,8,10,12 * myjob.sh
55 23 28 2               * myjob.sh

That will run on the 28th of February though, even on leap years so, if that's a problem, you'll need to find another way.


However, it's usually both substantially easier and correct to run the job as soon as possible on the first day of each month, with something like:

0 0 1 * * myjob.sh

and modify the script to process the previous month's data.

This removes any hassles you may encounter with figuring out which day is the last of the month, and also ensures that all data for that month is available, assuming you're processing data. Running at five minutes to midnight on the last day of the month may see you missing anything that happens between then and midnight.

This is the usual way to do it anyway, for most end-of-month jobs.


If you still really want to run it on the last day of the month, one option is to simply detect if tomorrow is the first (either as part of your script, or in the crontab itself).

So, something like:

55 23 28-31 * * [[ "$(date --date=tomorrow +\%d)" == "01" ]] && myjob.sh

should be a good start, assuming you have a relatively intelligent date program.

If your date program isn't quite advanced enough to give you relative dates, you can just put together a very simple program to give you tomorrow's day of the month (you don't need the full power of date), such as:

#include <stdio.h>
#include <time.h>

int main (void) {
    // Get today, somewhere around midday (no DST issues).

    time_t noonish = time (0);
    struct tm *localtm = localtime (&noonish);
    localtm->tm_hour = 12;

    // Add one day (86,400 seconds).

    noonish = mktime (localtm) + 86400;
    localtm = localtime (&noonish);

    // Output just day of month.

    printf ("%d\n", localtm->tm_mday);

    return 0;
}

and then use (assuming you've called it tomdom for "tomorrow's day of month"):

55 23 28-31 * * [[ "$(tomdom)" == "1" ]] && myjob.sh

Though you may want to consider adding error checking since both time() and mktime() can return -1 if something goes wrong. The code above, for reasons of simplicity, does not take that into account.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 12
    yeah it can be easier to run on every first day instead of the last day :) – Utku Dalmaz May 26 '11 at 13:36
  • 1
    1st day of month indeed. Here's how the code will look like in PHP $date = new DateTime('2013-03-01'); $date->modify('-1 month'); $previousMonth = $date->format('Y-m'); // $previousMonth is now 2013-02. Build query to fetch products for the previous month. – Yasen Jan 19 '13 at 06:41
  • Leap year feb 29 data will be lost, we need to consider that too. Thunder Rabbit answer below consider that but cron run twice in feb of leap year – Hari Swaminathan Feb 27 '14 at 07:30
  • 1
    @Hari, the _preferred_ solution would be to run on the first of the month and collect the previous month's data. Feb 29 would not be missed in that case. – paxdiablo Feb 27 '14 at 07:36
  • 1
    Considering leap years: and if you don't mind it running twice: 55 23 28,29 2 * myjob.sh – radiantRazor Jun 02 '15 at 06:29
  • Maybe should also add `55 23 29 2 * myjob.sh`, just in case. –  Jul 02 '15 at 10:51
  • You might want give credit to "Indie" as you essentially included his answer into yours. – oᴉɹǝɥɔ Feb 09 '18 at 14:58
  • I would think the upvotes would be enough credit, especially given the crossover is about eight lines out of sixty five :-) Just because answers may have similar information, doesn't mean they were copied, correlation is not causation and all that, I'll often go back and improve answers based on what I've learnt since the original answer, it's usually prompted by comments from people, just like this one. And, FWIW, there are *four* answers here that mention the tomorrow/plusoneday method so I'm not sure why you singled out just the one. – paxdiablo Feb 09 '18 at 22:35
58

There's a slightly shorter method that can be used similar to one of the ones above. That is:

[ $(date -d +1day +%d) -eq 1 ] && echo "last day of month"

Also, the crontab entry could be update to only check on the 28th to 31st as it's pointless running it the other days of the month. Which would give you:

0 23 28-31 * * [ $(date -d +1day +%d) -eq 1 ] && myscript.sh
Indie
  • 581
  • 4
  • 2
  • I couldn't get this to work as a crontab entry (something needs to be escaped I think). It worked fine in a shell script called from crontab however. FYI, the error I got was `/bin/sh: -c: line 1: unexpected EOF while looking for matching ')'`. – Mark Rajcok May 02 '14 at 15:56
  • 17
    This works great. In the crontab file the % must be escaped. So `[ $(date -d +1day +\%d) -eq 1 ] && run_job` – ColinM Sep 02 '14 at 14:27
  • Nice trick indeed! But the question was tagged ```posix``` and [POSIX date](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/date.html "IEEE Std 1003.1, 2013 Edition: date") doesn't support ```"-d +1day"``` :-\ A more complicated (and ugly) solution would be: ```[ `date +\%d` -eq `cal | xargs echo | awk '{print $NF}'` ] && myscript.sh``` – ckujau May 17 '15 at 20:31
19

For AWS Cloudwatch cron implementation (Scheduling Lambdas, etc..) this works:

55 23 L * ? *

Running at 11:55pm on the last day of each month.

Dan Herman
  • 1,395
  • 1
  • 15
  • 26
17

What about this one, after Wikipedia?

55 23 L * * /full/path/to/command
Piohen
  • 1,514
  • 15
  • 23
  • Well, what about it? That: "bad day-of-month errors in crontab file, can't install. Do you want to retry the same edit?" – webjunkie Jul 17 '12 at 15:23
  • 17
    Just to be clear, that wikipedia entry also mentions that "L" is non-standard. – sdupton Sep 11 '13 at 21:10
12

Adapting paxdiablo's solution, I run on the 28th and 29th of February. The data from the 29th overwrites the 28th.

# min  hr  date     month          dow
  55   23  31     1,3,5,7,8,10,12   * /path/monthly_copy_data.sh
  55   23  30     4,6,9,11          * /path/monthly_copy_data.sh
  55   23  28,29  2                 * /path/monthly_copy_data.sh
Thunder Rabbit
  • 5,405
  • 8
  • 44
  • 82
  • 4
    Unless, of course, the job has some destructive aspect such as clearing out all data as it's processed :-) – paxdiablo Feb 09 '18 at 22:32
  • This is actually a painless option (least technical), and you might not care that thrice in 4 years you will get the cronjob too early if you just omit the `,29`. – Matt Jul 03 '18 at 12:33
  • @Matt: Umm, don't you mean that it will run one day too early ***once*** in four years if your crontab entry says ```55   23   28    2```? – G-Man Says 'Reinstate Monica' Feb 08 '19 at 04:10
  • @G-Man Yes, you're right, and on top of that, you have it run a second time on the 29th. – Matt Oct 26 '19 at 22:15
9

Some cron implementations support the "L" flag to represent the last day of the month.

If you're lucky to be using one of those implementations, it's as simple as:

0 55 23 L * ?

That will run at 11:55 pm on the last day of every month.

http://www.quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger

Noah Heldman
  • 6,724
  • 3
  • 40
  • 40
9

For a safer method in a crontab based on @Indie solution (use absolute path to date + $() does not works on all crontab systems):

0 23 28-31 * * [ `/bin/date -d +1day +\%d` -eq 1 ] && myscript.sh
zigarn
  • 10,892
  • 2
  • 31
  • 45
8

You could set up a cron job to run on every day of the month, and have it run a shell script like the following. This script works out whether tomorrow's day number is less than today's (i.e. if tomorrow is a new month), and then does whatever you want.

TODAY=`date +%d`
TOMORROW=`date +%d -d "1 day"`

# See if tomorrow's day is less than today's
if [ $TOMORROW -lt $TODAY ]; then
echo "This is the last day of the month"
# Do stuff...
fi
thomson_matt
  • 7,473
  • 3
  • 39
  • 47
  • I'm not sure what you gain by checking if it's less than today - you should just be able to check if it's the 1st. Unless the first day of the month can be the 2nd or 3rd :-) – paxdiablo Apr 02 '20 at 12:47
7
#########################################################
# Memory Aid 
# environment    HOME=$HOME SHELL=$SHELL LOGNAME=$LOGNAME PATH=$PATH
#########################################################
#
# string         meaning
# ------         -------
# @reboot        Run once, at startup.
# @yearly        Run once a year, "0 0 1 1 *".
# @annually      (same as @yearly)
# @monthly       Run once a month, "0 0 1 * *".
# @weekly        Run once a week, "0 0 * * 0".
# @daily         Run once a day, "0 0 * * *".
# @midnight      (same as @daily)
# @hourly        Run once an hour, "0 * * * *".
#mm     hh      Mday    Mon     Dow     CMD # minute, hour, month-day month DayofW CMD
#........................................Minute of the hour
#|      .................................Hour in the day (0..23)
#|      |       .........................Day of month, 1..31 (mon,tue,wed)
#|      |       |       .................Month (1.12) Jan, Feb.. Dec
#|      |       |       |        ........day of the week 0-6  7==0
#|      |       |       |        |      |command to be executed
#V      V       V       V        V      V
*       *       28-31   *       *       [ `date -d +'1 day' +\%d` -eq 1 ] && echo "Tomorrow is the first today now is  `date`" >> ~/message
1       0       1       *       *       rm -f ~/message
*       *       28-31   *       *       [ `date -d +'1 day' +\%d` -eq 1 ] && echo "HOME=$HOME LOGNAME=$LOGNAME SHELL = $SHELL PATH=$PATH" 
7

I found out solution (On the last day of the month) like below from this site.

 0 0 0 L * ? *

CRON details:

Seconds Minutes Hours   Day Of Month    Month   Day Of Week  Year
0       0       0       L               *       ?            *

To cross verify above expression, click here which gives output like below.

2021-12-31 Fri 00:00:00
2022-01-31 Mon 00:00:00
2022-02-28 Mon 00:00:00
2022-03-31 Thu 00:00:00
2022-04-30 Sat 00:00:00
Justin
  • 855
  • 2
  • 11
  • 30
5

Set up a cron job to run on the first day of the month. Then change the system's clock to be one day ahead.

Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
4
00 23 * * * [[ $(date +'%d') -eq $(cal | awk '!/^$/{ print $NF }' | tail -1) ]] && job

Check out a related question on the unix.com forum.

rid
  • 61,078
  • 31
  • 152
  • 193
3
55 23 28-31 * * echo "[ $(date -d +1day +%d) -eq 1 ] && my.sh" | /bin/bash 
  • 1
    echoing the command and piping it into bash is the best way when working with cron. Since cron is using sh and not bash. Check where your bash is using 'which bash'. On FreeBSD it is /usr/local/bin/bash, on Linux /bin/bash. – Donald Duck Sep 12 '18 at 02:39
3

You can just connect all answers in one cron line and use only date command.

Just check the difference between day of the month which is today and will be tomorrow:

0 23 * * * root [ $(expr $(date +\%d -d '1 days') - $(date +\%d)  ) -le 0 ]  && echo true

If these difference is below 0 it means that we change the month and there is last day of the month.

Lukasz Stelmach
  • 5,281
  • 4
  • 25
  • 29
2

In tools like Jenkins, where usually there is no support for L nor tools similar to date, a cool trick might be setting up the timezone correctly. E.g. Pacific/Kiritimati is GMT+14:00, so if you're in Europe or in the US, this might do the trick.

TZ=Pacific/Kiritimati \n H 0 1 * * 

Result: Would last have run at Saturday, April 30, 2022 10:54:53 AM GMT; would next run at Tuesday, May 31, 2022 10:54:53 AM GMT.

wekt0r
  • 21
  • 2
1

What about this?

edit user's .bashprofile adding:

export LAST_DAY_OF_MONTH=$(cal | awk '!/^$/{ print $NF }' | tail -1)

Then add this entry to crontab:

mm hh * * 1-7 [[ $(date +'%d') -eq $LAST_DAY_OF_MONTH ]] && /absolutepath/myscript.sh
livibetter
  • 19,832
  • 3
  • 42
  • 42
Raul Baron
  • 67
  • 1
  • 7
1

Use the below code to run cron on the last day of the month in PHP

$commands = '30 23 '.date('t').' '.date('n').' *';
vimuth
  • 5,064
  • 33
  • 79
  • 116
0

The last day of month can be 28-31 depending on what month it is (Feb, March etc). However in either of these cases, the next day is always 1st of next month. So we can use that to make sure we run some job always on the last day of a month using the code below:

0 8 28-31 * * [ "$(date +%d -d tomorrow)" = "01" ] && /your/script.sh
Rakesh Chintha
  • 615
  • 5
  • 6
0

Not sure of other languages but in javascript it is possible.

If you need your job to be completed before first day of month node-cron will allow you to set timezone - you have to set UTC+12:00 and if job is not too long most of the world will have results before start of their month.

fider
  • 1,976
  • 26
  • 29
0

If the day-of-the-month field could accept day zero that would very simply solve this problem. Eg. astronomers use day zero to express the last day of the previous month. So

00 08 00 * * /usr/local/sbin/backup

would do the job in simple and easy way.

Shubham Chadokar
  • 2,520
  • 1
  • 24
  • 45
0

Better way to schedule cron on every next month of 1st day

This will run the command foo at 12:00AM.

0 0 1 * * /usr/bin/foo

  • 1
    This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/30354038) – BNazaruk Nov 17 '21 at 18:02
  • not better if you need everything to run before midnight (for instance, if you have other things like automatic upgrades and reboots scheduled for after midnight which would interfere) – Michael Mar 30 '23 at 23:41
0

Be cautious with "yesterday", "today", "1day" in the 'date' program if running between midnight and 1am, because often those really mean "24 hours" which will be two days when daylight saving time change causes a 23 hour day. I use "date -d '1am -12 hour' "

dnsbob
  • 1