Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk10 min

Why a Python Script Runs Manually but Fails in Crontab (and How to Fix It)

Cron does not inherit your terminal session. Use an absolute virtual-environment interpreter, deterministic paths, explicit environment settings and redirected logs to make Python jobs reliable.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Define 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#!/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
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Diagnose the failure in a controlled sequence

  1. Confirm the installed schedule and account. Run crontab -l as 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.
  2. Replace names with absolute paths. Substitute python with the verified virtual-environment interpreter and use an absolute script path. Do the same for helper programs called by the job.
  3. Add a safe startup diagnostic. Temporarily log sys.executable, sys.version, os.getcwd(), sys.argv and a safe allowlist of environment keys. Never dump tokens, passwords or the entire environment.
  4. Run the exact absolute command outside cron. Copy the interpreter, script, arguments and redirections exactly. This separates Python errors from scheduler parsing errors.
  5. 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.
  6. 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.
  7. 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.
  8. 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.