Take the 2-minute tour ×
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It's 100% free, no registration required.

Many a time and oft, crontab scripts are not executed as expected. There are numerous reasons for that, for example: wrong crontab notation, permissions, environment variables and many more.

This community wiki aims to aggregate the top reasons for crontab scripts not being executed as expected. Write each reason in a separate answer.

Please include one reason per answer - details about why it's not executed - and fix(es) for that one reason.

Please write only cron-specific issues, e.g. commands that execute as expected from the shell but execute erroneously by cron.

share|improve this question
add comment

closed as too broad by jokerdino Oct 22 '13 at 17:34

There are either too many possible answers, or good answers would be too long for this format. Please add details to narrow the answer set or to isolate an issue that can be answered in a few paragraphs.If this question can be reworded to fit the rules in the help center, please edit the question.

32 Answers

up vote 109 down vote accepted
+50

Different environment

Cron passes a minimal set of environment variables to your jobs. To see the difference, add a dummy job like this:

* * * * * env > /tmp/env.output

Wait for /tmp/env.output to be created, then remove the job again. Now compare the contents of /tmp/env.output with the output of env run in your regular terminal.

A common "gotcha" here is the PATH environment variable being different. Maybe your cron script uses the command somecommand found in /opt/someApp/bin, which you've added to PATH in /etc/environment? cron does not read that file, so runnning somecommand from your script will fail when run with cron, but work when run in a terminal.

To get around that, just set your own PATH variable at the top of the script. E.g.

#!/bin/bash
PATH=/opt/someApp/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# rest of script follows

Some prefer to just use absolute paths to all the commands instead. I recommend against that. Consider what happens if you want to run your script on a different system, and on that system, the command is in /opt/someAppv2.2/bin instead. You'd have to go through the whole script replacing /opt/someApp/bin with /opt/someAppv2.2/bin instead of just doing a small edit on the first line of the script.

You can also set the PATH variable in the crontab file, which will apply to all cron jobs. E.g.

PATH=/opt/someApp/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

15 1 * * * backupscript --incremental /home /root
share|improve this answer
12  
+1 For adding PATH to the crontab file. –  Adam Matan Jan 27 '11 at 6:31
1  
+1 for env, I had completely forgotten about that command and thought PATH was working. It was actually sliiiightly different in my case. –  Izkata Jan 18 '12 at 15:16
1  
@pbr If such directories are left writable to others, the system is already compromized. –  geirha Apr 9 '12 at 8:23
1  
@pbr A sysadmin could unwittingly delete the root filesystem. You can't guard against sysadmins making silly mistakes. If you install a newer version of an interpreter that is not backwards compatible, I'd expect breakage regardless. The sane way to handle that is to install it as a different command. E.g. you have python version 2.x and install python 3, you install it as python3, not python. And as for /opt/someApp/bin, why on earth wouldn't it have sane permissions/ownership? any sane admin would ensure sane permissions/ownership on system files. –  geirha Apr 10 '12 at 6:36
show 8 more comments

My top gotcha: If you forget to add a newline at the end of the crontab file. In other words, the crontab file should end with an empty line.

Below is the relevant section in the man pages for this issue (man crontab then skip to the end):

   Although cron requires that each entry in a crontab end  in  a  newline
   character,  neither the crontab command nor the cron daemon will detect
   this error. Instead, the crontab will appear to load normally. However,
   the  command  will  never  run.  The best choice is to ensure that your
   crontab has a blank line at the end.

   4th Berkeley Distribution      29 December 1993               CRONTAB(1)
share|improve this answer
11  
this is such a showstopper, how come it hasn't been fixed in so many years of cron? –  barraponto Jan 27 '11 at 3:20
1  
Seems to be fixed in Vixie cron: man crontab on Ubuntu 10.10 says "cron requires that each entry in a crontab end in a newline character. If the last entry in a crontab is missing the newline, cron will consider the crontab (at least partially) broken and refuse to install it." (And the date at the end is 19 April 2010.) –  Marius Gedminas Feb 1 '11 at 22:58
3  
@barraponto This is actually a bug in new text editors. The "newline" character is supposed to be a line termination character, so the final line in a text file is supposed to end in a newline character that doesn't get shown in the editor. Vi and vim use the character correctly, and cron was built before the new editors started their odd behavior... Hence playing it save and including a blank line. –  Izkata Jan 18 '12 at 15:20
add comment

