Back to Blog
Tutorial

How to Set and Use Environment Variables in Cron Jobs Correctly

Your script works in the terminal but fails in cron with 'command not found' or missing variables? Learn why cron's minimal environment breaks your scripts and exactly how to fix it.

12 min read
By Cron Generator Team

Your script runs perfectly when you execute it from the terminal. Every command works. Every variable is set. You've tested it a dozen times.

Then you add it to cron. It fails immediately.

command not found
undefined variable
configuration file not found
permission denied (but the permissions are correct)

What changed? The script is the same. The permissions are the same. The only difference is who's running it: you versus cron.

The problem is environment variables—or rather, the complete lack of them in cron's execution environment.

This is one of the most frustrating and non-obvious problems in cron. Your terminal session has dozens of environment variables set automatically (PATH, HOME, USER, and application-specific ones). Cron has almost none of them.

This guide explains exactly why this happens and shows you three robust solutions with clear guidance on which to use when.

The Problem: Cron's Minimal Environment

First, understand just how bare-bones cron's environment really is.

Your Interactive Shell's Environment

When you log in and open a terminal, your shell (bash, zsh, etc.) sets up a rich environment by running several configuration files:

Login sequence:

  1. /etc/profile (system-wide)
  2. ~/.bash_profile or ~/.profile (user-specific)
  3. ~/.bashrc (for interactive shells)
  4. Any custom RC files

These files set:

  • PATH - Where to find commands
  • HOME - Your home directory
  • USER - Your username
  • Application paths (NVM_DIR, PYENV_ROOT, RBENV_ROOT)
  • API keys and credentials
  • Database connection strings
  • Custom application variables
  • Aliases and functions

See your environment:

env

Example output (truncated):

PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/user/.local/bin:/home/user/.nvm/versions/node/v18.0.0/bin
HOME=/home/user
USER=john
SHELL=/bin/bash
LANG=en_US.UTF-8
NVM_DIR=/home/user/.nvm
PYENV_ROOT=/home/user/.pyenv
DATABASE_URL=postgresql://localhost/myapp
API_KEY=sk_live_abc123def456
EDITOR=vim
... (50+ more variables)

You have 50-100+ environment variables set automatically.

Cron's Environment

When cron runs a job, it starts with a nearly empty environment.

See cron's environment:

# Add this temporary cron job
* * * * * env > /tmp/cron-env.txt

Wait one minute, then check:

cat /tmp/cron-env.txt

Example output:

HOME=/home/user
LOGNAME=user
PATH=/usr/bin:/bin
SHELL=/bin/sh

That's it. Four variables.

Notice what's missing:

  • ❌ No extended PATH (no /usr/local/bin, no ~/.local/bin)
  • ❌ No language version managers (NVM_DIR, PYENV_ROOT)
  • ❌ No application config (DATABASE_URL, API_KEY)
  • ❌ No custom variables from .bashrc
  • ❌ No aliases or functions
  • ❌ Different SHELL (/bin/sh instead of /bin/bash)

Why This Breaks Your Scripts

Example script that works in terminal:

#!/bin/bash
# File: /home/user/scripts/deploy.sh

# Uses node (found via NVM in PATH)
node --version

# Uses custom variable
echo "Deploying to: $DEPLOY_ENV"

# Uses database URL
psql "$DATABASE_URL" -c "SELECT version();"

# Uses AWS credentials
aws s3 ls s3://my-bucket/

Running in terminal:

./deploy.sh

Output:

v18.0.0
Deploying to: production
PostgreSQL 14.5
2025-01-09 file.txt

Everything works.

Running in cron:

# Crontab entry
0 2 * * * /home/user/scripts/deploy.sh >> /var/log/deploy.log 2>&1

Output in log:

./deploy.sh: line 4: node: command not found
Deploying to: 
psql: error: connection to server failed: could not connect to server
aws: command not found

Everything fails.

Why?

  • node isn't in cron's minimal PATH
  • $DEPLOY_ENV isn't set
  • $DATABASE_URL isn't set
  • aws isn't in cron's minimal PATH

The Root Cause

