Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
VGSources
Blog

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

Cron runs Python with a different shell, environment, directory, and command parser than your terminal. Learn a deterministic setup and a step-by-step diagnostic method.
WorldCrontab-and How to Fix It Length3 min Posted Quest giverVGSources Team

Free tools Windows power users keep installed

One-click scans. No signup required.

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

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.

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.

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

Linux 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. 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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
/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.

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.

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

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#!/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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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

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.

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.

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

7. Reproduce the cron conditions deliberately

Testing only in your normal shell can conceal the cause. Narrow the difference in a fixed sequence.

  1. Confirm the schedule and owner. Run crontab -l as the intended account. Check that the crontab ends with a newline and that you edited the user crontab you think is active.
  2. Replace ambiguous commands. Change python and relative script names to absolute paths.
  3. Add startup diagnostics. Log sys.executable, sys.version, os.getcwd(), sys.argv, and a safe subset of environment variables.
  4. Run the literal command manually. Copy the exact absolute command, including redirections, into a shell as the cron user.
  5. 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.

  1. 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.
  2. Inspect logs. Read the application log and the operating system’s cron logs. Do not rely on terminal output or assumed email delivery.
  3. Review metacharacters. Check percent signs, quotes, redirections, wildcard expansion, pipes, and any syntax that works in Bash but not in /bin/sh.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

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

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

  1. Run crontab -l as the intended account and verify the schedule.
  2. Confirm the crontab file ends with a newline.
  3. Use an absolute Python interpreter, preferably the project’s .venv/bin/python.
  4. Use an absolute script path.
  5. Resolve application files from Path(__file__).resolve().parent or use absolute paths.
  6. Define required non-secret environment variables explicitly.
  7. Set SHELL and a known PATH when helper commands are used.
  8. Escape literal percent signs or move complicated commands into a wrapper.
  9. Redirect both output streams to an absolute writable log.
  10. Test the exact command normally and with env -i.
  11. Check interpreter, import, directory traversal, file, and log permissions.
  12. 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.

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 *

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.

More quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair 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.