Thornton 2 dot Com
1K38A

Simulating the Cron Environment To Troubleshoot Cronjobs

Getting a shellscript or command to work properly in a shell is one thing, but it turns out getting it to work properly in the cron environment is something else. Setting up cron is fairly straightforward, whether you're used to Unix or Linux, but troubleshooting cronjobs becomes very difficult unless you simulate the cron environment.

First, you have to get a dump of the environment variables present in the cron environment. To do that, create a new cronjob to execute within the next minute or two. (The time I give here is just an example.)


36 15 * * * /usr/bin/env > ~/cron-env

After the cron job executes and you have the ~/cron-env file, you can simulate the environment by using env to start a new shell in it.


env -i `cat ~/cron-env` /bin/sh
. ~/cron-env

Tip: One common problem is that the $PATH variable is preserved, even though it seems it shouldn't be. This can lead to scripts working fine while troubleshooting yet still failing in cron, or vice versa. Once in this cron environment, sourcing the environment variables (with the "." builtin command) overwrites the current $PATH as well as other variables (including $PWD).

Now in the cron environment, you can run the cron command or shellscript giving you trouble and see exactly what is (or more likely isn't) happpening. Once you figure out your next step, exit the environment, make the changes you need, and start over by editing the time the cronjob dumping the cron environment runs.

Once you've finished troubleshooting, delete the cronjob you created at the beginning of troubleshooting.

Tip: Save a dump of your crontab before any editing. That way, if you want to start again from your old configuration or if you accidentally nuke it, you have a crontab to reinstall. I like to use something in the form of "archive/crontab-YYYYMMDD.txt" where "YYYYMMDD" is the current date. The command I used on the day I wrote this page, for example, was:


crontab -l > ~/archive/crontab-20190810.txt

Also save a dump of your crontab after you've finished editing, again just in case something happens later.

Special note about $PATH in crontabs

There are about five ways to set a custom $PATH for shellscripts run from cron:

  1. The most straightforward way is to add a PATH= line to the crontab head. For me, that would be:

    PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/home/arielmt/bin

    Which can be rather long. Note that you can't split this into "PATH=$PATH:/more/dirs" lines because vixie-cron doesn't expand variables into values this way.

  2. Edit your shellscripts, specifying the full path for every command or script not within the default cron $PATH directories. For me, this means prefixing every call to another shellscript with "$HOME/bin/", forcing those related shellscripts to live in that directory instead of possibly in other places in the future.

  3. Edit your shellscripts, adding a custom PATH= line near the top of the script. For me, this means adding "PATH=$PATH:$HOME/bin", again forcing related scripts to live in one directory, but at least that directory can move, and all I'd have to edit is the "PATH=" line later on.

  4. Prefix shellscripts in crontab with custom PATH= values. For example, I had the following line successfully running a shellscript calling a perl script in my home-bin directory:

    59 * * * * PATH=$PATH:$HOME/bin /bin/sh $HOME/bin/update-moonclock.sh
    

    (I also have SHELL=/bin/sh at the very head of my crontab.)

  5. If your ~/.shrc sets the $PATH and other variables your shellscripts need while running from cron (without doing other things that shouldn't be done in cron), add lines setting $SHELL and $ENV at the head of the crontab, and source $ENV as part of the cronjob entry, like so:

    SHELL=/bin/sh
    ENV=/home/arielmt/.shrc
    #
    59 * * * * . $ENV; /bin/sh $HOME/bin/update-moonclock.sh
    

    Note that you can't use "ENV=~/.shrc" or "ENV=$HOME/.shrc" like you can in a shell. You must spell out the full pathname instead.