A script that works in your terminal can fail in crontab because cron starts it with a different shell, environment, user context, working directory and often a different Python interpreter. Make the scheduled command deterministic: call the intended interpreter by absolute path, use absolute or script-relative file paths, define required environment variables, avoid shell assumptions, and redirect output and errors to an absolute log file.
Need a server to try this on? Antihost offers Linux VPS plans with NVMe storage, full root access and a 14-day free trial.
What changes when cron starts the script
Your interactive terminal is the product of many settings accumulated during login. Shell startup files may modify PATH, define aliases and functions, activate a virtual environment, export API keys, select a pyenv or conda installation, and leave you in a particular project directory. Cron does not replay that session.
As an Amazon Associate I earn from qualifying purchases.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Linux crontab specifies /bin/sh as the default shell. It sets HOME and LOGNAME from the crontab owner’s account, while POSIX requires defaults for HOME, LOGNAME, PATH and SHELL. Other variables that happened to be exported in your terminal are not guaranteed to exist. The process also starts with a working directory chosen by the cron implementation and user context, not necessarily the directory shown in your terminal prompt.
These differences explain the common symptoms: python: command not found, an import that succeeds interactively but fails under cron, a missing input file, permission-denied errors, or a job that appears to run but produces no visible result.
#1 Best Overall
Use the exact Python interpreter you intend
Why python is unreliable in a crontab
When you type python, your interactive shell searches PATH. That search may find a distribution-managed Python, a user installation, a pyenv shim, a conda environment, or a project virtual environment. Cron may have a much shorter PATH, so it can find a different executable or none at all. Even if both commands are named python, their installed packages and Python versions can differ.
Verify the interpreter used interactively with command -v python and python -c 'import sys; print(sys.executable); print(sys.version)'. Do not copy the result blindly unless that path is readable and executable by the cron user.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Call a virtual-environment interpreter directly
Python’s virtual-environment documentation explains that activation prepends the environment’s bin directory to PATH, causing python to resolve to that environment. Activation is optional: calling the environment’s executable directly selects the same interpreter without relying on shell startup behavior. PEP 405 likewise defines a virtual environment with a bin/ directory on POSIX systems (or Scripts on Windows) containing its Python executable.
For example, if the project is in /srv/app, use /srv/app/.venv/bin/python, not just python. Install dependencies into that same environment and test imports with that exact executable:
/srv/app/.venv/bin/python -c 'import sys; print(sys.executable); import your_package; print("import ok")'
Calling the interpreter directly is usually more deterministic than sourcing an activation script in cron. Activation changes shell variables; it does not magically repair missing files, permissions or application configuration.
Rank #2
Make file paths independent of the launch directory
Why relative paths break
A path such as data/input.csv is resolved relative to the process’s current working directory. The directory from which you manually ran the command is not part of the script. Under cron, that directory may be the account’s home directory, a scheduler-specific directory, or another location. The same relative path can therefore refer to a nonexistent file or to the wrong file.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsBuild paths from the script location
For a Python program whose data belongs beside the source tree, derive the base directory from __file__:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
input_path = BASE_DIR / "data" / "input.csv"
log_path = BASE_DIR / "logs" / "job.log"
This approach remains stable when cron launches the process from another directory. For larger applications, define an explicit configuration root and pass it as an argument or environment variable rather than scattering assumptions about the current directory.
Change directory only when the application requires it
A wrapper can establish a known working directory before starting Python:
#!/bin/sh
set -eu
cd /srv/app
exec /srv/app/.venv/bin/python /srv/app/job.py
Save the wrapper with an absolute path, make it executable, and ensure the cron user can traverse every parent directory. Changing directory is a useful compatibility measure for code that intentionally uses relative paths, but script-relative paths are generally safer because they make the dependency visible in Python.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallDefine the environment your program actually needs
Variables from shell startup files are not automatic
Values exported by .bashrc, .profile, pyenv, conda, desktop sessions or a secret manager may be absent in cron. Typical failures include an unset database URL, a missing cloud-region setting, a different locale, or a command-line tool that cannot be found.
List the variables your program requires and provide them deliberately. A crontab may define simple non-secret settings before the schedule:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
APP_ENV=production
30 2 * * * /srv/app/.venv/bin/python /srv/app/job.py >> /srv/app/logs/job.log 2>&1
Use an absolute PATH only for commands you have verified. Defining it does not make an unavailable executable appear, and it does not replace an absolute Python path.
Keep secrets out of world-readable crontabs
Putting a password or API token directly in a crontab can expose it through account access, backups or process and configuration inspection. Prefer a permissions-restricted environment file, a protected credentials store, or a service-specific secret mechanism. Have the wrapper or application read it with the least permissions necessary, and never print secret values in diagnostic logs.
Recommended Free Tools
Remember that cron parses command text through a shell
The default shell is /bin/sh
Bash-only syntax, shell functions, aliases and interactive conveniences are not portable to /bin/sh. If your command requires shell-specific behavior, put that behavior in an explicitly selected script and use a matching shebang, or rewrite it using portable syntax. A script’s shebang controls how that script is interpreted after it starts; it does not change how cron parses the crontab line itself.
Escape percent signs
In a Linux crontab command, an unescaped % is converted to a newline; the remainder is supplied to the command as standard input. A date format such as date +%Y-%m-%d can therefore be split unexpectedly. Escape each percent sign as %, or move complicated command construction into a wrapper script where normal shell quoting applies.
30 2 * * * /srv/app/.venv/bin/python /srv/app/job.py --stamp "$(date +%Y-%m-%d)" >> /srv/app/logs/job.log 2>&1
Review quotes, redirections, pipes, wildcard expansion and command substitutions as crontab syntax, not as a copy of the command you typed interactively.
Make output and errors observable
Terminal output is visible during a manual run but has nowhere obvious to go under cron. POSIX permits unredirected standard output and standard error to be mailed to the user. That mail is easy to miss when local mail delivery is not configured or monitored.
Redirect both streams to an absolute, writable log path:
30 2 * * * /srv/app/.venv/bin/python /srv/app/job.py >> /srv/app/logs/job.log 2>&1
Create the log directory in advance and check its ownership and mode. If the program uses Python’s logging module, configure a file handler or send structured output to the same known destination. Include timestamps so you can distinguish a new failure from an old one, and arrange rotation so an unattended job cannot fill the filesystem.
Best Value
Use this reliable baseline configuration
A direct line is appropriate when the job needs only a deterministic interpreter, script path and log:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
30 2 * * * /srv/app/.venv/bin/python /srv/app/job.py >> /srv/app/logs/job.log 2>&1
Use a wrapper when you need a working directory, several environment settings, preflight checks or clearer shell logic:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#!/bin/sh
set -eu
cd /srv/app
export APP_ENV=production
exec /srv/app/.venv/bin/python /srv/app/job.py
Then schedule the wrapper itself:
30 2 * * * /srv/app/run-job.sh >> /srv/app/logs/job.log 2>&1
| Fix style | Interpreter determinism | Path and environment setup | Observability and reproduction |
|---|---|---|---|
| Direct crontab line | High when an absolute interpreter is used | Best for a short command with explicit paths and variables | Shorter, but quoting becomes fragile as complexity grows |
| Wrapper script | High when it calls the absolute interpreter | Convenient for cd, exports, checks and portable shell logic |
Easier to run manually with the same arguments; adds a file whose permissions must be maintained |
Diagnose the failure in a controlled sequence
- Confirm the installed schedule and account. Run
crontab -las the account that should execute the job. Ensure the crontab ends with a newline. For a system-wide entry, verify which user field is configured. - Replace names with absolute paths. Substitute
pythonwith the verified virtual-environment interpreter and use an absolute script path. Do the same for helper programs called by the job. - Add a safe startup diagnostic. Temporarily log
sys.executable,sys.version,os.getcwd(),sys.argvand a safe allowlist of environment keys. Never dump tokens, passwords or the entire environment. - Run the exact absolute command outside cron. Copy the interpreter, script, arguments and redirections exactly. This separates Python errors from scheduler parsing errors.
- Recreate a minimal environment. Test with a command such as
env -i HOME="$HOME" PATH="/usr/bin:/bin" /srv/app/.venv/bin/python /srv/app/job.py. Add only the variables the application demonstrably needs. This exposes hidden dependencies on interactive exports. - Check permissions along the complete path. The cron user needs execute permission on each parent directory, read permission for code and input files, execute permission for scripts or wrappers, and write permission for output and logs.
- Inspect the log and system cron records. Look for the first error, not only the final traceback. Check the platform’s cron service logs and the redirected application log; do not depend on terminal output or local mail.
- Review crontab metacharacters. Check percent signs, quoting, redirections, wildcard expansion, pipes and shell-specific syntax. Move complicated logic into a wrapper and test that wrapper independently.
Match common symptoms to the underlying cause
| Symptom | Likely cause | Targeted correction |
|---|---|---|
python: command not found |
Cron’s PATH does not contain the interactive Python location |
Call the absolute interpreter, or define and verify a suitable PATH |
ModuleNotFoundError only under cron |
A different interpreter or virtual environment is running | Use /project/.venv/bin/python and test imports with that executable |
| Input file cannot be found | The current directory differs from the terminal’s directory | Use Path(__file__).resolve().parent, absolute paths, or an explicit wrapper cd |
| Environment-dependent configuration is empty | Variables were exported only by an interactive shell or desktop session | Define required non-secret values explicitly and load secrets securely |
| Command appears truncated around a date format | An unescaped percent sign was parsed as a newline | Escape % as % or move the logic into a script |
| No visible error or result | Output was mailed or discarded from your normal view, or the log path is unwritable | Redirect both streams to an absolute writable log and inspect cron service logs |
| Permission denied | The cron user cannot traverse a parent directory or read, execute or write a required path | Check every path component and grant only the permissions the job needs |
Instrument the Python entry point without leaking secrets
A small diagnostic block can make the scheduler’s context obvious:
import os
import sys
from pathlib import Path
print({
"executable": sys.executable,
"version": sys.version,
"cwd": os.getcwd(),
"argv": sys.argv,
"script_dir": str(Path(__file__).resolve().parent),
"env": {key: os.environ.get(key) for key in ("HOME", "LOGNAME", "PATH", "APP_ENV")},
}, flush=True)
Keep the allowlist deliberate. Do not include credentials, full configuration blobs or arbitrary environment dumps in a shared log. Remove or lower the verbosity of this diagnostic after the cause is identified.
Choosing between a direct line and a wrapper
Use a direct crontab line when the command has a single absolute interpreter, a single absolute script and straightforward redirection. It minimizes moving parts and makes the scheduled command visible in crontab -l.
Use a wrapper when the job needs a project directory, multiple exports, preflight checks, cleanup, shell branching or complex quoting. Keep the wrapper’s shebang explicit, use set -eu where compatible with the script’s needs, and finish with exec so the Python process receives the expected exit status and signals. In both designs, retain absolute paths and log output; a wrapper is not a substitute for deterministic configuration.
Quick Recap
Final preflight checklist
- The crontab is installed for the intended user and ends with a newline.
- The Python path points to the interpreter containing the required packages.
- The script, input files, wrapper and log directory are addressed with absolute or script-relative paths.
- The cron user can traverse parent directories and read, execute or write each required path.
- Required environment variables are defined explicitly, while secrets are stored with restricted access.
- The command is valid for
/bin/sh, and every literal percent sign in crontab text is escaped. - Standard output and standard error go to an absolute log that you can inspect.
- A minimal-environment run reproduces the scheduled command before you wait for the next interval.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




