Cron Jobs with Python's schedule Library and APScheduler

System cron is great for independent scripts, but when your scheduled task needs to live inside a running Python application — sharing a database pool, an in-memory cache, or application config — an in-process scheduler is often simpler. Python has two popular options: the tiny schedule library for simple cases, and APScheduler for anything that needs real cron syntax or persistence.

Option 1: the schedule library (simple, readable, no cron syntax)

Install with pip install schedule. It uses a fluent, human-readable API instead of cron strings — great for small scripts and simple intervals:

import schedule
import time

def sync_orders():
    print("Syncing orders...")

def send_daily_report():
    print("Sending daily report...")

schedule.every(10).minutes.do(sync_orders)
schedule.every().day.at("09:00").do(send_daily_report)
schedule.every().monday.at("07:30").do(lambda: print("Weekly cleanup"))

while True:
    schedule.run_pending()
    time.sleep(1)

The catch: schedule requires you to keep a process alive running that while True loop — nothing runs unless your script is actively executing. It also has no built-in persistence, so scheduled state resets every time the process restarts, and there's no native way to write full cron expressions like 0 9 * * 1-5.

Option 2: APScheduler (real cron syntax, background execution, persistence)

Install with pip install apscheduler. APScheduler supports three trigger types — interval, date (one-off), and cron — and can run jobs in a background thread inside a long-running app like a Flask or FastAPI service:

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

scheduler = BackgroundScheduler(timezone="Asia/Kolkata")

def sync_orders():
    print("Syncing orders...")

# Real cron-style fields as keyword arguments
scheduler.add_job(sync_orders, CronTrigger(minute="*/10"))

# 9:00 AM on weekdays — equivalent to cron "0 9 * * 1-5"
scheduler.add_job(
    lambda: print("Weekday report"),
    CronTrigger(hour=9, minute=0, day_of_week="mon-fri"),
)

# Parse a traditional 5-field cron string directly
scheduler.add_job(
    lambda: print("Midnight backup"),
    CronTrigger.from_crontab("0 0 * * *"),
)

scheduler.start()

Persisting jobs across restarts

APScheduler can store job definitions in a database (SQLite, PostgreSQL, Redis) via a job store, so scheduled jobs survive an app restart instead of being redefined only in code:

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore

scheduler = BackgroundScheduler(
    jobstores={'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')}
)
scheduler.start()

Choosing between schedule, APScheduler, and system cron

  • schedule — quick scripts, single-server, don't need real cron syntax, comfortable keeping a process alive.
  • APScheduler — need real cron expressions, background execution inside a bigger app, or persistence across restarts.
  • System cron — jobs are standalone scripts, you want the OS itself (not your app process) to guarantee execution, and you don't need to share Python in-memory state.

A common production pattern on Linux is actually hybrid: use system cron to invoke a lightweight Python entrypoint script for reliability and OS-level guarantees, and use APScheduler only for schedules that must live and adapt inside a running web service (e.g. schedules configurable by end users at runtime).

Frequently Asked Questions

What is the difference between Python schedule and APScheduler?

The schedule library is lightweight and uses a simple fluent API for basic interval-based jobs, but it requires your own while loop and has no persistence. APScheduler is more powerful, supports true cron-style expressions, can persist jobs to a database, and can run jobs in background threads without a manual loop.

Can APScheduler use real cron expressions?

Yes. APScheduler's CronTrigger accepts standard cron-style fields (minute, hour, day, month, day_of_week) as keyword arguments, and also supports a shorthand CronTrigger.from_crontab() method that parses a traditional 5-field cron string directly.

Should I use Python scheduling instead of system cron?

Use Python scheduling libraries when your jobs need to share in-process state, Python objects, or a database connection pool with the rest of your application, or when you need cross-platform scheduling that doesn't depend on the host having cron installed (e.g. Windows). Use system cron when jobs are independent scripts and you want the OS to guarantee they run even if your app crashes.

Try the Free Cron Expression Generator

Get a valid cron expression in plain English, then drop it straight into CronTrigger.from_crontab() or your system crontab.

Related articles