Cron daemon is not running. I really screwed up with this some months ago.

Type:

pgrep cron 

If you see no number, then cron is not running. sudo /etc/init.d/cron start can be used to start cron.

EDIT: Rather than invoking init scripts through /etc/init.d, use the service utility, e.g.

sudo service cron start
share|improve this answer
8  
Thanks for showing me pgrep. I kept doing ps -ef | grep foo –  ripper234 Mar 17 '11 at 17:01
1  
You could also use pidof cron which will omit results for other applications that also have the word 'cron', like crontab. –  Pithikos Mar 11 at 16:19
show 2 more comments

The script filenames in cron.d/, cron.daily/, cron.hourly/, etc., should not contain dot(.), otherwise run-parts will skip them.

See run-parts(8):

   If neither the --lsbsysinit option nor the --regex option is given then
   the names must consist entirely of upper and lower case  letters,  dig‐
   its, underscores, and hyphens.

   If  the  --lsbsysinit  option  is given, then the names must not end in
   .dpkg-old  or .dpkg-dist or .dpkg-new or .dpkg-tmp, and must belong  to
   one  or more of the following namespaces: the LANANA-assigned namespace
   (^[a-z0-9]+$);   the   LSB   hierarchical   and   reserved   namespaces
   (^_?([a-z0-9_.]+-)+[a-z0-9]+$);  and  the  Debian cron script namespace
   (^[a-zA-Z0-9_-]+$).

So, if you have a cron script backup.sh, analyze-logs.pl in cron.daily/ directory, you'd best to remove the extension names.

share|improve this answer
3  
+1 That's one sneaky bug. –  Adam Matan Mar 8 '12 at 6:49
4  
It's a feature not a bug - it keeps things like myscript.backup or myscript.original or myscript.rpm-new from running right beside myscript. –  pbr Apr 8 '12 at 22:45
show 1 more comment

In many environments cron executes commands using sh, while many people assume it will use bash.

Suggestions to test or fix this for a failing command:

  • Try running the command in sh to see if it works
  • Wrap the command in a bash subshell to make sure it gets run in bash:
    bash -c "mybashcommand"
  • Tell cron to run all commands in bash by setting the shell at the top of your crontab:
    SHELL=/bin/bash
  • If the command is a script, make sure the script contains a shebang:
    #!/bin/bash
share|improve this answer
add comment

Absolute path should be used for scripts:

For example, /bin/grep should be used instead of grep:

# m h  dom mon dow   command
0 0 *  *  *  /bin/grep ERROR /home/adam/run.log &> /tmp/errors

Instead of:

# m h  dom mon dow   command
0 0 *  *  *  grep ERROR /home/adam/run.log &> /tmp/errors

This is especially tricky, because the same command will work when executed from shell. The reason is that cron does not have the same PATH environment variable as the user.

share|improve this answer
1  
see geirha answer, you can (must) define cron's PATH –  barraponto Jan 27 '11 at 3:22
4  
Bzzt. you do NOT need to define the PATH - using absolute paths is the best practice here. "because an executable may be elsewhere on some other computer" doesn't trump "I want it to run exactly this program and not some other one someone put in the path in front of my original program" –  pbr Apr 8 '12 at 22:55
add comment

Permissions problems are quite common, I'm afraid.

Note that a common workaround is to execute everything using root's crontab, which sometimes is a Really Bad Idea. Setting proper permissions is definitely a largely overlooked issue.

share|improve this answer
add comment

I had some issues with the time zones. Cron was running with the fresh installation time zone. The solution was to restart cron:

sudo service cron restart
share|improve this answer
2  
Yes, after changing the timezone on a system, one must either restart every service that cares about what time it is, or reboot. I prefer the reboot, to be sure I've caught everything. –  pbr Apr 8 '12 at 22:48
add comment

If your crontab command has a % symbol in it, cron tries to interpret it. So if you were using any command with a % in it (such as a format specification to the date command) you will need to escape it.

That and other good gotchas here:
http://www.pantz.org/software/cron/croninfo.html

share|improve this answer
show 1 more comment

It is also possible that the user's password has expired. Even root's password can expire. You can tail -f /var/log/cron.log and you will see cron fail with password expired. You can set the password to never expire by doing this: passwd -x -1 <username>

In some systems (Debian, Ubuntu) logging for cron is not enabled by default. In /etc/rsyslog.conf or /etc/rsyslog.d/50-default.conf the line:

