What Is a Cron Job?
A cron job is a scheduled task managed by the cron daemon — a background service that runs continuously on Linux and Unix-based systems. The name comes from the Greek word chronos (time). Cron reads a configuration file called a crontab (cron table) that lists tasks and when to run them, then executes them automatically at the right time.
Cron is the go-to tool for:
- Running Python or Bash scripts on a schedule (backups, scrapers, reports)
- Clearing temporary files and logs periodically
- Sending automated emails or notifications
- Pulling data from APIs every hour or every morning
- Restarting services or checking server health at intervals
- Running database cleanup queries nightly
Is cron available on my server? Cron comes pre-installed on virtually every Linux distribution — Ubuntu, Debian, CentOS, AlmaLinux, Rocky Linux and more. Check it is running with: systemctl status cron (Debian/Ubuntu) or systemctl status crond (CentOS/RHEL).
Understanding Cron Syntax
Every cron job is defined by a single line in the crontab file. The line has five time fields followed by the command to run. Understanding what each field means is the foundation of using cron effectively.
Special Characters
Five special characters let you build any schedule imaginable:
| Character | Meaning | Example |
|---|---|---|
* | Every possible value | * * * * * — every minute |
, | List of values | 0 9,18 * * * — at 9 AM and 6 PM |
- | Range of values | 0 9-17 * * * — every hour 9 AM–5 PM |
/ | Step / interval | */15 * * * * — every 15 minutes |
L | Last (day of month/week) | 0 0 L * * — last day of month (some cron variants) |
Essential Crontab Commands
All cron management happens through the crontab command. You never edit the crontab file directly — always use these commands:
# Open your crontab for editing (creates one if it doesn't exist)
crontab -e
# List all your current cron jobs
crontab -l
# Remove/delete your entire crontab (CAREFUL — no confirmation!)
crontab -r
# Edit another user's crontab (root only)
crontab -u username -e
# View another user's crontab (root only)
crontab -u username -l
# Check if the cron service is running
systemctl status cron # Debian / Ubuntu
systemctl status crond # CentOS / RHEL / AlmaLinux
# Start / restart cron service
sudo systemctl restart cron
Never run crontab -r when you meant crontab -e. The -r flag silently deletes your entire crontab with no confirmation prompt. Always back up your crontab first: crontab -l > crontab_backup.txt
Your First Cron Job — Step by Step
Open your crontab
Run crontab -e in your terminal. The first time, it will ask you to choose an editor — select nano if you are a beginner (option 1 on most systems).
Write your cron expression
Add a new line at the bottom of the file. For example, to run a Python script every day at 2 AM: 0 2 * * * /usr/bin/python3 /home/user/myscript.py
Use absolute paths
Cron runs in a minimal environment. Always use full absolute paths for both the command and any files it references. Find Python's path with which python3.
Save and exit
In nano: press Ctrl+O then Enter to save, then Ctrl+X to exit. Cron will display "crontab: installing new crontab" confirming your changes are active.
Make your script executable
If running a shell script, make it executable first: chmod +x /home/user/myscript.sh. Python scripts called via /usr/bin/python3 do not need this.
20+ Real-World Cron Job Examples
Copy and adapt any of these examples directly into your crontab:
# ── Every minute ──────────────────────────────
* * * * * /usr/bin/python3 /home/user/monitor.py
# ── Every 5 minutes ───────────────────────────
*/5 * * * * /home/user/check_api.sh
# ── Every 15 minutes ──────────────────────────
*/15 * * * * /usr/bin/php /var/www/html/cron.php
# ── Every hour (at :00) ───────────────────────
0 * * * * /home/user/hourly_task.py
# ── Every day at midnight ─────────────────────
0 0 * * * /home/user/daily_backup.sh
# ── Every day at 2:30 AM ──────────────────────
30 2 * * * /usr/bin/python3 /home/user/scraper.py
# ── Every weekday (Mon–Fri) at 9 AM ───────────
0 9 * * 1-5 /home/user/send_report.py
# ── Every Monday at 8 AM ─────────────────────
0 8 * * 1 /home/user/weekly_digest.sh
# ── First day of every month ─────────────────
0 0 1 * * /home/user/monthly_invoice.py
# ── Every January 1st at noon ────────────────
0 12 1 1 * /home/user/yearly_cleanup.sh
# ── Every day at 9 AM and 6 PM ───────────────
0 9,18 * * * /home/user/check_prices.py
# ── Every 2 hours ────────────────────────────
0 */2 * * * /home/user/sync_data.sh
# ── Weekdays between 9 AM–5 PM, every hour ───
0 9-17 * * 1-5 /home/user/office_hours_task.py
# ── Every Sunday at 3 AM (DB backup) ─────────
0 3 * * 0 mysqldump -u root mydb > /backups/mydb.sql
# ── Clear temp files every night ─────────────
0 1 * * * find /tmp -type f -mtime +7 -delete
# ── Restart Nginx every Sunday at 4 AM ───────
0 4 * * 0 systemctl restart nginx
# ── Pull latest code from GitHub daily ───────
0 3 * * * cd /var/www/myapp && git pull origin main
# ── Run Laravel scheduler (every minute) ─────
* * * * * cd /var/www/laravel && php artisan schedule:run >> /dev/null 2>&1
# ── Python virtualenv script ─────────────────
0 6 * * * /home/user/myapp/venv/bin/python /home/user/myapp/run.py
Special Cron Strings (Shortcuts)
Most modern cron implementations support special @strings as convenient aliases for common schedules. These are much easier to read than five-field expressions:
| String | Equivalent | Runs |
|---|---|---|
@reboot | — | Once at system startup |
@yearly | 0 0 1 1 * | Once a year, Jan 1st midnight |
@annually | 0 0 1 1 * | Same as @yearly |
@monthly | 0 0 1 * * | First day of each month, midnight |
@weekly | 0 0 * * 0 | Every Sunday at midnight |
@daily | 0 0 * * * | Every day at midnight |
@midnight | 0 0 * * * | Same as @daily |
@hourly | 0 * * * * | Every hour at :00 |
# Run a script on every server reboot
@reboot /home/user/start_services.sh
# Daily database backup
@daily /home/user/backup_db.sh
# Weekly log cleanup
@weekly find /var/log/myapp -name "*.log" -mtime +30 -delete
# Monthly report generation
@monthly /usr/bin/python3 /home/user/monthly_report.py
Environment Variables in Cron
One of the most common reasons cron jobs fail silently is missing environment variables. Cron runs in a stripped-down shell environment — it does not load your ~/.bashrc or ~/.profile, so variables like PATH, HOME and any custom variables you rely on are often not available.
# Set environment variables at the TOP of your crontab
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/home/youruser
MAILTO="" # Suppress email output
# Custom variables (API keys, app paths)
APP_DIR=/var/www/myapp
API_KEY=your_api_key_here
# Now your jobs can use these variables
0 6 * * * cd $APP_DIR && /usr/bin/python3 run.py
Best practice: Set MAILTO="" at the top of your crontab to silence the default behaviour of emailing output to the local root mailbox. Instead, redirect output to a log file explicitly in each job line.
Logging and Debugging Cron Jobs
When a cron job does not run as expected, logging is your only debugging tool. Always redirect both standard output and standard error to a log file so you can see exactly what happened.
# Redirect stdout AND stderr to a log file (append mode)
0 2 * * * /usr/bin/python3 /home/user/script.py >> /home/user/logs/script.log 2>&1
# Separate stdout and stderr logs
0 2 * * * /home/user/script.sh >> /logs/out.log 2>> /logs/err.log
# Discard all output (silent mode)
0 2 * * * /home/user/script.sh > /dev/null 2>&1
# Timestamped log entry
0 2 * * * echo "[$(date)] Starting job" >> /logs/job.log && /home/user/script.py >> /logs/job.log 2>&1
Checking System Cron Logs
# View all cron-related entries in syslog (Debian/Ubuntu)
grep CRON /var/log/syslog
# Watch cron log in real time
tail -f /var/log/syslog | grep CRON
# On systems using journald (CentOS/RHEL)
journalctl -u crond -f
# Watch your script's log file in real time
tail -f /home/user/logs/script.log
Troubleshooting — Why Is My Cron Job Not Running?
Here are the most common causes of cron jobs failing and how to fix each one:
| Problem | Cause | Fix |
|---|---|---|
| Job never runs | Script not executable | chmod +x /path/to/script.sh |
| Command not found | Relative path used | Use full path — run which python3 to find it |
| Script runs manually but not in cron | Missing environment vars | Set PATH at top of crontab or source your profile in the script |
| Cron service not running | Daemon stopped | sudo systemctl start cron |
| Wrong timezone | Server in UTC, you expect local time | Add CRON_TZ=America/New_York at top of crontab |
| Syntax error in crontab | Malformed expression | Validate at WebTigers Cron Generator |
| Script output filling disk | No output redirection | Add >> /log/file.log 2>&1 or > /dev/null 2>&1 |
Cron with Python Scripts
Python is one of the most popular languages for cron automation. Here are the key patterns for running Python scripts reliably via cron — especially when using virtual environments.
# ── System Python ──────────────────────────────────────
0 6 * * * /usr/bin/python3 /home/user/script.py >> /home/user/cron.log 2>&1
# ── Virtual Environment (recommended) ──────────────────
# Use the venv's python directly — no need to activate
0 6 * * * /home/user/myproject/venv/bin/python /home/user/myproject/script.py >> /home/user/cron.log 2>&1
# ── Change directory first (for relative imports) ──────
0 6 * * * cd /home/user/myproject && /home/user/myproject/venv/bin/python script.py >> cron.log 2>&1
# ── Multiple commands in sequence ──────────────────────
0 6 * * * cd /home/user/myproject && git pull && venv/bin/python run.py >> logs/run.log 2>&1
# ── Email scraper example (from our other tutorial) ────
0 2 * * * /home/user/email-scraper/venv/bin/python /home/user/email-scraper/pipeline.py >> /home/user/email-scraper/logs/scraper.log 2>&1
Cron with PHP and Laravel
PHP applications often use cron for scheduled jobs. Laravel has its own scheduler that requires a single cron entry — all other schedules are defined in PHP code.
# ── Plain PHP script ───────────────────────────────────
0 0 * * * /usr/bin/php /var/www/html/cron/daily_cleanup.php >> /var/log/php-cron.log 2>&1
# ── Laravel Scheduler (single entry, handles all tasks) ─
* * * * * cd /var/www/laravel && php artisan schedule:run >> /dev/null 2>&1
# ── WordPress WP-Cron via CLI (bypass HTTP trigger) ────
*/5 * * * * /usr/bin/php /var/www/wordpress/wp-cron.php > /dev/null 2>&1