Cron is the backbone of server automation — backups, reports, cleanup, health checks, database maintenance. Understanding it completely, including its environment quirks and logging gaps, is what prevents the classic "runs manually but not in cron" problem.
1
Cron syntax — the five fields
CRON
# Cron field layout:
# ┌─────────── minute (0-59)
# │ ┌───────── hour (0-23)
# │ │ ┌─────── day of month (1-31)
# │ │ │ ┌───── month (1-12 or JAN-DEC)
# │ │ │ │ ┌─── day of week (0-7 or SUN-SAT, 0 and 7 = Sunday)
# │ │ │ │ │
# * * * * * command
# ── Common patterns ───────────────────────────────────────
*/5 * * * * /scripts/health_check.sh # every 5 minutes
0 * * * * /scripts/hourly_report.sh # every hour at :00
30 6 * * * /scripts/daily_backup.sh # daily at 06:30
0 2 * * 0 /scripts/weekly_clean.sh # Sunday at 02:00
0 3 1 * * /scripts/monthly_stats.sh # 1st of month at 03:00
0 9 * * 1-5 /scripts/weekday_report.sh # Mon-Fri at 09:00
0 8 1 1,7 * /scripts/biannual.sh # Jan 1 and Jul 1
# ── Step values ───────────────────────────────────────────
*/10 * * * * cmd # every 10 minutes
0 */2 * * * cmd # every 2 hours
0 0 */3 * * cmd # every 3 days
# ── Special strings (easier to read) ─────────────────────
@reboot /scripts/on_boot.sh # on system startup
@hourly /scripts/hourly.sh # same as: 0 * * * *
@daily /scripts/daily.sh # same as: 0 0 * * *
@weekly /scripts/weekly.sh # same as: 0 0 * * 0
@monthly /scripts/monthly.sh # same as: 0 0 1 * *
@yearly /scripts/yearly.sh # same as: 0 0 1 1 *
2
crontab — managing cron jobs
BASH
# ── crontab commands ──────────────────────────────────────
crontab -e # edit current user's crontab
crontab -l # list current crontab
crontab -r # remove crontab (dangerous!)
crontab -u vriddh -l # list another user's crontab (root only)
crontab -u vriddh -e # edit another user's crontab
# ── Install crontab from file ──────────────────────────────
crontab mycrons.txt # installs file as crontab (replaces existing)
# ── System-wide cron directories ─────────────────────────
# Drop scripts into these dirs — no crontab editing needed
# /etc/cron.hourly/ — runs every hour
# /etc/cron.daily/ — runs daily
# /etc/cron.weekly/ — runs weekly
# /etc/cron.monthly/ — runs monthly
# /etc/cron.d/ — custom cron files with full syntax
# Example /etc/cron.d/myapp file:
# SHELL=/bin/bash
# PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# 0 2 * * * vriddh /opt/myapp/scripts/daily_backup.sh
# ── Backup and restore crontab ────────────────────────────
crontab -l > ~/crontab.bak # backup
crontab ~/crontab.bak # restore
3
The cron environment — why scripts break in cron
The most common cron problem: scripts work manually but fail silently in cron. The cause is always the environment — cron runs with a minimal PATH and no shell config is sourced.
BASH
# ── Cron's minimal PATH ───────────────────────────────────
# Default PATH in cron: /usr/bin:/bin
# Your interactive PATH: /usr/local/bin:/usr/bin:/bin:/opt/...etc
# mysql, python3, node etc may NOT be found in cron!
# ── Solution 1: Set PATH in crontab ──────────────────────
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 2 * * * /opt/myapp/backup.sh
# ── Solution 2: Use full paths in scripts ────────────────
# Instead of: mysql -h host
# Use: /usr/bin/mysql -h host
# Find full path of a command:
which mysql # /usr/bin/mysql
which python3 # /usr/bin/python3
# ── Solution 3: Source profile in script ─────────────────
#!/usr/bin/env bash
source /etc/profile 2>/dev/null || true
source ~/.bashrc 2>/dev/null || true
# ... rest of script
# ── Cron has no HOME, DISPLAY, etc ───────────────────────
# Always set HOME explicitly if needed
HOME=/home/vriddh
0 2 * * * /opt/scripts/backup.sh
4
Cron logging and output handling
BASH
# ── Default: cron emails output to local user ─────────────
# Suppress all output (silent cron job):
0 2 * * * /scripts/backup.sh >/dev/null 2>&1
# ── Log to file ───────────────────────────────────────────
0 2 * * * /scripts/backup.sh >> /var/log/backup.log 2>&1
# ── Log with timestamp ────────────────────────────────────
0 2 * * * /scripts/backup.sh >> /var/log/backup.log 2>&1; \
echo "[$(date)] Exit: $?" >> /var/log/backup.log
# ── Send output to email ─────────────────────────────────
MAILTO=ops@example.com
0 2 * * * /scripts/backup.sh # output emailed to MAILTO
# ── Only email on failure ─────────────────────────────────
0 2 * * * /scripts/backup.sh >> /var/log/backup.log 2>&1 \
|| mail -s "BACKUP FAILED" ops@example.com < /var/log/backup.log
# ── View cron logs ────────────────────────────────────────
grep CRON /var/log/syslog # Debian/Ubuntu
grep CRON /var/log/cron # CentOS/RHEL
journalctl -u cron # systemd systems
5
at — one-time scheduled jobs
BASH
# ── at — run a command once at a specific time ────────────
at 22:30 # at 10:30 PM today
at 09:00 tomorrow # tomorrow morning
at noon # today at 12:00
at midnight # tonight at 00:00
at now + 30 minutes # 30 minutes from now
at now + 2 hours # 2 hours from now
at 14:00 Jul 15 # specific date
# Interactive mode
$ at 23:00
at> /scripts/maintenance.sh
at> # submit the job
# Non-interactive (better for scripts)
echo "/scripts/maintenance.sh" | at 23:00
at 23:00 << EOF
/scripts/maintenance.sh --mode full
/scripts/send_report.sh
EOF
# Manage at jobs
atq # list pending at jobs
atrm 3 # remove job number 3
at -l # same as atq
at -d 3 # same as atrm 3
6
Production crontab — complete real example
CRON
# /etc/cron.d/myapp — Production cron for myapp
# Managed by: Vriddh Technologies
# Last updated: 2026-05-01
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=ops@example.com
# ── Health checks (every 5 min) ───────────────────────────
*/5 * * * * vriddh /opt/myapp/scripts/health_check.sh >> /var/log/myapp/health.log 2>&1
# ── Database backup (daily at 02:00) ─────────────────────
0 2 * * * vriddh /opt/myapp/scripts/db_backup.sh >> /var/log/myapp/backup.log 2>&1
# ── Log cleanup (weekly Sunday 03:00) ────────────────────
0 3 * * 0 vriddh /opt/myapp/scripts/rotate_logs.sh >> /var/log/myapp/rotate.log 2>&1
# ── Monthly usage report (1st of month 08:00) ────────────
0 8 1 * * vriddh /opt/myapp/scripts/monthly_report.sh | mail -s "Monthly Report" management@example.com
# ── Session cleanup (every 6 hours) ──────────────────────
0 */6 * * * vriddh /opt/myapp/scripts/cleanup_sessions.sh >/dev/null 2>&1
# ── Disk alert (every 30 min) ─────────────────────────────
*/30 * * * * vriddh /opt/myapp/scripts/disk_alert.sh >/dev/null 2>&1
# ── Replication check (every 10 min, business hours only) ─
*/10 8-18 * * 1-5 vriddh /opt/myapp/scripts/check_replication.sh >> /var/log/myapp/repl.log 2>&1
Terminal output
vriddh@prod-01:~$crontab -l
# Vriddh production crontab
*/5 * * * * /opt/myapp/scripts/health_check.sh >> /var/log/myapp/health.log 2>&1
0 2 * * * /opt/myapp/scripts/db_backup.sh >> /var/log/myapp/backup.log 2>&1
vriddh@prod-01:~$grep CRON /var/log/syslog | tail -5
May 1 10:00:01 prod-01 CRON[18421]: (vriddh) CMD (/opt/myapp/scripts/health_check.sh)
May 1 10:00:02 prod-01 CRON[18421]: (CRON) INFO (No MTA installed)
May 1 10:05:01 prod-01 CRON[18539]: (vriddh) CMD (/opt/myapp/scripts/health_check.sh)
█
✔ Cron production checklist — Always set
SHELL and PATH at the top of every crontab. Always redirect output — either >> logfile 2>&1 or >/dev/null 2>&1. Always use absolute paths. Test scripts with env -i /bin/bash --login -c 'your_script.sh' to simulate cron's environment. Use MAILTO to send failure alerts automatically.