# cron.*                          /var/log/cron.log

should be edited (sudo nano /etc/rsyslog.conf) uncommented to:

cron.*                          /var/log/cron.log

After that, you need to restart rsyslog via

/etc/init.d/rsyslog restart

or

service rsyslog restart 

Source: Enable crontab logging in Debian Linux

share|improve this answer
add comment

Cron is calling a script which is not executable.

By running chmod +x /path/to/scrip the script becomes executable and should resolve this issue.

share|improve this answer
2  
That's not unique to cron, and easily traceable by simply trying to execute /path/to/script from the command line. –  Adam Matan Jan 27 '11 at 6:33
1  
If you're used to executing scripts with . scriptname or sh scriptname or bash scriptname, then this becomes a cron-specific problem. –  Eliah Kagan Nov 24 '11 at 23:09
add comment

Script is location-sensitive. This is related to always using absolute paths in a script, but not quite the same. Your cron job may need to cd to a specific directory before running, e.g. a rake task on a Rails application may need to be in the application root for Rake to find the correct task, not to mention the appropriate database configuration, etc.

So a crontab entry of

23 3 * * * /usr/bin/rake db:session_purge RAILS_ENV=production

would be better as

23 3 * * * cd /var/www/production/current && /usr/bin/rake db:session_purge RAILS_ENV=production

Or, to keep the crontab entry simpler and less brittle:

23 3 * * * /home/<user>/scripts/session-purge.sh

with the following code in /home/<user>/scripts/session-purge.sh:

cd /var/www/production/current
/usr/bin/rake db:session_purge RAILS_ENV=production
share|improve this answer
add comment

If your cronjob invokes GUI-apps, you need to tell them what DISPLAY they should use.

Example: Firefox launch with cron.

Your script should contain export DISPLAY=:0 somewhere.

share|improve this answer
add comment

Most frequent reason I have seen cron fail in an incorrectly stated schedule. It takes practice to specify a job scheduled for 11:15 pm as 30 23 * * * instead of * * 11 15 * or 11 15 * * *. Day of week for jobs after midnight also gets confused M-F is 2-6 after midnight not 1-5. Specific dates are usually a problem as we rarely use them * * 3 1 * is not March 3rd.

If your work with different platforms using unsupported options such as 2/3 in time specifications can also cause failures. This is a very useful option but not universally available. I have also run across issues will lists like 1-5 or 1,3,5.

Using unqualified paths have also caused problems. The default path is usually /bin:/usr/bin so only standard commands will run. These directories usually don't have the desired command. This also affects scripts using non standard commands. Other environment variables can also be missing.

Clobbering an existing crontab entirely has caused me problems. I now load from a file copy. This can be recovered from the existing crontab using crontab -l if it gets clobbered. I keep the copy of crontab in ~/bin. It is commented throughout and ends with the line # EOF. This is reloaded daily from a crontab entry like:

#!/usr/bin/crontab
# Reload this crontab
#
54 12    *   *   *   ${HOME}/bin/crontab

The reload command above relies on an executable crontab with a bang path running crontab. Some systems require the running crontab in the command and specifying the file. If the directory is network shared, then I often use crontab.$(hostname) as the name of the file. This will eventually correct cases where the wrong crontab is loaded on the wrong server.

Using the file provides a backup of what the crontab should be, and allows temporary edits (the only time I use crontab -e) to be backed out automatically. There are headers available which help with getting the scheduling parameters right. I have added them when inexperience users would be editing a crontab.

Rarely, I have run into commands that require user input. These fail under crontab, although some will work with input redirection.

share|improve this answer
2  
This covers three separate problems. Can they be split into separate answers? –  Eliah Kagan Nov 24 '11 at 23:07
show 1 more comment

cron script is invoking a command with --verbose option

I had a cron script fail on me because I was in autopilot while typing the script and I included the --verbose option:

#!/bin/bash
some commands
tar cvfz /my/archive/file.tar.gz /my/shared/directory
come more commands

The script ran fine when executing from shell, but failed when running from crontab because the verbose output goes to stdout when run from shell, but nowhere when run from crontab. Easy fix to remove the 'v':

#!/bin/bash
some commands
tar cfz /my/archive/file.tar.gz /my/shared/directory
some more commands
share|improve this answer
2  
Why is this causing a failure? Buffer issues? –  Adam Matan May 31 '12 at 6:38
show 1 more comment

