Crontab Environment Variables

How environment variables work in crontab. Setting PATH, SHELL, MAILTO, CRON_TZ, and passing variables to cron job commands.

Crontab Environment Variables: PATH, SHELL, MAILTO, CRON_TZ

Cron jobs run in a stripped environment. The cron daemon does not read .bashrc, .bash_profile, or any login script; it starts each job with a minimal set of variables defined by the system.1 This stripped environment is the root cause of the most common cron failure: a command that works in a terminal because your PATH includes /usr/local/bin silently fails in cron because that directory is not on the cron PATH.

Four variables cover the majority of cron environment needs: SHELL (which interpreter runs your command), PATH (where to find executables), MAILTO (where output goes), and CRON_TZ (which timezone to use for schedule evaluation). All four are set at the top of the crontab file, above the job lines they apply to.

What to look for

  • /usr/bin:/bin

Opens the Cron Expression Parser & Next-Run Preview with this page's reference values shown at the top of the tool.

Open in the tool →

SHELL, PATH, and MAILTO

SHELL=/bin/bash changes the command interpreter from the default /bin/sh. This matters when your command uses bash-specific syntax like arrays, [[ ]], or process substitution.2 Choosing the wrong shell is one of the most common reasons a cron job fails silently, because the command parses without error but produces no output when the shell rejects the syntax.

Setting PATH and MAILTO

PATH=/usr/local/bin:/usr/bin:/bin:/home/user/.local/bin extends the default minimal path so that executables installed by package managers or language-specific toolchains are visible to cron jobs. [email protected] routes job output to an email address; set MAILTO="" to suppress output entirely when you redirect to a log file. Setting both variables at the top of every crontab is a low-cost habit that prevents the two most common classes of silent cron failure: command not found and lost output.

Keep the assignment block at the very top of the file rather than next to each job, because the variables only influence lines that appear below them and a job above the block silently runs without them. A single preamble also makes the environment auditable at a glance, which is useful when the same file is edited by several people over time. Treat the block as part of the job definition, not an optional comment, since removing it can reintroduce the exact failures it was added to prevent.

Passing variables to cron job commands

Variables set in the crontab file are available to all job lines below them. DB_HOST=prod.example.com above a job line makes $DB_HOST visible inside the command. Yet unlike shell scripts, the crontab file does not support command substitution ($(...)) in variable assignments: values must be literal strings. For complex environment setup, source a dedicated env file in the command itself3: bash -c 'source /etc/myapp/env && python3 /opt/myapp/run.py'. The distinction between crontab variable assignments and shell script variable assignments is subtle but important, because assuming command substitution works is how jobs end up with literal $(...) strings in their environment instead of resolved values.

Keeping secrets out of the crontab

Avoid placing API tokens or database passwords directly in the crontab, even though variable assignments would technically work. Prefer a restricted file, a secrets manager, or a service account with limited permissions, then source only what the command needs. This keeps the schedule readable and prevents credentials from spreading through backups and terminal history. A crontab file is often world-readable on the system and routinely backed up in plain text, which makes it one of the worst places to store sensitive values that rotate frequently.

CRON_TZ and HOME

CRON_TZ=America/Chicago sets the timezone used to evaluate the schedule for all subsequent job lines4. It does not affect the system clock. HOME=/home/user overrides the home directory (~) that cron uses when expanding paths. Setting HOME is rarely necessary but matters for commands that write to ~/.config or read ~/.ssh because cron may default to the root home directory depending on the daemon configuration.5

Both CRON_TZ and HOME are especially useful when a single server hosts jobs for users in different timezones or with different home directory structures. By grouping all timezone-dependent jobs under a single CRON_TZ assignment and all path-dependent jobs under a HOME assignment, you avoid scattering timezone and path overrides throughout every individual job line.

Sourcing a .env file in a cron job command

When your application uses a .env file for configuration, sourcing it inside the cron command is the cleanest way to pass environment variables without duplicating them in the crontab. The crontab file does not support command substitution in variable assignments, so the source must happen in the command itself: bash -c 'set -a; source /opt/myapp/.env; set +a; exec /opt/myapp/run.py'.

The set -a flag tells bash to export every variable it reads from the .env file, making each key-value pair available to the command that follows. After sourcing, set +a stops automatic export. For Python applications using a virtual environment, combine both steps: bash -c 'set -a; source /opt/myapp/.env; set +a; /opt/myapp/venv/bin/python3 /opt/myapp/run.py >> /var/log/myapp.log 2>&1'.

Debugging missing environment variables in cron jobs

In the cron job command, print the environment to a log file as the first operation: env >> /tmp/cron-env-debug.log 2>&1. Comparing this output against your terminal's env output reveals every missing variable immediately. This side-by-side comparison is the fastest way to identify which variables your command needs that cron is not providing, because the diff between the two logs shows exactly which keys are present in one but absent in the other.

Sharing variables between a systemd service and cron

For applications managed by systemd, environment variables set in the systemd service unit are not automatically visible to cron jobs because cron is a separate service with its own environment. The reliable way to share variables between a systemd service and its associated cron jobs is to write them to a shared file during service startup and then source that file in the cron command. This decouples the two environments without requiring duplicate variable declarations in both the systemd unit and the crontab. Keep the shared file readable only by the service account when it contains secrets. That limits exposure while preserving the convenience of one source of truth, and while you sort out the environment, you can still prove your cron job fires as planned instead of guessing whether the timing or the environment is the real problem.

When to use this

Set SHELL, PATH, and MAILTO in every new crontab to prevent silent failures. Set CRON_TZ when your server runs in UTC but your schedule should follow a local timezone. Pass application-specific variables as crontab variables rather than hardcoding them in the command string.

Examples

Crontab preamble for a Python application

A full preamble covering the four essential variables, placed at the top of the crontab file.

Passing a database URL to a cron job

Set the variable above the job line; it is available as $DATABASE_URL inside the command.

Sources
  1. 1.

    The Open Group, "crontab — schedule periodic background work," opengroup.org, 2018. https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html

  2. 2.

    Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html

  3. 3.

    The Open Group, "env — set the environment for command invocation," IEEE Std 1003.1-2024, pubs.opengroup.org, 2024. https://pubs.opengroup.org/onlinepubs/9799919799/utilities/env.html

  4. 4.

    IANA, "Time Zones," iana.org, accessed September 2026. https://www.iana.org/time-zones

  5. 5.

    Linux man7, "cron(8) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man8/cron.8.html

FAQ

Cron's default PATH is typically /usr/bin:/bin. Commands like python3, node, npm, or pip often live in /usr/local/bin or /home/user/.local/bin, not on the default PATH. When cron cannot find the executable, it fails silently unless output is captured. CapyToolkit's parser can help verify the timing while you keep the path fixes in the crontab. Always use absolute paths or set an explicit PATH variable.

Not directly in the variable assignment section, but you can do it in the command: bash -c 'set -a; source /path/to/.env; set +a; exec /opt/myapp/run.py'. This sources the env file and then runs the command with all exported variables visible.

Setting MAILTO="" tells the cron daemon to discard all output (stdout and stderr) from jobs below that line instead of mailing it. Use this in combination with explicit log file redirection so you can still inspect job output when needed.

Yes. Place a new PATH= assignment before any job that needs a different PATH. The variable assignment applies to all lines below it until overridden by another PATH= line.

It depends on how the cron daemon is started. On systemd systems, the cron daemon inherits the systemd service environment, which may be minimal or may include global environment variables set via Environment= in the cron service unit. User crontabs do not inherit the user's login session environment regardless.

Additional resources