Cron Jobs Explained: Automate Tasks on Linux Like a Pro
Cron is the built-in task scheduler on every Linux system. It runs commands automatically at specified times — every minute, every hour, every Monday at 3 AM, or any schedule you define. It’s the backbone of server automation: backups, log rotation, monitoring checks, database maintenance, and more.
How Cron Works
The cron daemon (crond) runs in the background and checks every minute for scheduled jobs. Jobs are defined in crontab files — one per user, plus system-wide crontabs.
The Crontab Syntax
┌───────────── minute (0–59)
│ ┌───────────── hour (0–23)
│ │ ┌───────────── day of month (1–31)
│ │ │ ┌───────────── month (1–12)
│ │ │ │ ┌───────────── day of week (0–7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * * command_to_execute
Special Characters
| Character | Meaning | Example |
|---|---|---|
* | Every value | * * * * * = every minute |
, | Multiple values | 1,15,30 = minutes 1, 15, 30 |
- | Range | 1-5 = Monday to Friday |
/ | Step | */15 = every 15 units |
Common Schedule Examples
# Every minute
* * * * * /path/to/script.sh
# Every 5 minutes
*/5 * * * * /path/to/script.sh
# Every hour (at minute 0)
0 * * * * /path/to/script.sh
# Every day at 3:00 AM
0 3 * * * /path/to/script.sh
# Every Monday at 9:00 AM
0 9 * * 1 /path/to/script.sh
# Every weekday at 8:30 AM
30 8 * * 1-5 /path/to/script.sh
# First day of every month at midnight
0 0 1 * * /path/to/script.sh
# Every 6 hours
0 */6 * * * /path/to/script.sh
# Every Sunday at 2:30 AM
30 2 * * 0 /path/to/script.sh
# January 1st at midnight (yearly)
0 0 1 1 * /path/to/script.sh
Special Shortcuts
@reboot /path/to/script.sh # Run once at startup
@hourly /path/to/script.sh # 0 * * * *
@daily /path/to/script.sh # 0 0 * * *
@weekly /path/to/script.sh # 0 0 * * 0
@monthly /path/to/script.sh # 0 0 1 * *
@yearly /path/to/script.sh # 0 0 1 1 *
Use crontab.guru to validate and visualize cron expressions.
Managing Crontabs
# Edit your crontab
crontab -e
# List your crontab
crontab -l
# Remove your crontab (careful!)
crontab -r
# Edit another user's crontab (as root)
sudo crontab -u username -e
The first time you run crontab -e, you may be asked to choose an editor. Select nano for simplicity or vim if you’re comfortable with it.
Real-World Examples
1. Automated Backups
# Daily database backup at 2 AM
0 2 * * * pg_dump -U postgres mydb | gzip > /backups/db-$(date +\%Y\%m\%d).sql.gz
# Daily file backup at 3 AM
0 3 * * * tar -czf /backups/files-$(date +\%Y\%m\%d).tar.gz /var/www/html
# Clean backups older than 30 days
0 4 * * * find /backups -name "*.gz" -mtime +30 -delete
Note: In crontab, percent signs % must be escaped as \%.
2. SSL Certificate Renewal
# Check and renew Let's Encrypt certificates twice daily
0 0,12 * * * certbot renew --quiet && systemctl reload nginx
3. System Monitoring
# Check disk usage every hour, alert if over 90%
0 * * * * df -h / | awk 'NR==2 {if ($5+0 > 90) print "DISK ALERT: " $5 " used"}' | mail -s "Disk Alert" admin@example.com
# Log system stats every 5 minutes
*/5 * * * * echo "$(date): $(uptime)" >> /var/log/system-stats.log
4. Log Rotation and Cleanup
# Compress logs older than 7 days
0 1 * * * find /var/log/myapp -name "*.log" -mtime +7 -exec gzip {} \;
# Delete compressed logs older than 90 days
0 2 * * 0 find /var/log/myapp -name "*.gz" -mtime +90 -delete
# Truncate large active log files
0 0 * * * truncate -s 0 /var/log/myapp/debug.log
5. Docker Maintenance
# Update Docker containers weekly
0 4 * * 0 cd /home/user/server && docker compose pull && docker compose up -d
# Clean Docker resources monthly
0 3 1 * * docker system prune -af --volumes
6. Git Auto-Pull
# Pull latest code every 10 minutes (for staging servers)
*/10 * * * * cd /var/www/staging && git pull origin main
7. Health Checks
# Restart service if it's down
*/5 * * * * pgrep -x nginx > /dev/null || systemctl restart nginx
Output and Logging
By default, cron emails command output to the user. Most servers don’t have mail configured, so redirect output explicitly:
# Discard all output
0 3 * * * /path/to/script.sh > /dev/null 2>&1
# Log output to a file
0 3 * * * /path/to/script.sh >> /var/log/myjob.log 2>&1
# Log with timestamp
0 3 * * * echo "$(date): Starting backup" >> /var/log/myjob.log && /path/to/script.sh >> /var/log/myjob.log 2>&1
Set a MAILTO for all jobs
Add at the top of your crontab:
MAILTO=admin@example.com
# Jobs below will email output to this address
0 3 * * * /path/to/backup.sh
Set MAILTO="" to suppress all email.
Environment Variables
Cron runs with a minimal environment — it doesn’t load your .bashrc or .profile. This is the #1 cause of “works in terminal but not in cron” issues.
Fix: Set PATH explicitly
PATH=/usr/local/bin:/usr/bin:/bin
0 3 * * * /home/user/backup.sh
Fix: Use full paths
# Bad — 'node' might not be found
*/5 * * * * node /app/check.js
# Good — use full path
*/5 * * * * /usr/bin/node /app/check.js
Find full paths with which:
which node # /usr/bin/node
which python3 # /usr/bin/python3
which pg_dump # /usr/bin/pg_dump
Fix: Source your profile
0 3 * * * bash -lc '/home/user/backup.sh'
System Crontabs
Beyond per-user crontabs, there are system-wide cron directories:
/etc/crontab # System crontab (includes username field)
/etc/cron.d/ # Drop-in cron files
/etc/cron.hourly/ # Scripts run every hour
/etc/cron.daily/ # Scripts run every day
/etc/cron.weekly/ # Scripts run every week
/etc/cron.monthly/ # Scripts run every month
To add a system cron job, place a script in the appropriate directory:
sudo cp my-script.sh /etc/cron.daily/
sudo chmod +x /etc/cron.daily/my-script.sh
Note: Scripts in cron.daily/ etc. must not have file extensions in some systems (no .sh).
Systemd Timers (Modern Alternative)
Systemd timers are a more powerful alternative to cron on modern Linux distributions.
Create a service file /etc/systemd/system/backup.service:
[Unit]
Description=Daily Backup
[Service]
Type=oneshot
ExecStart=/home/user/backup.sh
User=user
Create a timer file /etc/systemd/system/backup.timer:
[Unit]
Description=Run backup daily at 3 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
Enable and start:
sudo systemctl enable backup.timer
sudo systemctl start backup.timer
# Check timer status
sudo systemctl list-timers --all
# Run manually
sudo systemctl start backup.service
# View logs
journalctl -u backup.service
Advantages over cron
- Better logging (integrated with journald)
- Dependency management (wait for network, etc.)
- Missed job handling (
Persistent=trueruns missed jobs) - Resource control (CPU/memory limits)
Debugging Cron Jobs
Check if cron is running
sudo systemctl status cron
# or
sudo systemctl status crond
Check cron logs
# Debian/Ubuntu
grep CRON /var/log/syslog
# CentOS/RHEL
grep CRON /var/log/cron
# Follow live
tail -f /var/log/syslog | grep CRON
Test your script manually first
# Run with the same minimal environment cron uses
env -i /bin/bash -c '/path/to/script.sh'
Common issues checklist
- ✅ Script has execute permission (
chmod +x) - ✅ Use full paths for all commands
- ✅ Escape percent signs as
\% - ✅ PATH is set in crontab
- ✅ Output is redirected (
>> log 2>&1) - ✅ Script works when run manually
- ✅ Cron daemon is running
Security
- Never put sensitive passwords directly in crontab — use environment files or secret managers
- Restrict crontab access:
/etc/cron.allowand/etc/cron.denycontrol who can use cron - Review crontabs regularly:
for user in $(cut -f1 -d: /etc/passwd); do echo "=== $user ==="; sudo crontab -l -u $user 2>/dev/null; done
Conclusion
Cron is one of the most useful tools on any Linux server. Whether you’re automating backups, rotating logs, checking service health, or running periodic maintenance, cron handles it reliably and silently in the background. Master the five-field syntax, remember to use full paths and redirect output, and you’ll have a rock-solid automation foundation for any server.