Cron intentionally runs with a minimal environment:

  1. Security - Prevents scripts from accessing sensitive variables accidentally
  2. Consistency - Same environment regardless of which user runs it
  3. Predictability - No hidden dependencies on user config
  4. Isolation - Jobs don't interfere with each other

But this means YOU must explicitly provide every variable your script needs.

Solution 1: Declare Variables in Crontab

The most straightforward solution: define variables directly in your crontab file.

Basic Syntax

Variables set at the top of your crontab apply to all jobs below them:

# In crontab -e

# Set variables
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin
HOME=/home/user
SHELL=/bin/bash
DATABASE_URL=postgresql://localhost/myapp
API_KEY=sk_live_abc123def456

# Jobs use variables set above
0 2 * * * /home/user/scripts/deploy.sh

Setting PATH

PATH is the most important variable to set.

Problem: Cron's default PATH is /usr/bin:/bin. If your commands are in /usr/local/bin or custom locations, cron won't find them.

Solution: Set a complete PATH at the top of your crontab:

PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin

Or copy your interactive PATH:

# In terminal, get your PATH
echo $PATH

Example output:

/usr/local/bin:/usr/bin:/bin:/home/user/.local/bin:/home/user/.nvm/versions/node/v18.0.0/bin

Copy to crontab:

# In crontab -e
PATH=/usr/local/bin:/usr/bin:/bin:/home/user/.local/bin:/home/user/.nvm/versions/node/v18.0.0/bin

0 2 * * * /home/user/scripts/deploy.sh

Now cron finds commands in all those directories.

Setting SHELL

Problem: Cron defaults to /bin/sh, which is often a minimal shell (not bash).

Solution: Explicitly set bash if your scripts use bash features:

SHELL=/bin/bash

Bash features that don't work in sh:

  • Arrays: array=(one two three)
  • Process substitution: <(command)
  • Double brackets: [[ condition ]]
  • Brace expansion: {1..10}
  • source command (use . instead in sh)

If your scripts start with #!/bin/bash, setting SHELL=/bin/bash ensures consistency.

Setting Custom Application Variables

Problem: Your scripts rely on custom variables set in .bashrc.

Solution: Declare them in crontab:

# In crontab -e

# Application variables
DATABASE_URL=postgresql://user:pass@localhost/myapp
REDIS_URL=redis://localhost:6379
API_KEY=sk_live_abc123def456
DEPLOY_ENV=production
LOG_LEVEL=info

0 2 * * * /home/user/scripts/deploy.sh

Complete Example: Application Deployment

# File: crontab -e

# Environment setup
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin
HOME=/home/user

# Application config
NODE_ENV=production
DATABASE_URL=postgresql://app:secret@localhost/myapp
REDIS_URL=redis://localhost:6379/0
AWS_REGION=us-east-1
API_BASE_URL=https://api.example.com
LOG_DIR=/var/log/myapp

# Email notifications
MAILTO=admin@example.com

# Jobs
0 2 * * * /home/user/scripts/deploy.sh >> $LOG_DIR/deploy.log 2>&1
0 3 * * * /home/user/scripts/backup.sh >> $LOG_DIR/backup.log 2>&1
*/15 * * * * /home/user/scripts/health-check.sh >> $LOG_DIR/health.log 2>&1

Variable Scope

Variables apply to all jobs below them until redefined:

# Set default PATH
PATH=/usr/local/bin:/usr/bin:/bin

# This job uses the PATH above
0 2 * * * /home/user/job1.sh

# Override PATH for specific job
PATH=/custom/bin:/usr/local/bin:/usr/bin:/bin
0 3 * * * /home/user/job2.sh

# Reset to default PATH
PATH=/usr/local/bin:/usr/bin:/bin
0 4 * * * /home/user/job3.sh

Pros and Cons of Crontab Variables

Pros:

  • ✅ Clear and explicit
  • ✅ All configuration in one place
  • ✅ Easy to review what variables are set
  • ✅ No hidden dependencies
  • ✅ Works with all cron implementations

Cons:

  • ❌ Duplicates configuration (if same variables in .bashrc)
  • ❌ Have to update crontab when variables change
  • ❌ Sensitive data (API keys) visible in crontab
  • ❌ Verbose if you have many variables

