Linux and Bash for Developers · Environment, Networking and Remote Access

It Works in My Terminal but Not in cron

Reproduce cron's minimal environment, and make scheduled scripts independent of your interactive shell setup.

Create a free account to save this page, take notes and track your progress. Sign in

Scenario

A backup script works every time you run it by hand. Scheduled in cron, it fails every night with command not found or uploads to the wrong place, and nothing in the script changed.

What you see in production

  • command not found for tools that are definitely installed.
  • Relative paths resolving somewhere unexpected.
  • Variables from your ~/.bashrc (API endpoints, credentials) are empty.

The naive code

#!/bin/bash
cd backups
mytool export --target "$BACKUP_BUCKET"     # mytool lives in ~/bin, BACKUP_BUCKET comes from ~/.bashrc

Reproduce it

env -i starts a command with an (almost) empty environment, much like cron:

The same script with cron's environment
mkdir -p "$HOME/bin" backups
printf '#!/usr/bin/env bash\necho "exporting to ${1:-?}"\n' > "$HOME/bin/mytool"
chmod +x "$HOME/bin/mytool"
export PATH="$HOME/bin:$PATH" BACKUP_BUCKET="s3://acme-backups"
cat > backup.sh <<'EOF'
#!/bin/bash
cd backups && mytool "$BACKUP_BUCKET"
EOF
chmod +x backup.sh
echo "interactive: $(./backup.sh)"
echo "cron-like:   $(env -i PATH=/usr/bin:/bin HOME="$HOME" ./backup.sh 2>&1 | sed 's/^.*: line [0-9]*: //')"

Output

interactive: exporting to s3://acme-backups
cron-like:   mytool: command not found

Run this on your computer — see “Get Started” for setup.

Root cause

cron runs jobs with a minimal environment: a short PATH (often /usr/bin:/bin), no ~/.bashrc or profile, /bin/sh as the default shell for the crontab line, and the home directory as the working directory. Anything the script relied on from your interactive session is missing.

The fix

Make the script self-contained: absolute paths, its own working directory, explicit configuration, strict mode, and logging:

A script that doesn't depend on your shell
mkdir -p "$HOME/bin" "$HOME/app/backups"
printf '#!/usr/bin/env bash\necho "exporting to ${1:?missing bucket}"\n' > "$HOME/bin/mytool"
chmod +x "$HOME/bin/mytool"
echo 'BACKUP_BUCKET=s3://acme-backups' > "$HOME/app/backup.env"
cat > "$HOME/app/backup.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
export PATH="$HOME/bin:/usr/local/bin:/usr/bin:/bin"
cd "$(dirname "$(realpath "$0")")"          # always run relative to the script
source ./backup.env                          # explicit configuration
cd backups
mytool "$BACKUP_BUCKET"
EOF
chmod +x "$HOME/app/backup.sh"
env -i HOME="$HOME" PATH=/usr/bin:/bin "$HOME/app/backup.sh"
echo '# crontab: 0 2 * * * $HOME/app/backup.sh >> $HOME/app/backup.log 2>&1'

Output

exporting to s3://acme-backups
# crontab: 0 2 * * * $HOME/app/backup.sh >> $HOME/app/backup.log 2>&1

Run this on your computer — see “Get Started” for setup.

  • Set PATH and other required variables inside the script (or at the top of the crontab).
  • cd to a known directory; never rely on the working directory.
  • Redirect output to a log and alert on failures; cron's email often goes nowhere.
  • Test with env -i before scheduling.

Trade-offs

  • Sourcing an env file keeps secrets out of crontabs but must be protected with file permissions (600).
  • systemd timers give better logs (journalctl) and environment control (Environment=, EnvironmentFile=) than cron.
  • Hard-coding paths makes scripts less portable; derive them from the script's location instead.

Detect it in production

  • Log every scheduled run with a timestamp and exit code; alert when a job hasn't succeeded on schedule.
  • Heartbeat monitoring (the job pings a URL on success) catches jobs that silently stop running.
  • Run env -i tests in CI for scheduled scripts.

Key points

  • cron has a minimal environment: short PATH, no profile, different working directory.
  • Make scheduled scripts self-contained and test them with env -i.
  • Log and monitor scheduled jobs, or use systemd timers.

Further reading: crontab(5) · systemd.timer · Bash tutorial: scheduling

Practice questions

Why does a command found in your terminal fail in cron with 'command not found'?

Answer

cron uses a minimal PATH and doesn't load your shell profile, so directories like ~/bin or /usr/local/bin may be missing.

How can you test a script with an environment similar to cron's?

Answer

Run it with env -i and only the variables cron provides, for example env -i HOME=$HOME PATH=/usr/bin:/bin ./script.sh.

What advantages do systemd timers have over cron?

Answer

Logs in the journal, explicit environment settings, dependencies, resource limits, and catching up missed runs with Persistent=true.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?