☕ Buy a Coffee
Home / Productivity & Coding

How to Use 'Crontab' to Schedule a Python Script

Automate background web scrapers, database dumps, and telemetry reporting jobs on Linux servers using cron syntax.

Sachin Siju
Sachin Siju
Lead Systems Engineer & Tech Blogger
Jul 20, 2026 4 min read
How to Use 'Crontab' to Schedule a Python Script

Why Cron Is Still the Right Tool

For recurring background jobs on a Linux server — scraping a site every hour, dumping a database nightly, pushing telemetry every five minutes — cron remains the simplest reliable scheduler. It's built into every mainstream Linux distribution, requires no dependencies, and survives reboots as long as the cron daemon is enabled. The syntax trips people up occasionally, but it's five fields and a handful of conventions once you've seen it a couple of times.

Understanding Crontab Syntax

Each line in a crontab follows this structure:

* * * * * command-to-run
│ │ │ │ │
│ │ │ │ └── day of week (0-6, Sunday=0, or names: sun,mon,tue...)
│ │ │ └──── month (1-12)
│ │ └────── day of month (1-31)
│ └──────── hour (0-23)
└────────── minute (0-59)

A few common patterns you'll reuse constantly:

  • 0 * * * * — every hour, on the hour
  • */15 * * * * — every 15 minutes
  • 0 2 * * * — every day at 2:00 AM
  • 0 0 * * 0 — every Sunday at midnight
  • 0 9 1 * * — 9:00 AM on the first day of every month

Preparing the Python Script

Before scheduling anything, make sure the script runs cleanly from an absolute path with no reliance on an interactive shell environment — cron runs jobs with a minimal environment, not your login shell's environment.

#!/usr/bin/env python3
"""Nightly database dump script."""
import subprocess
import datetime
import logging

logging.basicConfig(
    filename="/var/log/db_backup.log",
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)

def main():
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    output_path = f"/backups/db_dump_{timestamp}.sql"
    try:
        subprocess.run(
            ["pg_dump", "-U", "backup_user", "-d", "production", "-f", output_path],
            check=True,
        )
        logging.info("Backup succeeded: %s", output_path)
    except subprocess.CalledProcessError as e:
        logging.error("Backup failed: %s", e)
        raise

if __name__ == "__main__":
    main()

Make it executable so cron (and you) can run it directly:

chmod +x /opt/scripts/db_backup.py

Editing the Crontab

Open the current user's crontab for editing:

crontab -e

The first time you run this, it'll ask which editor to use (nano is the friendliest default if you're unsure). Add a line for your job — this example runs the backup script every night at 2:30 AM:

30 2 * * * /usr/bin/python3 /opt/scripts/db_backup.py
Warning: Always use the full absolute path to both the Python interpreter and the script. Cron does not source your .bashrc or .profile, so python3 alone may resolve to nothing, or to a different interpreter than the one you tested with (particularly relevant if you use pyenv, conda, or a virtualenv — cron won't have activated it). Find the correct path with which python3 and use that exact string.

If your script depends on a virtual environment, point directly at the venv's interpreter instead of activating it in a shell:

*/30 * * * * /opt/scripts/venv/bin/python3 /opt/scripts/scraper.py

Capturing Output and Errors

By default, cron emails any output to the crontab owner via the local mail system — which is often not configured, meaning output silently disappears. Redirect output explicitly instead:

30 2 * * * /usr/bin/python3 /opt/scripts/db_backup.py >> /var/log/db_backup_cron.log 2>&1

This appends both stdout and stderr to a log file, so if a job fails silently at 2:30 AM you have somewhere to look in the morning. Combine this with the logging setup already inside the script (as in the example above) for two layers of visibility — one from the script's own structured logging, one as a raw output capture in case the script crashes before it can log anything itself.

Setting the Environment Explicitly

If your script needs environment variables (API keys, a PATH addition, locale settings), set them at the top of the crontab file rather than assuming they're inherited:

PATH=/usr/local/bin:/usr/bin:/bin
DB_PASSWORD=your_secret_here
30 2 * * * /usr/bin/python3 /opt/scripts/db_backup.py
Tip: For anything more than a couple of environment variables, or for secrets you don't want sitting in plaintext in the crontab file, have the Python script load them from a .env file with python-dotenv or read them from a proper secrets manager instead.

Verifying the Job Ran

List your current crontab entries at any time with:

crontab -l

Check that the cron daemon itself is active:

systemctl status cron    # Debian/Ubuntu
systemctl status crond   # RHEL/CentOS/Fedora

And tail the system cron log to confirm your job actually fired at the expected time (path varies by distro — often /var/log/syslog on Debian-based systems or accessible via journalctl -u cron):

grep CRON /var/log/syslog | tail -20

Wrap-Up

Cron scheduling for a Python script comes down to three habits that prevent 90% of "why didn't this run" debugging sessions: use absolute paths for both the interpreter and the script, redirect stdout/stderr to a log file instead of relying on system mail, and explicitly set any environment variables the script needs rather than assuming cron inherited your shell's environment.

Featured Infrastructure Partner

Deploy on High-Performance Hostinger Cloud

Get up to 75% OFF + free domain & SSL. Powering xube.me's sub-second response times.

Claim Discount ↗

Discussion & Insights

Related Technical Essays