When to use:

  • ✅ Small number of variables
  • ✅ Job-specific configuration
  • ✅ Want explicit, visible configuration
  • ✅ Different values than interactive environment

Solution 2: Source a Configuration File

Instead of duplicating variables, source them from a file.

Basic Technique

Create a configuration file:

# File: /home/user/.cron_env
export PATH=/usr/local/bin:/usr/bin:/bin:/home/user/.local/bin
export DATABASE_URL=postgresql://localhost/myapp
export API_KEY=sk_live_abc123def456
export NODE_ENV=production

Source it in cron:

# In crontab -e
0 2 * * * . /home/user/.cron_env && /home/user/scripts/deploy.sh

The . /home/user/.cron_env && part:

  • . is the source command (same as source in bash)
  • Loads all variables from the file
  • && means "if sourcing succeeds, then run the script"

Sourcing .bashrc or .profile

Attempt to source your existing shell configuration:

# In crontab -e
0 2 * * * . /home/user/.bashrc && /home/user/scripts/deploy.sh

⚠️ Warning: This often doesn't work well because:

  1. .bashrc may exit early if it detects non-interactive shell:

    # Common in .bashrc
    [ -z "$PS1" ] && return  # Exits if not interactive
    
  2. .bashrc may have interactive-only commands that fail in cron:

    # These fail in cron
    alias ll='ls -la'
    bind '"\e[A": history-search-backward'
    
  3. Side effects - .bashrc might print output, breaking cron job output

Better: Create a dedicated .cron_env file with only what cron needs.

Sourcing .profile or .bash_profile

These are better candidates because they're designed for login shells:

# In crontab -e
0 2 * * * . /home/user/.profile && /home/user/scripts/deploy.sh

Or:

0 2 * * * . /home/user/.bash_profile && /home/user/scripts/deploy.sh

Still risky because:

  • May contain interactive assumptions
  • May source .bashrc (inheriting its problems)
  • Harder to debug when things fail

Creating a Dedicated Environment File

Best practice:

# File: /home/user/.env.production
#!/bin/bash
# Environment variables for production cron jobs

# PATH
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin

# Language version managers
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # Load nvm

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"

# Application config
export NODE_ENV=production
export DATABASE_URL=postgresql://app:secret@localhost/myapp
export REDIS_URL=redis://localhost:6379/0

# AWS config
export AWS_REGION=us-east-1
export AWS_PROFILE=production

# Custom app variables
export API_BASE_URL=https://api.example.com
export LOG_LEVEL=info

Make it executable (optional but good practice):

chmod +x /home/user/.env.production

Source in crontab:

# In crontab -e
SHELL=/bin/bash

0 2 * * * . /home/user/.env.production && /home/user/scripts/deploy.sh >> /var/log/deploy.log 2>&1
0 3 * * * . /home/user/.env.production && /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1

Sourcing Inside the Script

Alternative: Source at the beginning of your script:

#!/bin/bash
# File: /home/user/scripts/deploy.sh

# Load environment
source /home/user/.env.production

# Now all variables are available
echo "Deploying to $NODE_ENV"
node --version
psql "$DATABASE_URL" -c "SELECT version();"

Crontab is simpler:

# In crontab -e
0 2 * * * /home/user/scripts/deploy.sh >> /var/log/deploy.log 2>&1

Pros:

  • ✅ Crontab stays clean
  • ✅ Environment loading is part of the script
  • ✅ Easy to test (running script manually also loads environment)

Cons:

  • ❌ Every script needs to source the file
  • ❌ Harder to share environment across unrelated scripts
  • ❌ Duplication if multiple scripts need same environment

Security Considerations for Environment Files

Problem: Environment files often contain secrets.

Solution 1: Restrict file permissions:

chmod 600 /home/user/.env.production

Only the owner can read it.

Solution 2: Separate secrets from non-secrets:

# File: /home/user/.env.production (non-sensitive)
export PATH=/usr/local/bin:/usr/bin:/bin
export NODE_ENV=production
export AWS_REGION=us-east-1

