365

I have a crontab running every hour. The user running it has environment variabless in the .bash_profile that work when the user runs the job from the terminal, however, obviously these don't get picked up by crontab when it runs.

I've tried setting them in .profile and .bashrc but they still don't seem to get picked up. Does anyone know where I can put environment vars that crontab can pick up?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
James
  • 15,085
  • 25
  • 83
  • 120

20 Answers20

308

You can define environment variables in the crontab itself when running crontab -e from the command line.

LANG=nb_NO.UTF-8
LC_ALL=nb_NO.UTF-8

# m h dom mon dow   command
* * * * *           sleep 5s && echo "yo"

This feature is only available to certain implementations of cron. Ubuntu and Debian currently use vixie-cron which allows these to be declared in the crontab file (also GNU mcron).

Archlinux and RedHat use cronie which does not allow environment variables to be declared and will throw syntax errors in the cron.log. Workaround can be done per-entry:

# m h dom mon dow   command
* * * * *           export LC_ALL=nb_NO.UTF-8; sleep 5s && echo "yo"
Nam G VU
  • 33,193
  • 69
  • 233
  • 372
carestad
  • 3,196
  • 2
  • 13
  • 10
  • 73
    Note that you can't use variable substitution as in shell, so a declaration like PATH=/usr/local/bin:$PATH is interpreted literally. – Zac Jul 05 '13 at 10:13
  • 12
    I was able to set the environment variables in the crontab itself under RedHat 4.4.7-3 and cronie-1.4.4-15.el6.x86_64 – Bruno Lange Jun 09 '15 at 13:24
  • 12
    You don't really need to export variables if the variables are only used within the command, just prepend them before your command. "* * * * * sleep 5s; LC_ALL=nb_NO.UTF-8 echo $LC_ALL" – vutran Nov 13 '15 at 08:10
  • 4
    [cronie does now copy all but a few environment variables from the crontab](https://github.com/cronie-crond/cronie/blob/master/src/security.c#L648) as @bruno said. – mtd Feb 02 '19 at 18:41
  • @BrunoLange could you pelase share how did you manage to set them up? – Newskooler Feb 13 '19 at 16:35
  • 1
    @Zac You can use variables [this way](https://serverfault.com/a/339822/407820) (option 3) – Pablo Bianchi Aug 12 '21 at 07:13
  • Just to clarify Zac's comment: The crontab environment setting lines will not substitute variables, but you can always substitute variables in the crontab command itself. Example: `* * * * * PATH=/usr/local/bin:$PATH ; echo $PATH >> /tmp/cron.out` will work correctly. – wisbucky Aug 28 '21 at 20:05
207

I got one more solution for this problem:

0 5 * * * . $HOME/.profile; /path/to/command/to/run

In this case it will pick all the environment variable defined in your $HOME/.profile file.

Of course $HOME is also not set, you have to replace it with the full path of your $HOME.

Nik
  • 2,885
  • 2
  • 25
  • 25
Vishal
  • 3,189
  • 1
  • 15
  • 18
  • 16
    this wasn't working for me until I realized I had left out that period preceding $HOME. What, exactly, does that period do? – flymike Mar 05 '13 at 16:48
  • 31
    The period is equivalent to the "source" command: http://tldp.org/LDP/abs/html/special-chars.html#DOTREF – Jeff W Mar 08 '13 at 21:55
  • 1
    @PeterLee does anything mentioned worked for you? I wrote this as the above mentioned solutions were not effective for me. If above mentioned solution does not work, I will have to do some research to find the reason. ;-) – Vishal Mar 22 '13 at 05:48
  • 3
    @Vishal Actually, it now works for me. I was trying to `source ~/.bashrc`, and it turns out that my `.bashrc` file is sort-of conflicting with the cron job. If I use a very simple `.env_setup_rc` file with only one line: `export MY_ENV_VAR=my_env_val`, it actually works. See my post: http://stackoverflow.com/questions/15557777/crontab-job-does-not-get-the-environment-variables-set-in-bashrc-file – Peter Lee Mar 22 '13 at 05:53
  • ${HOME} does get expanded all right on my Ubuntu ;) – Ivan Balashov Mar 25 '14 at 15:37
  • @JeffWehrwein But it looks like that the environmental variables are not exported as expected when I replace the period with 'source', even though the document says the period is equivalent to the "source" command. – yaobin Mar 03 '16 at 20:04
  • 1
    This is not a good practice at all. `$HOME/.profile` should be limited to interactive sessions (I mean, a real person logging in and doing things) only - an inadvertent edit of `.profile` shouldn't break production cron jobs! It is much better to have a common environment setup script that is called from `.profile` as well as a cron wrapper. See this answer for more info: [Effecting cron job management](http://stackoverflow.com/a/41756145/6862601) – codeforester Feb 14 '17 at 03:20
  • When I do this, I get an output from cron that says: `stdin: is not a tty`. Is there any way to avoid this? – flyingL123 Oct 16 '17 at 21:14
  • I like this type of solution but I second the use of only custom environment variables like @PeterLee. – Walf Oct 05 '18 at 08:43
  • replace with the full path of `$HOME` worked for me on Raspbian - thanks! – cloudxix Aug 09 '20 at 04:41
  • Be careful when using this solution - depending on the cron implementation, your `PATH` could be missing key dirs such as `/usr/sbin`, `/usr/local/bin`, and `/usr/local/sbin`! I would recommend writing a separate script that hard-sets `PATH` to `/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin`, then sources `.profile`. – cyqsimon Jun 02 '21 at 17:17
  • Some (as I was) might be tempted to use `source` instead of `.` - according to a comment [here](https://unix.stackexchange.com/questions/742066/cronjob-being-executed-but-doing-nothing) `source` may not be recognized, so just stick to `.` – bn_ln Apr 05 '23 at 22:05
126

Setting vars in /etc/environment also worked for me in Ubuntu. As of 12.04, variables in /etc/environment are loaded for cron.

Giacomo1968
  • 25,759
  • 11
  • 71
  • 103
Augusto Destrero
  • 4,095
  • 1
  • 23
  • 25
  • 25
    Best answer, just execute `env >> /etc/environment` and all the current env vars are now available in the CRON jobs. – Savageman Mar 16 '16 at 12:39
  • 11
    this works great for me. especially because i'm running into a Docker Container, so I don't care much about "system wide" implications. – Lucas Pottersky May 25 '16 at 13:31
  • 19
    @Savageman that's like killing flies with fusion bombs, also the stakes of unexpected behavior are extremely high doing so. – Fran Marzoa Aug 17 '16 at 09:41
  • Except the `PATH`. – astrojuanlu Aug 10 '19 at 19:35
  • 12
    Be CAREFUL: `env >> /etc/environment` will FAIL if there is a hash sign in one of the environment variables. I had the hardest time troubleshooting my application. It turned out to be a password containing '#' which was getting truncated at that step. – asachet Sep 18 '19 at 10:38
  • 3
    This should be the chosen answer. I don't know why people are complicating things with the other answers or with this stuff about env >> /etc/environment. Just blinking well edit etc/environment if you want these env vars to be universally available: my experiments seem to confirm that export statements for env vars in /etc/environment are available to crontab and also for users. THE PROBLEM: again from my experiments: it appears that these env vars are NOT EXPANDED within crontab itself! ... i.e. they are expanded only in the scripts called! – mike rodent Oct 17 '19 at 12:23
  • 3
    In my tests, variables in `/etc/environment` are NOT available in cron. Is there some setting that determines this? – iconoclast Feb 02 '20 at 04:21
  • Works on CentOS7 – Kiruahxh Feb 05 '20 at 16:39
  • 1
    Not an ideal solution for many reasons: 1. `>>` and the repetitive character of crontab will keep growing that file. 2. `/etc/environment` is world-readable by default, to serve other users, whereas root's environment may contain secret, for example in a Docker container, when `.env` is read by **Docker Compose**, and has secrets. 3. In my system it just won't work, as the user who executes crontab simply hasn't got the same env as root, although I would like root's. I chose as a solution to do `env >> .profile` as a manual one off, then prepended `. $HOME/.profile;` to the crontab line. – Fabien Haddadi Dec 14 '21 at 09:50
96

Have 'cron' run a shell script that sets the environment before running the command.

Always.

#   @(#)$Id: crontab,v 4.2 2007/09/17 02:41:00 jleffler Exp $
#   Crontab file for Home Directory for Jonathan Leffler (JL)
#-----------------------------------------------------------------------------
#Min     Hour    Day     Month   Weekday Command
#-----------------------------------------------------------------------------
0        *       *       *       *       /usr/bin/ksh /work1/jleffler/bin/Cron/hourly
1        1       *       *       *       /usr/bin/ksh /work1/jleffler/bin/Cron/daily
23       1       *       *       1-5     /usr/bin/ksh /work1/jleffler/bin/Cron/weekday
2        3       *       *       0       /usr/bin/ksh /work1/jleffler/bin/Cron/weekly
21       3       1       *       *       /usr/bin/ksh /work1/jleffler/bin/Cron/monthly

The scripts in ~/bin/Cron are all links to a single script, 'runcron', which looks like:

:       "$Id: runcron.sh,v 2.1 2001/02/27 00:53:22 jleffler Exp $"
#
#       Commands to be performed by Cron (no debugging options)

#       Set environment -- not done by cron (usually switches HOME)
. $HOME/.cronfile

base=`basename $0`
cmd=${REAL_HOME:-/real/home}/bin/$base

if [ ! -x $cmd ]
then cmd=${HOME}/bin/$base
fi

exec $cmd ${@:+"$@"}

(Written using an older coding standard - nowadays, I'd use a shebang '#!' at the start.)

The '~/.cronfile' is a variation on my profile for use by cron - rigorously non-interactive and no echoing for the sake of being noisy. You could arrange to execute the .profile and so on instead. (The REAL_HOME stuff is an artefact of my environment - you can pretend it is the same as $HOME.)

So, this code reads the appropriate environment and then executes the non-Cron version of the command from my home directory. So, for example, my 'weekday' command looks like:

:       "@(#)$Id: weekday.sh,v 1.10 2007/09/17 02:42:03 jleffler Exp $"
#
#       Commands to be done each weekday

# Update ICSCOPE
n.updics

The 'daily' command is simpler:

:       "@(#)$Id: daily.sh,v 1.5 1997/06/02 22:04:21 johnl Exp $"
#
#       Commands to be done daily

# Nothing -- most things are done on weekdays only

exit 0
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
91

If you start the scripts you are executing through cron with:

#!/bin/bash -l

They should pick up your ~/.bash_profile environment variables

Giacomo1968
  • 25,759
  • 11
  • 71
  • 103
breizhmg
  • 1,081
  • 8
  • 7
  • 9
    This answer should get more upvotes and simply be the selected answer: Very simple and elegant and avoids countless kludges that would require jumping all over the system. – Giacomo1968 Dec 14 '18 at 04:42
  • 1
    In the script itself. Which you then run with cron normally. – breizhmg Apr 25 '20 at 08:17
  • 1
    @Jim see [this example](https://stackoverflow.com/q/61438824/287948), classic executable file (chmod 777) use `#!/bin/bash`. The magic here is to add `-l` – Peter Krauss May 06 '20 at 02:24
  • 2
    Explanation from the man: -l Make bash act as if it had been invoked as a login shell (see INVOCATION below). – Ewoks Nov 19 '20 at 12:10
  • This worked like a charm, it was very basic, i forgot about this... my teacher emphasised on this several times. – Sriram Arvind Lakshmanakumar Apr 14 '21 at 02:20
  • 1
    You can also call the script in cron with the `-l` option: `0 * * * * bash -l hourly.sh`, which I personally prefer. (also see https://unix.stackexchange.com/a/422505/436224) – bersling Jun 18 '21 at 15:47
  • too bad there is not a `--login-shell` option instead of just `-l` for those of us who dont like memorizing command-line switches. excellent find though, thanks for sharing. – edwardsmarkf Aug 23 '21 at 19:30
  • 2
    @PeterKrauss Whatever you are hoping to accomplish, **`chmod 777` is *wrong* and *dangerous.*** You will want to revert to sane permissions ASAP (for your use case, probably `chmod 755`) and if you have had world writable system files on a public-facing system, at the very least investigate whether it could have been breached and used as a pivot point for breaking into your organization’s network. – tripleee Jun 22 '22 at 11:28
  • @Seamus usually root’s home dir is `/root`, not `/home/root` – törzsmókus Jul 04 '22 at 11:35
  • @Seamus `~` stands for `$HOME`, which is `/root` for root. so this solution is supposed to pick up the environment from `/root/.bash_profile` when using with root’s crontab. – törzsmókus Jul 06 '22 at 08:12
  • @törzsmókus: if you say so; I've not seen it used that way w/ the root account. But I'll say you're right if it will make you stop commenting? –  Jul 07 '22 at 00:25
  • In my opinion this is the solution, all I did was prepend ``/bin/bash`` to the cronjobs I currently had and then thay had variable data present within the logs. – oemb1905 Nov 24 '22 at 20:32
30

Expanding on @carestad example, which I find easier, is to run the script with cron and have the environment in the script.

In crontab -e file:

SHELL=/bin/bash

*/1 * * * * $HOME/cron_job.sh

In cron_job.sh file:

#!/bin/bash
source $HOME/.bash_profile
some_other_cmd

Any command after the source of .bash_profile will have your environment as if you logged in.

Robert Brisita
  • 5,461
  • 3
  • 36
  • 35
  • What is the purpose of `SHELL=/bin/bash` line? Wouldn't `#!/bin/bash` by itself be enough? – icemtel Oct 15 '22 at 07:40
  • 3
    crontab is not a script file but a program that has it's own format. To keep things consistent with possible commands, all shells are the same from start to finish. Please see: https://www.man7.org/linux/man-pages/man5/crontab.5.html and search for `SHELL` – Robert Brisita Oct 20 '22 at 18:59
20

Whatever you set in crontab will be available in the cronjobs, both directly and using the variables in the scripts.

Use them in the definition of the cronjob

You can configure crontab so that it sets variables that then the can cronjob use:

$ crontab -l
myvar="hi man"
* * * * * echo "$myvar. date is $(date)" >> /tmp/hello

Now the file /tmp/hello shows things like:

$ cat /tmp/hello 
hi man. date is Thu May 12 12:10:01 CEST 2016
hi man. date is Thu May 12 12:11:01 CEST 2016

Use them in the script run by cronjob

You can configure crontab so that it sets variables that then the scripts can use:

$ crontab -l
myvar="hi man"
* * * * * /bin/bash /tmp/myscript.sh

And say script /tmp/myscript.sh is like this:

echo "Now is $(date). myvar=$myvar" >> /tmp/myoutput.res

It generates a file /tmp/myoutput.res showing:

$ cat /tmp/myoutput.res
Now is Thu May 12 12:07:01 CEST 2016. myvar=hi man
Now is Thu May 12 12:08:01 CEST 2016. myvar=hi man
...
fedorqui
  • 275,237
  • 103
  • 548
  • 598
17

For me I had to set the environment variable for a php application. I resolved it by adding the following code to my crontab.

$ sudo  crontab -e

crontab:

ENVIRONMENT_VAR=production

* * * * * /home/deploy/my_app/cron/cron.doSomethingWonderful.php

and inside doSomethingWonderful.php I could get the environment value with:

<?php     
echo $_SERVER['ENVIRONMENT_VAR']; # => "production"

I hope this helps!

wonsuc
  • 3,498
  • 1
  • 27
  • 30
karlingen
  • 13,800
  • 5
  • 43
  • 74
13

Instead of

0  *  *  *  *  sh /my/script.sh

Use bash -l -c

0  *  *  *  *  bash -l -c 'sh /my/script.sh'
Ilya Kharlamov
  • 3,698
  • 1
  • 31
  • 33
  • 1
    Why do this instead of just having the Bash declaration at the top of the file have the `-l` like this: `#!/bin/bash -l`? [This other answer](https://stackoverflow.com/a/51591762/117259) is simple and elegant. – Giacomo1968 Dec 14 '18 at 04:41
  • 4
    What if I need to run a perl/python/ruby script not bash? I can’t add #!/bin/bash -l to the top of a python script. – Ilya Kharlamov Dec 14 '18 at 14:58
  • “What if I need to run a perl/python/ruby script not bash?” Fair enough. But in my mind you could write a simple Bash script wrapper that then calls the Python script. I do a similar thing for PHP scripts. The reason being is process locking is so much better and reliable in Bash, but Bash scripting is still a headache. So I wrote stuff in PHP for the complex stuff and let Bash handle the rest. – Giacomo1968 Dec 14 '18 at 20:35
11

You can also prepend your command with env to inject Environment variables like so:

0 * * * *   env VARIABLE=VALUE /usr/bin/mycommand
Hussam
  • 488
  • 3
  • 15
7

Expanding on @Robert Brisita has just expand , also if you don't want to set up all the variables of the profile in the script, you can select the variables to export on the top of the script

In crontab -e file:

SHELL=/bin/bash

*/1 * * * * /Path/to/script/script.sh

In script.sh

#!/bin/bash
export JAVA_HOME=/path/to/jdk

some-other-command
Marcos Pousada
  • 159
  • 2
  • 9
7

I'm using Oh-my-zsh in my macbook so I've tried many things to get the crontab task runs but finally, my solution was prepending the .zshrc before the command to run.

*/30 * * * * . $HOME/.zshrc; node /path/for/my_script.js

This task runs every 30 minutes and uses .zshrc profile to execute my node command.

Don't forget to use the dot before the $HOME var.

Robert Hovhannisyan
  • 2,938
  • 6
  • 21
  • 43
Stiakov
  • 81
  • 1
  • 4
  • Is your crontab's `$SHELL` already `zsh`? Mine is `/bin/sh` though I use zsh interactively. – Sridhar Sarnobat Sep 14 '21 at 02:47
  • Or, conversely, does your `.zshrc` only contain expressions which are also valid `sh` syntax? For quick and dirty fixes, sourcing a file which is used for interactive shells in a noninteractive one like your `crontab` is probably fine if you know what you are doing, but error-prone and hairy to troubleshoot if you don't. – tripleee Jun 22 '22 at 11:26
3

I tried most of the provided solutions, but nothing worked at first. It turns out, though, that it wasn't the solutions that failed to work. Apparently, my ~/.bashrc file starts with the following block of code:

case $- in
    *i*) ;;
    *) return;;
esac

This basically is a case statement that checks the current set of options in the current shell to determine that the shell is running interactively. If the shell happens to be running interactively, then it moves on to sourcing the ~/.bashrc file. However, in a shell invoked by cron, the $- variable doesn't contain the i value which indicates interactivity. Therefore, the ~/.bashrc file never gets sourced fully. As a result, the environment variables never got set. If this happens to be your issue, feel free to comment out the block of code as follows and try again:

# case $- in
#     *i*) ;;
#     *) return;;
# esac

I hope this turns out useful

Abdou
  • 12,931
  • 4
  • 39
  • 42
  • Should be a comment instead of an answer, but I upvote it anyway – Ryan Chen May 27 '22 at 01:29
  • The reason this code is there is to prevent you from abusing a file whose purpose is to configure interactive shell sessions. Several answers here misleadingly suggest this, but it is not best practice. A much better solution is to separate your noninteractive settings into a separate file, and then source only that from your `crontab`, as well as from your various interactive shell startup files. – tripleee Jun 22 '22 at 11:30
3

Unfortunately, crontabs have a very limited environment variables scope, thus you need to export them every time the corntab runs.

An easy approach would be the following example, suppose you've your env vars in a file called env, then:

* * * * * . ./env && /path/to_your/command

this part . ./env will export them and then they're used within the same scope of your command

Jamal Alkelani
  • 606
  • 7
  • 19
2

Another way - inspired by this this answer - to "inject" variables is the following (fcron example):

%daily 00 12 \
    set -a; \
    . /path/to/file/containing/vars; \
    set +a; \
    /path/to/script/using/vars

From help set:

-a Mark variables which are modified or created for export.

Using + rather than - causes these flags to be turned off.

So everything in between set - and set + gets exported to env and is then available for other scripts, etc. Without using set the variables get sourced but live in set only.

Aside from that it's also useful to pass variables when a program requires a non-root account to run but you'd need some variables inside that other user's environment. Below is an example passing in nullmailer vars to format the e-mail header:

su -s /bin/bash -c "set -a; \
                    . /path/to/nullmailer-vars; \
                    set +a; \
                    /usr/sbin/logcheck" logcheck
Community
  • 1
  • 1
Saucier
  • 4,200
  • 1
  • 25
  • 46
2

All the above solutions work fine.

It will create issues when there are any special characters in your environment variable.

I have found the solution:

eval $(printenv | awk -F= '{print "export " "\""$1"\"""=""\""$2"\"" }' >> /etc/profile)

Amul
  • 149
  • 1
  • 3
  • This worked for me in a Docker/Kubernetes set up. Put this command in a bash script and run this in the entry command, then prepend each crontab entry with: `. /etc/profile;` (eg. `* * * * * . /etc/profile; echo "$environmental_variable1"`) – Rishi Latchmepersad Jul 07 '22 at 01:15
1

For me I had to specify path in my NodeJS file.

// did not work!!!!!
require('dotenv').config()

instead

// DID WORK!!
require('dotenv').config({ path: '/full/custom/path/to/your/.env' })
Michael Nelles
  • 5,426
  • 8
  • 41
  • 57
1

I found this issue while looking at a similar problem that matched the title, but I am stuck with the environment file syntax that systemd or docker use:

FOO=bar
BAZ=qux

This won't work for Vishal's excellent answer because they aren't bash scripts (note the lack of export).
The solution I've used is to read each line into xargs and export them before running the command:

0 5 * * * export $(xargs < $HOME/.env); /path/to/command/to/run
GammaGames
  • 1,617
  • 1
  • 17
  • 32
0
  • Set Globally env
sudo sh -c "echo MY_GLOBAL_ENV_TO_MY_CURRENT_DIR=$(pwd)" >> /etc/environment"
  • Add scheduled job to start a script
crontab -e

  */5 * * * * sh -c "$MY_GLOBAL_ENV_TO_MY_CURRENT_DIR/start.sh"

=)

Dharman
  • 30,962
  • 25
  • 85
  • 135
oviniciusfeitosa
  • 915
  • 1
  • 11
  • 12
-1

what worked for me (debian based):

  1. create a file with all the needed env var :

    #!/bin/bash
    env | grep VAR1= > /etc/environment
    env | grep VAR2= >> /etc/environment
    env | grep VAR3= >> /etc/environment

  2. then build the crontab content, by calling the env file before calling the script that needs it, therefore start the cron service

    (crontab -l ; echo '* * * * * . /etc/environment; /usr/local/bin/python /mycode.py >> /var/log/cron-1.log 2>&1') | crontab
    service cron start

nb : for python use case, be sure to call the whole python path, else wrong python could be invocated, generating non-sense syntax error

  • You can easily use a single `grep`; viz. `env | grep -E `^(VAR1|VAR2|VAR3)=' >>/etc/environment` (for this simple task `grep '^VAR[123]='` would suffice, but real-world variables are rarely named so neatly). – tripleee Jun 22 '22 at 11:22
  • Perhaps emphasize that `/etc/environment` is one of several possible configuration files, and not necessarily portable to other Linuxes, let alone proper Unixes. – tripleee Jun 22 '22 at 11:23