35

Cron doesn't use the path of the user whose crontab it is and, instead, has its own. It can easily be changed by adding PATH=/foo/bar at the beginning of the crontab, and the classic workaround is to always use absolute paths to commands run by cron, but where is cron's default PATH defined?

I created a crontab with the following contents on my Arch system (cronie 1.5.1-1) and also tested on an Ubuntu 16.04.3 LTS box with the same results:

$ crontab -l
* * * * * echo "$PATH" > /home/terdon/fff

That printed:

$ cat fff
/usr/bin:/bin

But why? The default system-wide path is set in /etc/profile, but that includes other directories:

$ grep PATH= /etc/profile
PATH="/usr/local/sbin:/usr/local/bin:/usr/bin"

There is nothing else relevant in /etc/environment or /etc/profile.d, the other files I thought might possibly be read by cron:

$ grep PATH= /etc/profile.d/* /etc/environment
/etc/profile.d/jre.sh:export PATH=${PATH}:/usr/lib/jvm/default/bin
/etc/profile.d/mozilla-common.sh:export MOZ_PLUGIN_PATH="/usr/lib/mozilla/plugins"
/etc/profile.d/perlbin.sh:[ -d /usr/bin/site_perl ] && PATH=$PATH:/usr/bin/site_perl
/etc/profile.d/perlbin.sh:[ -d /usr/lib/perl5/site_perl/bin ] && PATH=$PATH:/usr/lib/perl5/site_perl/bin
/etc/profile.d/perlbin.sh:[ -d /usr/bin/vendor_perl ] && PATH=$PATH:/usr/bin/vendor_perl
/etc/profile.d/perlbin.sh:[ -d /usr/lib/perl5/vendor_perl/bin ] && PATH=$PATH:/usr/lib/perl5/vendor_perl/bin
/etc/profile.d/perlbin.sh:[ -d /usr/bin/core_perl ] && PATH=$PATH:/usr/bin/core_perl

There is also nothing relevant in any of the files in /etc/skel, unsurprisingly, nor is it being set in any /etc/cron* file:

$ grep PATH /etc/cron* /etc/cron*/*
grep: /etc/cron.d: Is a directory
grep: /etc/cron.daily: Is a directory
grep: /etc/cron.hourly: Is a directory
grep: /etc/cron.monthly: Is a directory
grep: /etc/cron.weekly: Is a directory
/etc/cron.d/0hourly:PATH=/sbin:/bin:/usr/sbin:/usr/bin

So, where is cron's default PATH for user crontabs being set? Is it hardcoded in cron itself? Doesn't it read some sort of configuration file for this?

  • 3
    There is no reason for cron to look at /etc/profile, or care about any particular shell. A better question is why doesn't cron read PATH from login.defs (on Linux) or login.conf (on *BSD). I suppose it's ultimately an implementation detail. – Satō Katsura Oct 31 '17 at 13:02
  • @SatōKatsura sure, I only mentioned /etc/profile because it uses the same syntax (var=value) as cron itself, so it would be easy enough to do and /etc/profile is, to my knowledge, very widespread. What surprised me is that I couldn't find it being set anywhere so it looked like it was hard coded. As is indeed the case, as Stephen explained below. – terdon Oct 31 '17 at 13:05
  • People using zsh as their interactive shell don't care about /etc/profile (which is specific to bash) – Basile Starynkevitch Oct 31 '17 at 13:16
  • 2
    @BasileStarynkevitch no, it is not specific to bash at all! Quite the contrary! While there are some shells that don't read it (the c-shell family AFAIK), zsh is not one of them. See the zsh manpage, if you don't believe me. In any case, interactive shells are irrelevant since the various profile files are only read by login shells, anyway. These may, or may not be interactive. – terdon Oct 31 '17 at 13:20
  • 1
    Sometimes running strings against a program can help find these hard-coded values, too. – jrw32982 Nov 2 '17 at 2:34
47

It’s hard-coded in the source code (that link points to the current Debian cron — given the variety of cron implementations, it’s hard to choose one, but other implementations are likely similar):

#ifndef _PATH_DEFPATH
# define _PATH_DEFPATH "/usr/bin:/bin"
#endif

#ifndef _PATH_DEFPATH_ROOT
# define _PATH_DEFPATH_ROOT "/usr/sbin:/usr/bin:/sbin:/bin"
#endif

cron doesn’t read default paths from a configuration file; I imagine the reasoning there is that it supports specifying paths already using PATH= in any cronjob, so there’s no need to be able to specify a default elsewhere. (The hard-coded default is used if nothing else specified a path in a job entry.)

  • Note that, despite the existence of the _PATH_DEFPATH_ROOT define, I confirmed (using a cron job of echo $PATH > /testfile) after editing root's crontab using crontab -e on Debian Stretch that root's crontab also uses _PATH_DEFPATH, i.e. "/usr/bin:/bin", not _PATH_DEFPATH_ROOT . This is also confirmed by the second source code link in this answer (in which _PATH_DEFPATH_ROOT is not used). It's not clear to me whether this orphaned define is a bug. – njahnke May 14 '18 at 19:18
8

Adding to Stephen Kitt's answer, there is a configuration file that sets PATH for cron on Ubuntu, and cron ignores that PATH to use the hard-coded default (or PATHs set in the crontabs themselves). The file is /etc/environment. Note cron's PAM configuration:

$ cat /etc/pam.d/cron
...   
# Read environment variables from pam_env's default files, /etc/environment
# and /etc/security/pam_env.conf.
session       required   pam_env.so

# In addition, read system locale information
session       required   pam_env.so envfile=/etc/default/locale
...

This is easily verifiable. Add a variable to /etc/environment, say foo=bar, run env > /tmp/foo as a cronjob and watch as foo=bar shows up in the output.


But why? The default system-wide path is set in /etc/profile, but that includes other directories:

$ grep PATH= /etc/profile
PATH="/usr/local/sbin:/usr/local/bin:/usr/bin"

That's true in Arch Linux, but in Ubuntu, the base PATH is set in /etc/environment. Files in /etc/profile.d tack on to an existing PATH, and you can append to it in ~/.pam_environment. I have a bug filed about Arch's behaviour.

Unfortunately, /etc/pam.d/cron does not include reading from ~/.pam_environment. Weirdly, /etc/pam.d/atd does include that file:

$ cat /etc/pam.d/atd
#
# The PAM configuration file for the at daemon
#

@include common-auth
@include common-account
session    required   pam_loginuid.so
@include common-session-noninteractive
session    required   pam_limits.so
session    required   pam_env.so user_readenv=1

... but commands run via at apparently inherit the environment available when creating the at job (for example, env -i /usr/bin/at ... seems to run jobs with a very clean environment).

Amending /etc/pam.d/cron to have user_readenv=1 seems to cause no problems, and variables in ~/.pam_environment started showing up fine (except for PATH, of course).


All told, setting environment variables for cron seems to be a messy business. The best place seems to be in the job specification itself, if only because you don't know which inherited environment variables cron might decide ignore (without reading the source).

  • Regarding at jobs, if you dump an at job you’ll see it explicitly sets the environment up to match the environment when the job was created. – Stephen Kitt Nov 1 '17 at 18:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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