# File: /home/user/.env.secrets (sensitive, restricted permissions)
export DATABASE_URL=postgresql://app:secret@localhost/myapp
export API_KEY=sk_live_abc123def456
export AWS_SECRET_ACCESS_KEY=xyz789

Secure the secrets file:

chmod 600 /home/user/.env.secrets
chown user:user /home/user/.env.secrets

Source both:

# In crontab -e or in script
. /home/user/.env.production
. /home/user/.env.secrets

Solution 3: Use a secrets manager:

#!/bin/bash
# File: /home/user/scripts/deploy.sh

# Load non-sensitive config
source /home/user/.env.production

# Fetch secrets from vault/secrets manager
export DATABASE_URL=$(aws secretsmanager get-secret-value --secret-id db-url --query SecretString --output text)
export API_KEY=$(aws secretsmanager get-secret-value --secret-id api-key --query SecretString --output text)

# Run deployment
# ...

Pros and Cons of Sourcing Files

Pros:

  • ✅ Single source of truth for environment
  • ✅ Reusable across multiple cron jobs
  • ✅ Easier to update (change file, not crontab)
  • ✅ Can share with interactive shell (if using .profile)
  • ✅ Better for complex environments (language version managers)

Cons:

  • ❌ Less explicit (variables not visible in crontab)
  • ❌ Harder to debug (where is variable defined?)
  • ❌ File permissions must be correct
  • ❌ Sourcing can fail silently
  • ❌ Potential for unintended side effects

When to use:

  • ✅ Many variables needed
  • ✅ Same environment for multiple jobs
  • ✅ Complex setup (nvm, pyenv, rbenv)
  • ✅ Variables change frequently
  • ✅ Want to reuse existing configuration

Solution 3: Inline Environment Variables

Set variables for a single command without affecting other jobs.

Syntax

# In crontab -e
0 2 * * * DEPLOY_ENV=production LOG_LEVEL=debug /home/user/scripts/deploy.sh

Variables before the command are set only for that command's execution.

Multiple Variables

0 2 * * * NODE_ENV=production DATABASE_URL=postgresql://localhost/myapp API_KEY=abc123 /home/user/scripts/deploy.sh

Combining with Sourcing

0 2 * * * . /home/user/.env.production && OVERRIDE_VAR=special /home/user/scripts/deploy.sh

This:

  1. Sources variables from .env.production
  2. Overrides or adds OVERRIDE_VAR=special
  3. Runs the script

When to Use Inline Variables

Good for:

  • ✅ Job-specific overrides
  • ✅ Testing different configurations
  • ✅ One or two variables

Avoid for:

  • ❌ Many variables (hard to read)
  • ❌ Long values (makes crontab messy)
  • ❌ Sensitive data (visible in process list with ps)

Handling Language Version Managers

Special attention needed for nvm (Node.js), pyenv (Python), rbenv (Ruby), rustup (Rust).

The Problem

These tools modify your PATH and environment, but only in interactive shells:

# In .bashrc
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Cron doesn't run .bashrc, so nvm isn't loaded, and node isn't found.

Solution 1: Use System-Installed Version

Install Node/Python/Ruby system-wide:

# Ubuntu/Debian
sudo apt install nodejs python3 ruby

# macOS
brew install node python ruby

Now commands are in standard locations:

/usr/bin/node
/usr/bin/python3
/usr/bin/ruby

Set PATH in crontab:

PATH=/usr/local/bin:/usr/bin:/bin

Cron finds them automatically.

Downside: Can't easily switch versions per project.

Solution 2: Use Absolute Path to Specific Version

Find the absolute path:

which node
# Output: /home/user/.nvm/versions/node/v18.0.0/bin/node

Use absolute path in script:

#!/bin/bash
# File: /home/user/scripts/deploy.sh

# Use specific Node.js version
/home/user/.nvm/versions/node/v18.0.0/bin/node app.js

Or in crontab PATH:

PATH=/home/user/.nvm/versions/node/v18.0.0/bin:/usr/local/bin:/usr/bin:/bin

Downside: Hardcoded version. Breaks when you upgrade.

Solution 3: Load Version Manager in Environment File

Create dedicated environment file:

# File: /home/user/.env.node
#!/bin/bash

