Skip to main content

Scheduling Linux Tasks with Cron — crontab, Systemd Timers, and Production Patterns

Schedule reliable automated tasks on Linux servers using cron and systemd timers with proper logging, error handling, and production-grade automation patterns.

30 Terms

Overview and What You Will Learn

Production servers always have scheduled work. Database backups run nightly. Certificate renewal runs weekly. Log rotation runs daily. Cache warming runs before peak hours. Health checks run every minute. Cron and systemd timers are the two mechanisms that drive all of this.

By the end of this lab you will:

  • Write correct crontab entries using all five time fields
  • Debug the most common cron failure (PATH mismatch)
  • Capture cron job output reliably to log files
  • Create systemd timer units as the modern alternative to cron
  • Build production-grade patterns for backup, log parsing, and health checks

Why This Matters in Production

A cron job that works when run manually but silently fails in cron is one of the most frustrating debugging scenarios in Linux. The cause is almost always the same: the cron execution environment has a minimal PATH that does not include the directories where your tools are installed. Understanding this saves hours of debugging.

At PhonePe, database backups run as cron jobs every night at 1 AM. If those backups silently fail because of a PATH issue, nobody knows until the day they need to restore — and the backup is not there.

Core Principles

Crontab time field syntax:

◈ DIAGRAM
+------------------------------------------+
| minute hour day month weekday |
| 0-59 0-23 1-31 1-12 0-7 |
+------------------------------------------+
| |
| * = every value (any) |
| , = list of values (1,15 = 1st,15th) |
| - = range (1-5 = Mon through Fri) |
| / = step (*/15 = every 15 minutes) |
+------------------------------------------+
Examples:
0 2 * * * Every day at 02:00
*/15 * * * * Every 15 minutes
0 9 * * 1-5 Weekdays at 09:00
0 0 1 * * First day of each month
30 23 * * 0 Sundays at 23:30

Cron vs systemd timers:

◈ DIAGRAM
+------------------------+ +------------------------+
| cron | | systemd timer |
| | | |
| Simple syntax | | More powerful syntax |
| No dependency support | | After= dependencies |
| Output to email | | Output to journald |
| Missed jobs lost | | Persistent= catches up |
| No status command | | systemctl status timer |
+------------------------+ +------------------------+
Use for simple Use for complex
scheduled tasks production services

Detailed Step-by-Step Practical Lab

Milestone 1 — Create and manage crontab entries

Bash
## Open your personal crontab in editor
crontab -e
## List current crontab
crontab -l
## Remove all crontab entries (DANGEROUS -- no confirmation)
## crontab -r
## View root's crontab
sudo crontab -l
## Edit root's crontab
sudo crontab -e
TEXT
## Example crontab entries:
## m h dom mon dow command
## Database backup at 1 AM every night
0 1 * * * /opt/scripts/backup-postgres.sh >> /var/log/backup.log 2>&1
## Health check every 5 minutes
*/5 * * * * /opt/scripts/health-check.sh
## Log cleanup every Sunday at 3 AM
0 3 * * 0 find /var/log/app -name "*.log" -mtime +30 -delete
## Cache warming every morning at 8:45 AM on weekdays
45 8 * * 1-5 /opt/scripts/warm-cache.sh >> /var/log/cache-warm.log 2>&1
## First day of each month -- rotate archived logs
0 0 1 * * /opt/scripts/archive-old-logs.sh
## Every 15 minutes -- metrics collection
*/15 * * * * /opt/scripts/collect-metrics.sh > /dev/null 2>&1

Milestone 2 — Fix the most common cron failure: PATH

Bash
## Test this: does your script work in cron's environment?
## Cron's default PATH is minimal:
## /usr/bin:/bin
## Your interactive shell's PATH is much richer:
echo $PATH
## /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
## Tools often installed in /usr/local/bin:
which node ## /usr/local/bin/node
which python3 ## /usr/bin/python3
which aws ## /usr/local/bin/aws
## The fix: always use absolute paths in cron scripts
## BAD:
## node /opt/app/index.js
## GOOD:
## /usr/local/bin/node /opt/app/index.js
## Or set PATH at the top of your crontab:
## PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
## Or set PATH inside the script itself:
cat /opt/scripts/backup-postgres.sh
Bash
#!/usr/bin/env bash
## Always set explicit PATH at top of cron scripts
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
## Always use strict mode
set -euo pipefail
## Use absolute paths for everything
PGDUMP=/usr/bin/pg_dump
BACKUP_DIR=/opt/backups
DATE=$(date +%Y%m%d-%H%M%S)
$PGDUMP -U postgres mydb > "$BACKUP_DIR/mydb-$DATE.sql"
echo "Backup completed: $BACKUP_DIR/mydb-$DATE.sql"

Milestone 3 — Capture cron output reliably

Bash
## By default cron emails output to the local user
## On a server with no mail configured, output is lost silently
## Option 1: Redirect both stdout and stderr to log file
0 1 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
## Option 2: Redirect to separate files
0 1 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>> /var/log/backup-errors.log
## Option 3: Discard all output (only for idempotent scripts you trust)
*/5 * * * * /opt/scripts/metrics.sh > /dev/null 2>&1
## Option 4: Use logger to send to syslog/journald
0 1 * * * /opt/scripts/backup.sh 2>&1 | logger -t backup-cron
## Then view with:
journalctl -t backup-cron
## Disable cron email for the whole crontab
MAILTO=""
## Or send to a specific address
MAILTO=ops@devops-network.in

