If you've ever wanted your computer to automatically run a task — like backing up a database every night, sending a report every Monday, or clearing temp files every hour — cron jobs are how you do it. Cron is one of the most powerful and widely-used scheduling tools in the Linux/Unix world, and once you understand it, you'll wonder how you ever lived without it.
What is a Cron Job?
A cron job is a scheduled task that runs automatically at specified times or intervals on Unix-based systems (Linux, macOS). The word "cron" comes from the Greek word chronos, meaning time. The cron daemon (crond) runs in the background and checks every minute if there's a job to execute.
You define cron jobs in a file called the crontab (cron table). Each line in the crontab represents one scheduled task.
The Cron Syntax — 5 Fields
Every cron expression has exactly 5 time fields followed by the command to run:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 7, where 0 and 7 = Sunday)
│ │ │ │ │
* * * * * command_to_run
The * (asterisk) means "every" — so * * * * * means "every minute of every hour of every day of every month on every day of the week."
Special Characters
Before we dive into examples, here are the special characters you'll use:
*— Every possible value (wildcard),— List separator (e.g.,1,3,5means 1 and 3 and 5)-— Range (e.g.,1-5means 1 through 5)/— Step value (e.g.,*/10means every 10th unit)
Basic Examples — Getting Started
Example 1: Run every minute
* * * * * /home/user/scripts/check-health.sh
This runs check-health.sh every single minute, 24/7. Useful for monitoring scripts.
Example 2: Run every hour (at minute 0)
0 * * * * /home/user/scripts/sync-data.sh
Runs at the top of every hour: 1:00, 2:00, 3:00, etc.
Example 3: Run once a day at midnight
0 0 * * * /home/user/scripts/daily-backup.sh
Runs at exactly 00:00 (midnight) every day. Perfect for nightly backups.
Example 4: Run once a day at 2:30 AM
30 2 * * * /home/user/scripts/cleanup.sh
Runs at 2:30 AM every day. Great for maintenance tasks during low-traffic hours.
Weekly & Monthly Examples
Example 5: Run every Monday at 9:00 AM
0 9 * * 1 /home/user/scripts/weekly-report.sh
Day of week: 0 = Sunday, 1 = Monday, ..., 6 = Saturday. This sends your weekly report every Monday morning.
Example 6: Run every weekday (Mon-Fri) at 8:00 AM
0 8 * * 1-5 /home/user/scripts/morning-alerts.sh
The 1-5 range covers Monday through Friday. No weekend noise.
Example 7: Run on the 1st of every month at midnight
0 0 1 * * /home/user/scripts/monthly-invoice.sh
Runs at midnight on the 1st day of each month. Ideal for monthly billing, reports, or cleanup.
Example 8: Run on the 1st and 15th of every month
0 0 1,15 * * /home/user/scripts/bimonthly-task.sh
The comma lets you specify multiple days. This runs twice a month — on the 1st and 15th.
Interval Examples — Every N Minutes/Hours
Example 9: Run every 5 minutes
*/5 * * * * /home/user/scripts/check-queue.sh
The */5 means "every 5th minute." Runs at :00, :05, :10, :15, etc.
Example 10: Run every 15 minutes
*/15 * * * * /home/user/scripts/poll-api.sh
Runs at :00, :15, :30, :45 of every hour.
Example 11: Run every 2 hours
0 */2 * * * /home/user/scripts/cache-refresh.sh
Runs at 00:00, 02:00, 04:00, 06:00, etc. Note the 0 in the minute field — without it, the job would run every minute during those hours!
Example 12: Run every 30 minutes during business hours (9 AM - 6 PM)
*/30 9-18 * * * /home/user/scripts/business-check.sh
Combines a step value (*/30) with an hour range (9-18). Only runs during working hours.
Specific Time Examples
Example 13: Run at 6:00 AM and 6:00 PM every day
0 6,18 * * * /home/user/scripts/twice-daily.sh
The comma in the hour field gives you two specific times.
Example 14: Run every Sunday at 11:30 PM
30 23 * * 0 /home/user/scripts/weekly-cleanup.sh
End-of-week cleanup right before Monday begins.
Example 15: Run at 3:15 AM on the first Monday of each month
15 3 1-7 * 1 /home/user/scripts/first-monday.sh
This is a clever trick: it targets days 1-7 (first week of the month) AND Mondays. The job only fires when both conditions overlap — the first Monday.
Real-World Use Cases
Example 16: Database backup every night at 3 AM
0 3 * * * pg_dump mydb > /backups/mydb_$(date +\%Y\%m\%d).sql 2>&1
Creates a dated backup file like mydb_20260403.sql. The \% escaping is needed in crontab because % has a special meaning (newline).
Example 17: Clear log files older than 7 days
0 4 * * * find /var/log/myapp -name "*.log" -mtime +7 -delete
Runs at 4 AM daily and removes log files older than 7 days. Keeps your disk from filling up.
Example 18: Restart a service every 6 hours
0 */6 * * * systemctl restart myapp.service
Restarts at midnight, 6 AM, noon, and 6 PM. Useful for apps with memory leaks you haven't fixed yet.
Example 19: Send a disk space alert if usage exceeds 90%
*/30 * * * * df -h / | awk 'NR==2 {if ($5+0 > 90) print "DISK ALERT: " $5 " used"}' | mail -s "Disk Alert" admin@example.com
Checks every 30 minutes and emails an alert if the root partition is over 90% full.
Example 20: Pull latest code and restart app (simple CI/CD)
*/10 * * * * cd /var/www/myapp && git pull origin main && systemctl restart myapp
A simple (but effective) deploy pipeline that checks for new code every 10 minutes.
Shortcut Strings
Most cron implementations support these convenient shortcuts:
@reboot— Run once at startup@yearlyor@annually— Same as0 0 1 1 *(Jan 1st, midnight)@monthly— Same as0 0 1 * *(1st of month, midnight)@weekly— Same as0 0 * * 0(Sunday, midnight)@dailyor@midnight— Same as0 0 * * *@hourly— Same as0 * * * *
Example 21: Run a script at system boot
@reboot /home/user/scripts/start-services.sh
Perfect for starting background processes after a server reboot.
Managing Cron Jobs
Here are the essential commands for working with crontab:
# Edit your crontab
crontab -e
# List all your cron jobs
crontab -l
# Remove all your cron jobs (use with caution!)
crontab -r
# Edit crontab for a specific user (requires root)
crontab -u username -e
Cron Jobs on Different Platforms
Cron syntax is universal, but the setup differs across operating systems and environments. Here's how to get cron running on each platform.
Linux (Ubuntu / Debian / CentOS / RHEL)
Cron is pre-installed on virtually all Linux distributions. The cron daemon (crond or cron) runs automatically.
# Check if cron is running
systemctl status cron # Ubuntu/Debian
systemctl status crond # CentOS/RHEL
# If not running, start and enable it
sudo systemctl start cron
sudo systemctl enable cron
# Edit your crontab
crontab -e
# Add your jobs — e.g., backup every night at 2 AM
0 2 * * * /home/deploy/scripts/backup.sh >> /var/log/backup.log 2>&1
# System-wide cron jobs go in /etc/crontab or /etc/cron.d/
# These require specifying the user:
# min hour day month dow USER command
0 3 * * * root /usr/local/bin/cleanup.sh
# You can also drop scripts into these directories:
# /etc/cron.daily/ — runs once a day
# /etc/cron.hourly/ — runs once an hour
# /etc/cron.weekly/ — runs once a week
# /etc/cron.monthly/ — runs once a month
sudo cp my-script.sh /etc/cron.daily/
sudo chmod +x /etc/cron.daily/my-script.sh
macOS
macOS has cron built-in, but Apple recommends launchd for modern scheduling. Both work — here's how to use each.
# Option 1: crontab (works exactly like Linux)
crontab -e
# Add: 0 9 * * 1-5 /Users/you/scripts/morning-report.sh
# ⚠️ macOS may prompt for "Full Disk Access" — grant it in:
# System Settings → Privacy & Security → Full Disk Access → cron
# Option 2: launchd (Apple's recommended approach)
# Create a plist file:
cat > ~/Library/LaunchAgents/com.you.backup.plist << 'PLIST'
Label
com.you.backup
ProgramArguments
/Users/you/scripts/backup.sh
StartCalendarInterval
Hour
2
Minute
0
StandardOutPath
/tmp/backup.log
StandardErrorPath
/tmp/backup-error.log
PLIST
# Load the job
launchctl load ~/Library/LaunchAgents/com.you.backup.plist
# Unload (disable)
launchctl unload ~/Library/LaunchAgents/com.you.backup.plist
# List all loaded jobs
launchctl list | grep com.you
Windows
Windows doesn't have cron, but Task Scheduler provides the same functionality. You can set it up via GUI or command line.
# PowerShell: Create a scheduled task (equivalent of a cron job)
# Example: Run a Python script every day at 3 AM
# Create action, trigger, and settings
$action = New-ScheduledTaskAction -Execute "C:\Python312\python.exe" -Argument "C:\scripts\daily-backup.py"
$trigger = New-ScheduledTaskTrigger -Daily -At 3:00AM
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd
# Register the task
Register-ScheduledTask -TaskName "DailyBackup" -Action $action -Trigger $trigger -Settings $settings -Description "Runs daily backup at 3 AM"
# List all scheduled tasks
Get-ScheduledTask | Where-Object {$_.TaskName -like "*Backup*"}
# Run a task immediately (for testing)
Start-ScheduledTask -TaskName "DailyBackup"
# Delete a task
Unregister-ScheduledTask -TaskName "DailyBackup" -Confirm:$false
# ─────────────────────────────────────────────
# Alternative: Use schtasks.exe (works in CMD too)
schtasks /create /tn "DailyBackup" /tr "python C:\scripts\backup.py" ^
/sc daily /st 03:00
# Using WSL? You can use Linux cron inside WSL:
wsl crontab -e
Docker
Running cron inside Docker requires a slightly different approach since containers are single-process by default.
# Dockerfile with cron
FROM python:3.12-slim
# Install cron
RUN apt-get update && apt-get install -y cron && rm -rf /var/lib/apt/lists/*
# Copy your scripts
COPY scripts/ /app/scripts/
RUN chmod +x /app/scripts/*.sh
# Create the crontab file
COPY crontab /etc/cron.d/app-cron
RUN chmod 0644 /etc/cron.d/app-cron
RUN crontab /etc/cron.d/app-cron
# Create log file
RUN touch /var/log/cron.log
# Start cron in the foreground + tail logs
CMD cron && tail -f /var/log/cron.log
# crontab file (placed at project root)
# Note: cron in Docker doesn't inherit ENV vars — pass them explicitly
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
# Run every 5 minutes
*/5 * * * * /app/scripts/health-check.sh >> /var/log/cron.log 2>&1
# Daily backup at midnight
0 0 * * * /app/scripts/backup.sh >> /var/log/cron.log 2>&1
# IMPORTANT: Must have an empty line at the end
# Better approach: Use Docker's --restart with a lightweight scheduler
# or use a sidecar pattern in Docker Compose:
# docker-compose.yml
version: '3.8'
services:
app:
build: .
# ... your main app
scheduler:
build: .
command: cron -f # Run cron in foreground
volumes:
- ./scripts:/app/scripts
depends_on:
- app
Kubernetes CronJob
Kubernetes has a first-class CronJob resource that runs Jobs on a cron schedule. This is the production-grade way to run scheduled tasks in a cluster — no need to install cron in your containers.
# cronjob.yaml — Kubernetes CronJob manifest
apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
namespace: production
spec:
schedule: "0 3 * * *" # Every day at 3 AM (same cron syntax!)
timeZone: "America/New_York" # K8s 1.27+ supports time zones
concurrencyPolicy: Forbid # Don't start new if previous still running
successfulJobsHistoryLimit: 3 # Keep last 3 successful runs
failedJobsHistoryLimit: 5 # Keep last 5 failed runs
startingDeadlineSeconds: 600 # Skip if more than 10 min late
jobTemplate:
spec:
backoffLimit: 3 # Retry up to 3 times on failure
activeDeadlineSeconds: 3600 # Kill if running longer than 1 hour
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: your-registry/db-backup:latest
command:
- /bin/sh
- -c
- |
echo "Starting backup at $(date)"
pg_dump $DATABASE_URL > /backups/db-$(date +%Y%m%d-%H%M%S).sql
echo "Backup completed at $(date)"
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
volumeMounts:
- name: backup-storage
mountPath: /backups
volumes:
- name: backup-storage
persistentVolumeClaim:
claimName: backup-pvc
# Apply it
kubectl apply -f cronjob.yaml
# Check the CronJob
kubectl get cronjobs -n production
# NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE
# database-backup 0 3 * * * False 0 3h
# List Jobs created by this CronJob
kubectl get jobs -n production -l job-name=database-backup
# NAME COMPLETIONS DURATION AGE
# database-backup-28571234 1/1 45s 3h
# Check logs from the latest run
kubectl logs job/database-backup-28571234 -n production
# Trigger a manual run (useful for testing)
kubectl create job --from=cronjob/database-backup manual-backup-test -n production
# Suspend a CronJob (pause without deleting)
kubectl patch cronjob database-backup -n production -p '{"spec":{"suspend":true}}'
# Resume
kubectl patch cronjob database-backup -n production -p '{"spec":{"suspend":false}}'
# Delete
kubectl delete cronjob database-backup -n production
Kubernetes CronJob — Advanced Patterns
# Pattern 1: CronJob with resource limits and monitoring
apiVersion: batch/v1
kind: CronJob
metadata:
name: report-generator
spec:
schedule: "0 9 * * 1" # Every Monday at 9 AM
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: report
image: your-registry/reports:latest
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
envFrom:
- configMapRef:
name: report-config
- secretRef:
name: report-secrets
---
# Pattern 2: CronJob with init container (wait for dependency)
apiVersion: batch/v1
kind: CronJob
metadata:
name: data-sync
spec:
schedule: "*/30 * * * *" # Every 30 minutes
concurrencyPolicy: Replace # Kill previous if still running
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
initContainers:
- name: wait-for-api
image: busybox
command: ['sh', '-c', 'until wget -q -O- http://api-service:8080/health; do sleep 2; done']
containers:
- name: sync
image: your-registry/data-sync:latest
command: ["python", "sync.py"]
---
# Pattern 3: CronJob that sends Slack alerts on failure
apiVersion: batch/v1
kind: CronJob
metadata:
name: health-monitor
spec:
schedule: "*/5 * * * *" # Every 5 minutes
jobTemplate:
spec:
backoffLimit: 0 # Don't retry — alert immediately
template:
spec:
restartPolicy: Never
containers:
- name: monitor
image: curlimages/curl
command:
- /bin/sh
- -c
- |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health)
if [ "$STATUS" != "200" ]; then
curl -X POST $SLACK_WEBHOOK -H 'Content-Type: application/json' \
-d "{"text": "🚨 API health check failed! Status: $STATUS"}"
exit 1
fi
echo "Health check passed: $STATUS"
env:
- name: SLACK_WEBHOOK
valueFrom:
secretKeyRef:
name: slack-config
key: webhook-url
Cron vs Kubernetes CronJob — When to Use Which
Common Mistakes to Avoid
- Forgetting the full path: Cron runs with a minimal environment. Always use absolute paths like
/usr/bin/python3instead of justpython3. - Not escaping %: In crontab,
%is treated as a newline. Escape it with\%when using date formats. - Missing output redirection: Cron emails output by default. Redirect to a file or
/dev/nullto avoid mailbox spam:
# Log output to a file
0 3 * * * /scripts/backup.sh >> /var/log/backup.log 2>&1
# Discard output completely
0 3 * * * /scripts/backup.sh > /dev/null 2>&1
- Not setting PATH: Add a PATH variable at the top of your crontab if your scripts depend on it:
PATH=/usr/local/bin:/usr/bin:/bin
0 3 * * * backup.sh
Quick Reference Cheat Sheet
Expression Description
────────────────── ──────────────────────────────
* * * * * Every minute
*/5 * * * * Every 5 minutes
0 * * * * Every hour
0 0 * * * Every day at midnight
0 0 * * 0 Every Sunday at midnight
0 0 1 * * First day of every month
0 0 1 1 * Once a year (Jan 1st)
0 9-17 * * 1-5 Every hour, Mon-Fri, 9AM-5PM
*/10 * * * * Every 10 minutes
0 6,18 * * * At 6 AM and 6 PM
0 0 1,15 * * 1st and 15th of each month
Cron jobs are one of those tools that, once mastered, become an essential part of your DevOps toolkit. Whether you're automating backups, scheduling reports, managing deployments, or monitoring systems, cron has been doing it reliably for over 40 years — and it's not going anywhere.