Developer guide

Cron Job Python: Schedule a Python Script the Right Way

“It works in my terminal, but cron won't run it” is the #1 Python scheduling complaint. cron does not load your shell, your PATH, or your virtual environment — so the fix is almost always the crontab line, not your script. Here is the exact pattern that works, every time.

Benjamin Rotshtein

Written by Benjamin Rotshtein

Updated

How do I schedule a Python script with cron?

Add one line to crontab -e that uses the absolute interpreter and absolute script path, captures output, and points at your venv directly: 0 9 * * * /home/user/.venv/bin/python /home/user/job.py >> /home/user/job.log 2>&1. This pattern fixes the three classic failures — missing PATH, no venv, and invisible errors — in one line.

The one-line crontab that always works?
0 9 * * * /home/user/.venv/bin/python /home/user/job.py >> /home/user/job.log 2>&1 — absolute interpreter, absolute script path, output captured.
Why does cron fail while my terminal works?
cron runs with PATH=/usr/bin:/bin and no venv activated. Bare python and your installed packages are invisible to it.
Need to run every 30 seconds?
cron's resolution is one minute. Use APScheduler's IntervalTrigger for sub-minute Python scheduling instead.

The 5-step setup that works

  1. Find the interpreter — run which python3 and copy the absolute path. Use it in the crontab line, never the bare python.
  2. Use the venv interpreter for dependencies — if the script imports anything from a virtual environment, point cron at it directly:
    # crontab line
    0 9 * * * /home/user/.venv/bin/python /home/user/job.py   >> /home/user/job.log 2>&1
  3. Use absolute paths for everything — the script's own reads/writes should use absolute paths too, because cron's working directory is not your project folder.
  4. Capture output>> /path/log 2>&1 sends stdout and stderr to a log. If the job fails silently you will find the traceback there.
  5. Install the entrycrontab -e, paste, save. Verify with crontab -l. Remember the trailing newline — cron ignores a crontab without one.

The three classic Python cron failures

  • ModuleNotFoundError — the system python3 was used instead of the venv's. Fix: absolute venv path in crontab.
  • python: command not found — cron's minimal PATH. Fix: /usr/bin/python3 or add PATH=/usr/local/bin:/usr/bin:/bin at the top of the crontab.
  • Job runs but does nothing — the script's relative paths resolve against cron's working directory. Fix: make the script os.chdir() to its own folder first, or use absolute paths.

Ready-made expressions

ScheduleCrontab line
Every minute* * * * *
Every 5 minutes*/5 * * * *
Every hour0 * * * *
Daily at 9:30 AM30 9 * * *
Weekdays at 9 AM0 9 * * 1-5
Every Monday at midnight0 0 * * 1
1st of the month at 2 AM0 2 1 * *

Not sure what an expression means? Use the cron-to-English tool or the full cheat sheet.

When to use APScheduler instead

cron is an OS-level scheduler: robust, survives reboots, and keeps running even if Python crashes. APScheduler runs inside your process and is the right choice when: you need sub-minute precision (every 30 or 5 seconds), you ship a long-running service that should schedule itself, or you deploy on Windows where cron is absent.

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger

scheduler = BackgroundScheduler()
scheduler.add_job(
    my_python_job,
    CronTrigger(minute="*/5"),          # every 5 minutes
    id="job_5min",
    replace_existing=True,
)
scheduler.start()                       # keep your app alive afterwards

Both patterns need the schedule written somewhere deliberate. If you are still designing the expression, the schedule library lists every common cadence with its exact crontab string.

Frequently asked questions

How do I run a Python script with cron every day?

Edit the crontab with crontab -e and add a line in the form: 0 9 * * * /usr/bin/python3 /home/user/scripts/job.py >> /home/user/scripts/job.log 2>&1. Use the absolute path to the interpreter (which python3) and absolute paths everywhere — cron does not load your shell profile, so relative paths and bare python fail.

Why does my Python cron job run from the terminal but not from cron?

Almost always the environment. Cron runs with PATH=/usr/bin:/bin and no venv activated. If your script imports packages installed in a virtual environment, cron either cannot find python or cannot find the packages. Two fixes: point the crontab at the venv interpreter directly (e.g. /home/user/.venv/bin/python), or source the venv inside the script before importing anything.

Should I use cron or APScheduler for Python?

cron is the right choice for system-level, OS-scheduled jobs that must survive your Python process crashing — it is managed by the OS and restarts on reboot. APScheduler (BackgroundScheduler) is right when the schedule lives inside your app, when you need milliseconds-level precision, or on Windows where cron does not exist. For scripts that just need to run every N minutes or at a fixed hour, cron is simpler and more robust.

How do I set up a cron job for a Python script with a virtual environment?

Best approach: put a shebang and an explicit interpreter in the crontab. Create the venv with python3 -m venv ~/.venv, install your packages into it, then use: 30 2 * * * /home/user/.venv/bin/python /home/user/job.py >> /home/user/job.log 2>&1. Using the venv's bin/python path means every package is importable without any activation step.

Can I run a Python script every minute with cron?

Yes — the crontab line * * * * * runs the job once per minute. If you need sub-minute intervals (every 30 or 5 seconds), cron cannot do that; use APScheduler with an IntervalTrigger instead. For every 5 minutes use */5 * * * *.

How do I find the absolute path to my Python interpreter?

Run which python3 (or which python) in a terminal and copy the full path, for example /usr/bin/python3 or /home/user/.venv/bin/python. Use that exact path in the crontab line — never the bare python, because cron's minimal PATH cannot resolve it.

Build cron expressions without guessing

Generate, translate and verify any cron string — with the next 5 run times shown instantly, 100% in your browser.

Related guides