Milestone 4 — System-wide cron locations

Bash
## System cron directories (no crontab -e needed, just drop files)
ls /etc/cron.d/ ## arbitrary schedule files
ls /etc/cron.daily/ ## runs once daily
ls /etc/cron.hourly/ ## runs once hourly
ls /etc/cron.weekly/ ## runs once weekly
ls /etc/cron.monthly/ ## runs once monthly
## Add a system-wide cron job
sudo tee /etc/cron.d/payment-health << 'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
## Run health check every 5 minutes as payment-svc user
*/5 * * * * payment-svc /opt/payment/scripts/health-check.sh >> /var/log/payment-health.log 2>&1
EOF
## Verify cron service is running
systemctl status cron ## Debian/Ubuntu
systemctl status crond ## RHEL/Amazon Linux

Milestone 5 — Create a systemd timer as modern alternative

Bash
## Step 1: Create the service unit (what runs)
sudo tee /etc/systemd/system/db-backup.service << 'EOF'
[Unit]
Description=PostgreSQL Database Backup
After=postgresql.service
[Service]
Type=oneshot
User=backup-svc
ExecStart=/opt/scripts/backup-postgres.sh
StandardOutput=journal
StandardError=journal
EOF
## Step 2: Create the timer unit (when it runs)
sudo tee /etc/systemd/system/db-backup.timer << 'EOF'
[Unit]
Description=Daily PostgreSQL backup timer
Requires=db-backup.service
[Timer]
## Run daily at 01:00
OnCalendar=*-*-* 01:00:00
## If server was off at 01:00, run at next boot
Persistent=true
[Install]
WantedBy=timers.target
EOF
## Step 3: Enable and start the timer
sudo systemctl daemon-reload
sudo systemctl enable --now db-backup.timer
## Step 4: Verify timer is scheduled
systemctl list-timers db-backup.timer
## NEXT LEFT UNIT ACTIVATES
## Mon 2024-01-16 01:00:00 UTC 12h left db-backup.timer db-backup.service
## Step 5: Run immediately to test
sudo systemctl start db-backup.service
## Step 6: Check results
journalctl -u db-backup.service -n 20

Milestone 6 — Production cron patterns

Bash
## Pattern 1: Backup with retention
cat > /opt/scripts/backup-postgres.sh << 'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
BACKUP_DIR=/opt/backups/postgres
RETENTION_DAYS=7
DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="$BACKUP_DIR/mydb-$DATE.sql.gz"
mkdir -p "$BACKUP_DIR"
echo "Starting backup at $(date)"
/usr/bin/pg_dump -U postgres mydb | gzip > "$BACKUP_FILE"
echo "Backup created: $BACKUP_FILE ($(du -sh $BACKUP_FILE | cut -f1))"
## Delete backups older than RETENTION_DAYS
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
echo "Cleanup done: removed files older than $RETENTION_DAYS days"
echo "Backup completed at $(date)"
SCRIPT
chmod +x /opt/scripts/backup-postgres.sh
## Add to crontab
## 0 1 * * * /opt/scripts/backup-postgres.sh >> /var/log/backup.log 2>&1

Production Best Practices and Common Pitfalls

Mistake Problem Fix
No absolute paths in script Works manually, fails in cron Use full paths or set PATH at top of script
No output redirection Silent failures, no debugging Always >> logfile 2>&1
crontab -r typo Deletes all cron jobs Use crontab -e to edit, never -r
Script not executable Cron exits silently chmod +x /opt/scripts/myscript.sh
Cron job overlaps itself Two instances run simultaneously Use flock to prevent concurrent execution

Quick Reference and Troubleshooting Commands

Task Command
Edit crontab crontab -e
List crontab crontab -l
List timers systemctl list-timers
Run timer now sudo systemctl start name.service
Check timer logs journalctl -u name.service
Debug PATH issues Add env > /tmp/cron-env.txt to crontab
Check cron daemon systemctl status cron
Validate cron syntax https://crontab.guru
Tip

Add * * * * * env > /tmp/cron-environment.txt to your crontab temporarily to see exactly what environment cron uses. This immediately reveals PATH differences and explains why commands work manually but fail in cron.

Remember

The cron execution environment has a minimal PATH (/usr/bin:/bin). Commands that work in your interactive shell may silently fail in cron because the binary is not in cron's PATH. Always use absolute paths or set PATH= at the top of every cron script.

Security

Cron jobs run with the permissions of the user whose crontab contains them. A root crontab entry that executes a world-writable script is a privilege escalation vulnerability. Always ensure cron scripts are owned by root and have chmod 750 or stricter permissions.

Common Mistake

Writing crontab -r when you meant crontab -e. The -r flag removes all cron entries with no confirmation and no undo. Always use -e to edit and -l to list. If you accidentally run -r, immediately check if you have a backup in ~/crontab.bak or restore from your configuration management system.

Resources

Explore More in Linux Process and System Management

All 4 Topics

Frequently Asked Questions

Is Scheduling Linux Tasks with Cron — crontab, Systemd Timers, and Production Patterns free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Scheduling Linux Tasks with Cron — crontab, Systemd Timers, and Production Patterns topic cover?

Schedule reliable automated tasks on Linux servers using cron and systemd timers with proper logging, error handling, and production-grade automation patterns.