# Load nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# Optionally set default version
nvm use default

Source in crontab:

SHELL=/bin/bash
0 2 * * * . /home/user/.env.node && /home/user/scripts/deploy.sh

Or source in script:

#!/bin/bash
# File: /home/user/scripts/deploy.sh

# Load nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# Now node is available
node --version
npm run deploy

Solution 4: Use .nvmrc for Project-Specific Version

In project directory:

# File: /home/user/projects/myapp/.nvmrc
18.0.0

In script:

#!/bin/bash
# File: /home/user/scripts/deploy.sh

# Load nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# Change to project directory
cd /home/user/projects/myapp

# Use version specified in .nvmrc
nvm use

# Now correct Node version is active
node --version
npm run deploy

Example: Python with pyenv

# File: /home/user/.env.python
#!/bin/bash

# Load pyenv
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"
eval "$(pyenv init -)"

# Set Python version
pyenv global 3.11.0

In crontab:

SHELL=/bin/bash
0 2 * * * . /home/user/.env.python && /home/user/scripts/data-processing.py

Example: Ruby with rbenv

# File: /home/user/.env.ruby
#!/bin/bash

# Load rbenv
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

In script:

#!/bin/bash
# File: /home/user/scripts/process.sh

# Load rbenv
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

# Now ruby is available
ruby --version
bundle exec rake deploy

Best Practices: Which Method to Use When

Decision Matrix

| Scenario | Best Solution | Reason | |----------|--------------|--------| | 1-3 simple variables | Declare in crontab | Clear and explicit | | Many variables (10+) | Source environment file | Avoid crontab clutter | | Same env for multiple jobs | Source environment file | DRY principle | | Job-specific override | Inline variable | Precise control | | Sensitive data | Environment file with restricted permissions | Security | | Language version manager | Source file that loads manager | Only way to load nvm/pyenv/rbenv | | Testing different configs | Inline variables | Easy to experiment | | Production stability | Declare in crontab | No external file dependencies | | PATH only | Declare in crontab | Simple, one-liner |

Recommended Patterns

Pattern 1: Simple jobs (recommended starting point)

# In crontab -e
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash

0 2 * * * /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1

Pattern 2: Complex environment (recommended for apps)

# File: /home/user/.env.myapp
export PATH=/usr/local/bin:/usr/bin:/bin
export NODE_ENV=production
export DATABASE_URL=postgresql://localhost/myapp
# ... more variables
# In crontab -e
SHELL=/bin/bash
0 2 * * * . /home/user/.env.myapp && /home/user/scripts/deploy.sh >> /var/log/deploy.log 2>&1

Pattern 3: Environment in script (recommended for self-contained scripts)

#!/bin/bash
# File: /home/user/scripts/deploy.sh

# Load environment at the top
if [ -f "$HOME/.env.myapp" ]; then
    source "$HOME/.env.myapp"
else
    echo "ERROR: Environment file not found"
    exit 1
fi

# Script logic here
echo "Deploying to $NODE_ENV"
# ...
# In crontab -e (stays clean)
0 2 * * * /home/user/scripts/deploy.sh >> /var/log/deploy.log 2>&1

Anti-Patterns to Avoid

❌ Don't source .bashrc in crontab:

# BAD - .bashrc often fails in non-interactive context
0 2 * * * . /home/user/.bashrc && /home/user/script.sh

❌ Don't put sensitive data directly in crontab:

# BAD - API keys visible in process list
0 2 * * * API_KEY=sk_live_secret123 /home/user/script.sh

❌ Don't assume any variables exist:

#!/bin/bash
# BAD - assumes $USER is set (it might not be in cron)
backup_dir="/home/$USER/backups"

# GOOD - explicit or check if set
backup_dir="/home/john/backups"
# Or
backup_dir="${HOME}/backups"

❌ Don't source files without error checking:

# BAD - silently fails if file doesn't exist
0 2 * * * . /home/user/.env && /home/user/script.sh

# GOOD - check file exists first
0 2 * * * [ -f /home/user/.env ] && . /home/user/.env && /home/user/script.sh

Debugging Environment Issues

When your cron job fails due to environment problems:

Step 1: Capture Cron's Environment

