Free tools Windows power users keep installed
One-click scans. No signup required.
A Python program that succeeds in a terminal can fail in crontab because cron is a separate, non-interactive process. It may use a different shell, interpreter, working directory, environment variables, permissions, and command parsing rules than your login session. Make the scheduled command deterministic: call the intended Python executable by absolute path, use absolute or script-relative file paths, define required environment values, escape cron metacharacters, and write stdout and stderr 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 program
When you run a command interactively, your terminal has already established a context. Shell startup files may have added directories to PATH, activated a virtual environment, exported configuration variables, selected a project directory, and loaded aliases or shell functions. Cron does not inherit that interactive context.
As an Amazon Associate I earn from qualifying purchases.
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 reinstallLinux crontab runs commands through /bin/sh by default. For the crontab owner, it sets HOME and LOGNAME from the account record. POSIX also requires a default environment containing HOME, LOGNAME, PATH, and SHELL, but the values and additional variables are not the same as those in your desktop or login shell. A job therefore has to declare the assumptions it needs instead of relying on whatever happened to be present in your terminal.
This explains several apparently unrelated symptoms: python: command not found, an import that works only after activating a virtual environment, missing configuration, “file not found” errors for a relative path, and a job that appears to run but produces no visible result.
#1 Best Overall
1. Select the same Python interpreter every time
The word python is only a command lookup. Your terminal may find a virtual-environment interpreter or a version-manager shim, while cron may find a system interpreter—or no executable at all. Different interpreters have different installed packages and Python versions.
Call the virtual-environment executable directly
Suppose the project is in /srv/app and its environment is /srv/app/.venv. Use:
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →/srv/app/.venv/bin/python /srv/app/job.py
Python’s virtual-environment documentation explains that activation prepends the environment’s bin directory to PATH; activation is not required when you invoke that interpreter directly. This removes dependence on shell startup files and on the value of PATH.
Check the path as the account that owns the crontab:
ls -l /srv/app/.venv/bin/python
/srv/app/.venv/bin/python -c 'import sys; print(sys.executable); print(sys.version)'
Run the exact absolute command outside cron before scheduling it. If it fails there, cron is not the cause; fix the interpreter, package installation, or application first.
Rank #2
Do not assume activation syntax is portable
A terminal command such as source .venv/bin/activate is shell-specific and depends on the current directory. The default cron shell is /bin/sh, where source may not exist. Even when activation is written with the POSIX . command, it adds another path and quoting dependency. Direct interpreter invocation is shorter and more reproducible.
2. Make file paths independent of the working directory
A path such as data/input.csv is resolved relative to the process’s current working directory, not relative to the Python file. The directory selected by cron can differ from the directory used in your terminal, so a program may report that a file is missing even though it exists in the project.
Build paths from the script location
For files shipped with the application, derive a base directory from __file__:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
input_file = BASE_DIR / "data" / "input.csv"
output_file = BASE_DIR / "data" / "output.csv"
text = input_file.read_text(encoding="utf-8")
This remains stable when the job is launched from another directory. For data intentionally stored outside the project, use an absolute path and document the required directory.
Change directory only when the application needs it
If libraries or legacy code require a project working directory, set it explicitly in a wrapper:
#!/bin/sh
set -eu
cd /srv/app
exec /srv/app/.venv/bin/python /srv/app/job.py
Save the wrapper, for example as /srv/app/run-job.sh, make it executable with chmod 750 /srv/app/run-job.sh, and use its absolute path in crontab. The exec command preserves the useful exit status and makes the Python process the job that cron is tracking.
3. Declare the environment your program requires
Variables exported by .bashrc, .profile, pyenv, conda, a desktop session, or a secret-management tool may not be present in cron. If the script needs a mode, an API endpoint, a locale, or a configuration directory, set it deliberately.
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
Environment assignments at the top of a crontab apply to jobs in that crontab. Keep the value set limited to what the program needs. Do not put passwords, private tokens, or other secrets directly into a crontab that other users can read. Prefer a configuration file with restrictive ownership and permissions, then pass its absolute path to the program.
Log only safe diagnostic values
During troubleshooting, record the interpreter and process context without dumping credentials:
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →import os
import sys
print("sys.executable:", sys.executable)
print("sys.version:", sys.version)
print("os.getcwd():", os.getcwd())
print("sys.argv:", sys.argv)
for key in ("HOME", "LOGNAME", "PATH", "APP_ENV"):
print(f"{key}=", os.environ.get(key))
Remove or reduce this output once the cause is known, especially if logs can be read by more people than the application account.
4. Remember that crontab has its own command syntax
After cron parses the five time fields, it passes the command to the configured shell. Shell quoting and redirection still matter, but crontab adds a particularly surprising rule: an unescaped percent sign starts standard input. The first unescaped % is converted to a newline; the remaining unescaped percent signs are converted to newlines as well. Text after the first one is supplied to the command on standard input instead of remaining part of the command.
A date format such as:
date +%Y-%m-%d
must be written in crontab as:
date +%Y-%m-%d
Backslashes are consumed by cron for this purpose. For commands with substantial quoting, pipes, JSON, or shell-specific features, put the logic in an executable wrapper and schedule the wrapper. This also gives you a normal file to test with a shell checker and to run manually.
5. Make output and errors visible
Interactive failures appear immediately in your terminal. Cron has no terminal. POSIX permits unredirected output and errors to be mailed to the user, but local mail delivery is often unconfigured or never monitored. A successful-looking schedule can therefore hide a traceback.
Redirect both streams to an absolute path:
30 2 * * * /srv/app/.venv/bin/python /srv/app/job.py >> /srv/app/logs/job.log 2>&1
Create the log directory first and ensure the cron account can traverse every parent directory and write the file:
install -d -m 750 -o appuser -g appuser /srv/app/logs
touch /srv/app/logs/job.log
chown appuser:appuser /srv/app/logs/job.log
chmod 640 /srv/app/logs/job.log
Use a log-rotation policy for jobs that run frequently or produce verbose tracebacks. An absolute log path prevents a second working-directory problem from hiding the first one.
Best Value
6. A reliable crontab pattern
For a simple job, this pattern states the shell, supplies a predictable executable search path for any helper commands, calls the virtual-environment interpreter directly, and captures output:
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 paths that actually exist for the account running the crontab. If the script expects a project directory, either build Python paths from Path(__file__).resolve().parent or schedule the wrapper shown earlier.
7. Reproduce the cron conditions deliberately
Testing only in your normal shell can conceal the cause. Narrow the difference in a fixed sequence.
- Confirm the schedule and owner. Run
crontab -las the intended account. Check that the crontab ends with a newline and that you edited the user crontab you think is active. - Replace ambiguous commands. Change
pythonand relative script names to absolute paths. - Add startup diagnostics. Log
sys.executable,sys.version,os.getcwd(),sys.argv, and a safe subset of environment variables. - Run the literal command manually. Copy the exact absolute command, including redirections, into a shell as the cron user.
- Strip the environment. A minimal test exposes hidden dependencies:
env -i HOME="$HOME" PATH="/usr/bin:/bin" /srv/app/.venv/bin/python /srv/app/job.py
Add only the variables the program demonstrably requires. If this test fails while the normal command works, the missing item is likely an environment variable, a helper executable, or a path supplied by a shell startup file.
- Check permissions. Verify read permission on the script, environment, imports, and input files; execute permission on directories and wrappers; and write permission on output and log directories. A file can be readable while one parent directory blocks traversal.
- Inspect logs. Read the application log and the operating system’s cron logs. Do not rely on terminal output or assumed email delivery.
- Review metacharacters. Check percent signs, quotes, redirections, wildcard expansion, pipes, and any syntax that works in Bash but not in
/bin/sh.
8. Diagnose common symptoms
| Symptom | Likely difference | Useful correction |
|---|---|---|
python: command not found |
Cron’s PATH does not contain the interpreter directory. |
Call the absolute virtual-environment or system interpreter. |
ModuleNotFoundError for a package installed in the project |
Cron selected a different Python installation. | Print sys.executable and use that environment’s interpreter directly. |
No such file or directory for a relative data path |
The process started in a different current directory. | Use absolute paths, script-relative pathlib paths, or an explicit cd in a wrapper. |
| Configuration or credentials are missing | An exported interactive-shell variable is absent. | Set non-secret variables in crontab; use a protected configuration file for secrets. |
| The command stops at a date format or receives strange input | An unescaped percent sign was parsed by cron. | Escape each percent sign or move the command into a script file. |
| The job appears to do nothing | Output was mailed or discarded from your normal view, or the log path is not writable. | Redirect stdout and stderr to an absolute, writable log and inspect cron logs. |
| A wrapper works in Bash but not from cron | The wrapper uses Bash-only syntax while cron invokes /bin/sh. |
Use POSIX shell syntax or give the wrapper an appropriate shebang and invoke it consistently. |
9. Choosing a direct line or a wrapper
Both approaches can be reliable when paths and permissions are explicit. The choice is mainly about how much shell setup the job needs.
| Approach | Interpreter determinism | Path and environment setup | Observability and maintenance |
|---|---|---|---|
| Direct crontab command | High when an absolute interpreter is used | Concise; assignments and redirections remain in one line | Easy for a short command; quoting becomes fragile as logic grows |
| POSIX wrapper script | High when the wrapper calls the absolute interpreter | Can set cd, options, checks, and variables in separate lines |
Easier to read, test, permission-check, and extend |
Use the direct line for a small, self-contained invocation. Use a wrapper when you need to change directory, validate required files, prepare directories, invoke multiple commands, or handle complex quoting. In either case, keep the Python path, script path, data paths, and log path absolute.
10. Security and operational checks
- Install the job in the correct user’s crontab. A root crontab and an application user’s crontab have different home directories, permissions, and available files.
- Give the wrapper and project directories ownership and modes that let the cron account execute the job without making private data world-readable.
- Keep secrets out of command lines and crontab text where other users may inspect them. Use a protected file and pass only its path.
- Ensure the log directory cannot be replaced or modified by an untrusted account; otherwise a scheduled job can write to an unexpected location.
- Record enough context to identify failures, but never log passwords, access tokens, or complete unfiltered environments.
11. A final preflight checklist
- Run
crontab -las the intended account and verify the schedule. - Confirm the crontab file ends with a newline.
- Use an absolute Python interpreter, preferably the project’s
.venv/bin/python. - Use an absolute script path.
- Resolve application files from
Path(__file__).resolve().parentor use absolute paths. - Define required non-secret environment variables explicitly.
- Set
SHELLand a knownPATHwhen helper commands are used. - Escape literal percent signs or move complicated commands into a wrapper.
- Redirect both output streams to an absolute writable log.
- Test the exact command normally and with
env -i. - Check interpreter, import, directory traversal, file, and log permissions.
- Read the application and system cron logs after the scheduled run.
Cron is not randomly breaking a working Python program; it is exposing assumptions supplied by your interactive session. Once the interpreter, current directory, environment, shell syntax, permissions, and logging are explicit, the scheduled execution becomes the same reproducible command you tested by hand.
Quick Recap
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.