Insecure cron table permission

A cron table is rejected if its permission is insecure

sudo service cron restart
grep -i cron /var/log/syslog|tail -2
2013-02-05T03:47:49.283841+01:00 ubuntu cron[49906]: (user) INSECURE MODE (mode 0600 expected) (crontabs/user)

The problem is solved with

# correct permission
sudo chmod 600 /var/spool/cron/crontabs/user
# signal crond to reload the file
sudo touch /var/spool/cron/crontabs
share|improve this answer
show 1 more comment

Crontab specs which worked in the past can break when moved from one crontab file to another. Sometimes the reason is that you've moved the spec from a system crontab file to a user crontab file or vice-versa.

The cron job specification format differs between users' crontab files (/var/spool/cron/username or /var/spool/cron/crontabs/username) and the system crontabs (/etc/crontab and the the files in /etc/cron.d).

The system crontabs have an extra field 'user' right before the command-to-run.

This will cause errors stating things like george; command not found when you move a command out of /etc/crontab or a file in /etc/cron.d into a user's crontab file.

Conversely, cron will deliver errors like /usr/bin/restartxyz is not a valid username or similar when the reverse occurs.

share|improve this answer
add comment

If you are accessing an account via SSH keys it is possible to login to the account but not notice that the password on the account is locked (e.g. due to expiring or invalid password attempts)