# Add temporary job
* * * * * env > /tmp/cron-env.txt

Compare to interactive environment:

env > /tmp/interactive-env.txt
diff /tmp/interactive-env.txt /tmp/cron-env.txt

Missing variables will appear in the diff.

Step 2: Test Your Script with Cron's Environment

Run your script with minimal environment:

env -i HOME=$HOME PATH=/usr/bin:/bin /home/user/scripts/deploy.sh

env -i starts with empty environment, then you add only what cron has.

If this fails the same way as in cron, you've identified the environment issue.

Step 3: Add Debugging to Your Script

#!/bin/bash
# File: /home/user/scripts/deploy.sh

# Debug: Print environment
echo "=== Environment ===" >> /tmp/script-debug.log
env >> /tmp/script-debug.log
echo "==================" >> /tmp/script-debug.log

# Debug: Print PATH
echo "PATH: $PATH" >> /tmp/script-debug.log

# Debug: Check if commands exist
command -v node >> /tmp/script-debug.log || echo "node not found" >> /tmp/script-debug.log
command -v psql >> /tmp/script-debug.log || echo "psql not found" >> /tmp/script-debug.log

# Your actual script
# ...

Check the debug log:

cat /tmp/script-debug.log

Step 4: Verify File Sourcing Works

#!/bin/bash
# File: /home/user/scripts/deploy.sh

# Debug: Before sourcing
echo "Before source: NODE_ENV=$NODE_ENV" >> /tmp/source-debug.log

# Source environment
if [ -f "/home/user/.env.myapp" ]; then
    source "/home/user/.env.myapp"
    echo "Sourced .env.myapp successfully" >> /tmp/source-debug.log
else
    echo "ERROR: .env.myapp not found" >> /tmp/source-debug.log
fi

# Debug: After sourcing
echo "After source: NODE_ENV=$NODE_ENV" >> /tmp/source-debug.log
env >> /tmp/source-debug.log

Step 5: Check File Permissions

# Can cron user read the environment file?
ls -l /home/user/.env.myapp

# Should show readable by owner
-rw------- 1 user user 456 Jan 09 14:23 .env.myapp

If permissions are wrong:

chmod 600 /home/user/.env.myapp
chown user:user /home/user/.env.myapp

Complete Production Example

Here's a production-ready setup combining best practices:

Environment File

# File: /home/user/.env.production
#!/bin/bash
# Production environment for cron jobs
# Permissions: chmod 600, owner only

# System PATH
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin

# Language version manager (Node.js)
export NVM_DIR="$HOME/.nvm"
if [ -s "$NVM_DIR/nvm.sh" ]; then
    \. "$NVM_DIR/nvm.sh"
    nvm use default --silent
fi

# Application environment
export NODE_ENV=production
export APP_NAME=myapp
export APP_VERSION=2.1.0

# Logging
export LOG_DIR=/var/log/myapp
export LOG_LEVEL=info

# Connections (sourced from secrets file)
if [ -f "$HOME/.env.secrets" ]; then
    source "$HOME/.env.secrets"
fi

# Derived variables
export APP_ROOT=/home/user/apps/$APP_NAME
export BACKUP_DIR=/var/backups/$APP_NAME

Secrets File

# File: /home/user/.env.secrets
# Permissions: chmod 600, owner only
# NEVER commit this to version control

export DATABASE_URL=postgresql://app:SecurePass123@localhost/myapp
export REDIS_URL=redis://:SecurePass456@localhost:6379/0
export API_KEY=sk_live_abc123def456ghi789
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Application Script

#!/bin/bash
# File: /home/user/scripts/deploy.sh
set -euo pipefail  # Exit on errors

# Load environment
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="$HOME/.env.production"

if [ ! -f "$ENV_FILE" ]; then
    echo "ERROR: Environment file not found: $ENV_FILE" >&2
    exit 1
fi

source "$ENV_FILE"

# Logging function
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_DIR/deploy.log"
}

# Main deployment logic
log "Starting deployment for $APP_NAME v$APP_VERSION"
log "Environment: $NODE_ENV"

