How to Test Cron Scripts in a Production-Like Environment: Debugging PATH and ENV Issues


3 views

One of the most frustrating experiences for Linux sysadmins is when a script runs perfectly from the command line but fails when executed through cron. The root cause typically lies in environmental differences between your interactive shell and cron's minimal environment.

Before diving deep, try these rapid verification techniques:

# Method 1: Simulate cron environment
env -i /bin/sh -c "your_script.sh"

# Method 2: Capture cron's actual environment
* * * * * env > /tmp/cronenv

Create this reusable testing script (cron_test.sh):

#!/bin/bash
# Set PATH to cron's default
PATH=/usr/bin:/bin
# Unset all other environment variables
unset $(env | cut -d= -f1)
# Source system environment (optional)
[ -f /etc/environment ] && . /etc/environment
# Now execute your script
exec "$@"

Usage example:

./cron_test.sh /path/to/your_script.sh arg1 arg2

When testing your cron jobs, pay special attention to:

# 1. Absolute paths
[[ $(grep -c '^/' your_script.sh) -eq $(wc -l < your_script.sh) ]] || echo "Warning: Relative paths found"

# 2. Missing environment variables
diff <(env | sort) <(crontab -l | grep -A10 "MAILTO" | grep "=" | sort)

For modern systems with systemd:

systemd-run --user --scope --property=Environment="PATH=/usr/bin:/bin" ./your_script.sh

Here's how to properly test a common cron task:

#!/bin/bash
# cron_test.sh /path/to/db_backup.sh

# First, test with clean environment
env -i /bin/sh -c "/path/to/db_backup.sh"

# Then verify with full logging
{
  echo "=== Environment ==="
  env
  echo "=== Execution ==="
  /path/to/db_backup.sh
} > /tmp/cron_test.log 2>&1

When developing scripts for cron execution, we frequently encounter the frustrating scenario where a script runs perfectly from the command line but fails under cron. The root causes typically involve:

# Example of a common failure scenario
#!/bin/bash
# Works interactively but fails in cron
some_command  # Fails because PATH differs

The most reliable way to test is to replicate cron's minimal environment. Here's how to do it properly:

env -i \
PATH=/usr/bin:/bin \
SHELL=/bin/sh \
HOME=/tmp \
/bin/sh -c "your_script.sh 2>&1 | tee /tmp/cron_test.log"

This creates an environment with:

  • Minimal PATH (only /usr/bin and /bin)
  • Basic shell configuration
  • Clean HOME directory
  • Output capture for debugging

For more complex scenarios, consider these approaches:

# Method 1: Full environment capture
(crontab -l | grep -v '^#' | grep -v '^$' | awk '{ print $6 }' | sort -u | \
while read line; do echo "Testing $line"; env -i $(cat /proc/$(pgrep cron)/environ | tr '\0' '\n' | grep -E '^PATH=|^SHELL=|^HOME=') $line; done)

# Method 2: Docker-based testing
docker run --rm -v $(pwd):/scripts alpine sh -c \
"apk add --no-cache bash && cd /scripts && env -i PATH=/usr/bin:/bin ./test.sh"

Common issues and their solutions:

# Problem: Missing environment variables
# Solution: Explicitly set them in script
#!/bin/bash
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export JAVA_HOME=/usr/lib/jvm/default-java

# Problem: Relative paths fail
# Solution: Use absolute paths or cd first
#!/bin/bash
cd "$(dirname "$0")" || exit 1
./subdir/script.sh

For teams managing multiple cron jobs, consider this testing approach:

#!/bin/bash
# cron_test_runner.sh

CRON_ENV="PATH=/usr/bin:/bin HOME=/tmp"
TEST_SCRIPT=$1
LOG_FILE="/tmp/cron_test_$(date +%s).log"

echo "Testing $TEST_SCRIPT with cron-like environment" > "$LOG_FILE"
env -i $CRON_ENV /bin/sh -c "$TEST_SCRIPT" >> "$LOG_FILE" 2>&1

if [ $? -eq 0 ]; then
    echo "SUCCESS - See details in $LOG_FILE"
else
    echo "FAILURE - Check $LOG_FILE for errors"
fi