If the system is using PAM and the account is locked, this can stop its cronjob from running. (I've tested this on Solaris, but not on Ubuntu)

You may find messages like this in /var/adm/messages:

Oct 24 07:51:00 mybox cron[29024]: [ID 731128 auth.notice] pam_unix_account: cron attempting to validate locked account myuser from local host
Oct 24 07:52:00 mybox cron[29063]: [ID 731128 auth.notice] pam_unix_account: cron attempting to validate locked account myuser from local host
Oct 24 07:53:00 mybox cron[29098]: [ID 731128 auth.notice] pam_unix_account: cron attempting to validate locked account myuser from local host
Oct 24 07:54:00 mybox cron[29527]: [ID 731128 auth.notice] pam_unix_account: cron attempting to validate locked account myuser from local host

All you should need to do is run:

# passwd -u <USERNAME>

as root to unlock the account, and the crontab should work again.

share|improve this answer
add comment

Line written in a way crontab doesn't understand. It needs to be correctly written. Here's CrontabHowTo.

share|improve this answer
1  
How can this be debugged? –  Adam Matan Jan 27 '11 at 6:34
show 1 more comment

Cron daemon could be running, but not actually working. Try restarting cron:

sudo /etc/init.d/cron restart
share|improve this answer
2  
I've NEVER seen this case in production. Doesn't mean it hasn't happened - just that I've not seen it in the 30 years I've been using UNIX and Linux. Cron is insanely robust. –  pbr Apr 8 '12 at 22:42
add comment

Writing to cron via "crontab -e" with the username argument in a line. I've seen examples of users (or sysadmins) writing their shell scripts and not understanding why they don't automate. The "user" argument exists in /etc/crontab, but not the user-defined files. so, for example, your personal file would be something like:

# m h dom mon dow command

* * */2  *   *  /some/shell/script

whereas /etc/crontab would be:

# m h dom mon dow user   command

* * */2  *   *  jdoe   /some/shell/script

So, why would you do the latter? Well, depending on how you want to set your permissions, this can become very convoluted. I've written scripts to automate tasks for users who don't understand the intricacies, or don't want to bother with the drudgery. By setting permissions to --x------, I can make the script executable without them being able to read (and perhaps accidentally change) it. However, I might want to run this command with several others from one file (thus making it easier to maintain) but make sure file output is assigned the right owner. Doing so (at least in Ubuntu 10.10) breaks on both the inability to read the file as well as execute, plus the afore-mentioned issue with putting periods in /etc/crontab (which, funnily enough, causes no error when going through crontab -e).

As an example, I've seen instances of sudo crontab -e used to run a script with root permissions, with a corresponding chown username file_output in the shell script. Sloppy, but it works. IMHO, The more graceful option is to put it in /etc/crontab with username declared and proper permissions, so file_output goes to the right place and owner.

share|improve this answer
show 1 more comment

Building off what Aaron Peart mentioned about verbose mode, sometimes scripts not in verbose mode will initialize but not finish if the default behavior of an included command is to output a line or more to the screen once the proc starts. For example, I wrote a backup script for our intranet which used curl, a utility that downloads or uploads files to remote servers, and is quite handy if you can only access said remote files through HTTP. Using 'curl http://something.com/somefile.xls' was causing a script I wrote to hang and never complete because it spits out a newline followed by a progress line. I had to use the silent flag (-s) to tell it not to output any information, and write in my own code to handle if the file failed to download.

share|improve this answer
1  
For programs that don't have a silent mode, you can redirect their output to /dev/null. For example: some-command > /dev/null This will redirect only standard output, and not error output (which is usually what you want, since you want to be informed of errors). To redirect error output too, use some-command &> /dev/null. –  Eliah Kagan Jun 12 '12 at 20:22
show 1 more comment

I was writing an install shell script that creates another script to purge old transaction data from a database. As a part of the task it had to configure daily cron job to run at an arbitrary time, when the database load was low.

I created a file mycronjob with cron schedule, username & the command and copied it to the /etc/cron.d directory. My two gotchas:

  1. mycronjob file had to be owned by root to run
  2. I had to set permissions of the file to 644 - 664 would not run.
share|improve this answer
add comment

To make sure you don't have any syntax problems, run crontab /etc/crontab once. (Just to check for syntax problems)

For example sometimes, some invisible unicode characters (specially when you copy/paste from somewhere), are making syntax problem.

share|improve this answer
show 1 more comment

If you edited your crontab file using a windows editor (via samba or something) and it's replaced the newlines with \n\r or just \r, cron won't run.

Also, if you're using /etc/cron.d/* and one of those files has a \r in it, cron will move through the files and stop when it hits a bad file. Not sure if that's the problem?

Use:

od -c * | grep \r
share|improve this answer
show 1 more comment

Although you can define environment variables in your crontable, you're not in a shell script. So constructions like the following won't work:

SOME_DIR=/var/log
MY_LOG_FILE=${SOME_LOG}/some_file.log

BIN_DIR=/usr/local/bin
MY_EXE=${BIN_DIR}/some_executable_file

0 10 * * * ${MY_EXE} some_param >> ${MY_LOG_FILE}

This is because variables are not interpreted in the crontable: all values are taken litterally. And this is the same if you omit the brackets. So your commands won't run, and your log files won't be written...

Instead you must define all your environment variables straight:

SOME_DIR=/var/log
MY_LOG_FILE=/var/log/some_file.log

BIN_DIR=/usr/local/bin
MY_EXE=/usr/local/bin/some_executable_file

0 10 * * * ${MY_EXE} some_param >> ${MY_LOG_FILE}
share|improve this answer
add comment

I had a problem with a script that called grep with a regular expression. Some regexs worked when the script was called from crontab while others did not, e.g. [[:print:]] did not work. It turns out that the environment variable LANG has an impact on character sets like [a-z] or [[:print:]]. When I pasted my LANG environment variable into the top of my crontab, i.e. "LANG=en_GB.UTF-8", the grep regular expressions all worked fine when the script was called from crontab. HTH.

share|improve this answer
add comment

If crontab mentions something like run-parts /etc/cron.daily, then run-parts may be refusing to run your script. In my case the script in cron.daily did not start with #!/bin/sh.

I discovered this by putting my script in a directory by itself and running run-parts against that directory.

share|improve this answer
add comment

This happened to me recently: I had two lines that modified PATH, like this:

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/home/ernesto/bin

Then, later in the file:

PATH=$PATH:/some/other/path

as one would typically do in a shell-interpreted context. However, $PATH seems to not have been expanded, causing all my jobs to fail. The solution is to put everything on a single line.

Edit:

Since I didn't want to wait until the next normal iteration of anacron to verify my jobs worked correctly, I ran:

anacron -fnd "jobname"

Where "jobname" is the job identifier specified in anacrontab. This forces jobs to be run by the same anacron process, sequentially, and without delay.

share|improve this answer
add comment

If you have a command like this:

* * * * * /path/to/script >> /tmp/output

and it doesn't work and you can't see any output, it may not necessarily mean cron isn't working. The script could be broken and the output going to stderr which doesn't get passed to /tmp/output. Check this isn't the case, by capturing this output as well:

* * * * * /path/to/script >> /tmp/output 2&>1

to see if this helps you catch your issue.

share|improve this answer
add comment

Not the answer you're looking for? Browse other questions tagged or ask your own question.