# Verify required variables
for var in DATABASE_URL REDIS_URL API_KEY; do
    if [ -z "${!var}" ]; then
        log "ERROR: Required variable $var is not set"
        exit 1
    fi
done

log "All required variables are set"

# Change to app directory
cd "$APP_ROOT" || exit 1

# Run deployment
log "Running database migrations..."
npx sequelize-cli db:migrate

log "Building application..."
npm run build

log "Restarting application..."
pm2 restart myapp

log "Deployment completed successfully"

Crontab Configuration

# File: crontab -e

# Global settings
SHELL=/bin/bash
MAILTO=admin@example.com

# Deploy every day at 2 AM
0 2 * * * /home/user/scripts/deploy.sh 2>&1 | tee -a /var/log/cron-deploy.log

# Backup every day at 3 AM  
0 3 * * * . /home/user/.env.production && /home/user/scripts/backup.sh 2>&1 | tee -a /var/log/cron-backup.log

# Health check every 15 minutes
*/15 * * * * . /home/user/.env.production && /home/user/scripts/health-check.sh 2>&1 | tee -a /var/log/cron-health.log

Security Checklist

# Set correct permissions
chmod 700 /home/user/scripts/
chmod 700 /home/user/scripts/*.sh
chmod 600 /home/user/.env.production
chmod 600 /home/user/.env.secrets

# Verify ownership
chown -R user:user /home/user/scripts/
chown user:user /home/user/.env.production
chown user:user /home/user/.env.secrets

# Never commit secrets to git
echo ".env.secrets" >> .gitignore

Quick Reference

Common Environment Variables for Cron

| Variable | Purpose | Example Value | |----------|---------|---------------| | PATH | Where to find commands | /usr/local/bin:/usr/bin:/bin | | HOME | User's home directory | /home/user | | SHELL | Which shell to use | /bin/bash | | USER | Username | john | | LOGNAME | Login name | john | | LANG | Language/encoding | en_US.UTF-8 | | TZ | Timezone | America/New_York | | MAILTO | Where to send cron output | admin@example.com |

Sourcing Syntax

| Command | Meaning | |---------|---------| | . /path/to/file | Source file (POSIX, works everywhere) | | source /path/to/file | Source file (bash-specific) | | . file && command | Source file, then run command if successful | | [ -f file ] && . file | Source file only if it exists |

Conclusion

The core problem: Cron runs with a minimal environment. Your scripts expect a rich environment.

The solutions:

  1. Declare in crontab - Best for simple cases, small number of variables
  2. Source environment file - Best for complex environments, many variables, version managers
  3. Inline variables - Best for job-specific overrides

Best practices:

  • ✅ Always set PATH explicitly
  • ✅ Use SHELL=/bin/bash if scripts need bash features
  • ✅ Create dedicated environment files for cron (don't rely on .bashrc)
  • ✅ Secure sensitive data with proper file permissions (chmod 600)
  • ✅ Test scripts with cron's minimal environment
  • ✅ Add debugging output to troubleshoot
  • ✅ Document which environment variables are required

Your checklist:

  1. ✅ Identify which variables your script needs
  2. ✅ Choose appropriate solution (crontab vs. file vs. inline)
  3. ✅ Set PATH to include all necessary directories
  4. ✅ Test script with minimal environment (env -i)
  5. ✅ Verify file permissions (600 for environment files)
  6. ✅ Add logging to confirm variables are set correctly
  7. ✅ Document environment requirements

From broken:

# Fails: command not found, undefined variables
0 2 * * * /home/user/scripts/deploy.sh

To working:

# Success: complete environment loaded
0 2 * * * . /home/user/.env.production && /home/user/scripts/deploy.sh >> /var/log/deploy.log 2>&1

Stop debugging "command not found" at 2 AM. Set up your environment correctly once, and your cron jobs will run reliably forever.

Ready to build bulletproof cron jobs? Use our Cron Expression Generator to create schedules that work the first time.


Related Articles

Essential Cron Troubleshooting:

Master Cron Fundamentals:

Language-Specific Guides:


Keywords: cron environment variables, cron path variable, cron .bashrc, crontab environment, cron command not found, cron source file, nvm cron, pyenv cron, cron env, environment variables crontab