Python has four credible ways to schedule work on a clock, and they cover different production realities. You can drop a script in crontab, run APScheduler inside a long-lived process, lean on a platform scheduler like AWS EventBridge, or expose an HTTP endpoint and let an external cron service like Crontap hit it on cadence.
This guide is the side-by-side walkthrough with code, plus when to pick which. For Linux crontab basics, see What is a cron job in Linux?.
Path 1: crontab -e on a Linux box
The classic shape: a script on disk and cron firing it.
# crontab -e
15 2 * * * /usr/bin/python3 /opt/app/nightly_sync.py >> /var/log/nightly_sync.log 2>&1Use the full path to python3. Cron's PATH is tiny. If you use uv or poetry, wrap the command:
15 2 * * * cd /opt/app && /usr/local/bin/uv run python nightly_sync.py >> /var/log/nightly_sync.log 2>&1Pick this when: one server, one team, SSH access, and you are fine grepping logs when something breaks at 3am.
Path 2: APScheduler or schedule in a long-running process
APScheduler runs inside your Python app. The process must stay up.
from apscheduler.schedulers.blocking import BlockingScheduler
def nightly_sync():
# your job
scheduler = BlockingScheduler()
scheduler.add_job(nightly_sync, "cron", hour=2, minute=15)
scheduler.start()The lighter schedule library fits the same model with a simpler API.
Pick this when: you already run a always-on worker and want scheduling colocated with app code. Avoid when: the process is allowed to die (serverless, spot instances, deploys that restart the box).
Path 3: Platform schedulers (Lambda, Cloud Run, Vercel)
Cloud vendors ship cron-shaped triggers:
- AWS: EventBridge rule → Lambda (see cron jobs for AWS Lambda)
- GCP: Cloud Scheduler → Cloud Run
- Vercel:
vercel.jsoncrons (hourly floor on Hobby)
Pick this when: the workload already lives on that platform and you want billing tied to invocations. Watch for: IAM setup, cold starts, and per-platform timezone quirks.
Path 4: External HTTP cron (Crontap)
Deploy a small HTTP route that runs the job. Crontap fires it on schedule with retries and alerts.
Why this beats local cron for many production apps
- No always-on Python process just to hold a scheduler
- Retries on 5xx with backoff
- Failure alerts to Slack, Discord, Telegram, or email
- Per-schedule IANA timezones
- Authorization headers stored in the dashboard, not in crontab
Flask example
import os
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["CRON_SECRET"]
@app.post("/internal/nightly-sync")
def nightly_sync():
if request.headers.get("Authorization") != f"Bearer {SECRET}":
abort(401)
# run sync
return "", 204In Crontap: POST https://api.yourapp.com/internal/nightly-sync, header Authorization: Bearer <secret>, cron 15 2 * * *, timezone America/New_York.
FastAPI example
from fastapi import FastAPI, Header, HTTPException
app = FastAPI()
@app.post("/internal/nightly-sync")
async def nightly_sync(authorization: str = Header()):
if authorization != f"Bearer {os.environ['CRON_SECRET']}":
raise HTTPException(status_code=401)
await run_sync()
return {"ok": True}Django management command as an endpoint
Expose a locked-down view that calls call_command("nightly_sync"). Keep the command idempotent (see below).
Fix this in 60 seconds with Crontap. Free forever tier. Three schedules. No credit card. Schedule your first job →
How to make a Python cron idempotent
Cron fires whether or not yesterday's run finished. Guard with:
- A database lock row (
SELECT ... FOR UPDATE) - A short-lived Redis key (
SET sync:lock NX EX 3600) - Writing job state before side effects
If two runs overlap, the second should no-op cleanly, not double-charge customers.
How to monitor a Python cron with Crontap
- Loud failures (4xx/5xx): Crontap schedule failure alerts
- Public health: Crontap uptime on your API
- Silent misses (job never ran): optional dead-man ping; see Dead man's switch, explained for developers
FAQ
Can I use cron with uv or poetry?
Yes, but not bare python in crontab. cd to the project and invoke uv run or poetry run with full paths.
How do I handle long-running Python jobs?
Set a timeout in Crontap above your p99 runtime. For jobs longer than HTTP cron allows, split into enqueue + worker, or use a queue (Celery, RQ) triggered by a short HTTP handler.
How do I keep secrets out of crontab?
Use Path 4: secrets live in your app's environment; Crontap stores only the bearer token for the schedule.
Related on Crontap
- Docker cron jobs. When your Python app runs in containers.
- cPanel cron jobs. Shared hosting setups.
- Cron job not running?. Debug checklist.
- Scheduled AI jobs. Python + LLM pipelines on a clock.
Fix this in 60 seconds with Crontap. Free forever tier. Three schedules. No credit card. Schedule your first job →
