cron is the original "run this at that time" scheduler — boring, reliable, on every Unix since the 1970s. This guide covers user crontabs vs. /etc/cron.d, environment gotchas, error capture, and when you should reach for systemd timers instead.
Prerequisites
- Debian 12 with
sudo. - A script or one-liner you want to run on a schedule.
Step 1: Confirm cron is installed and running
Debian ships cron by default; if missing:
sudo apt install -y cron
sudo systemctl enable --now cron
sudo systemctl status cron --no-pager
Step 2: User crontab vs system cron
You have three places to schedule things:
- User crontab —
crontab -e— runs as the user. Default for personal jobs. /etc/cron.d/<name>— runs as a specified user. Best for app-deployed jobs./etc/cron.{daily,hourly,weekly,monthly}/— drop-in scripts. No timing control beyond the bucket.
Edit your user crontab:
crontab -e
Add a line — note the six fields for system cron (/etc/cron.d) vs. five fields for user crontab:
# User crontab — 5 fields
# min hour day mon dow command
*/5 * * * * /usr/local/bin/poll-queue.sh
0 3 * * * /usr/local/bin/nightly-backup.sh
System cron (/etc/cron.d/skyline-backup) — 6 fields (user is the extra one):
# /etc/cron.d/skyline-backup
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=ops@example.sa
0 3 * * * ops /usr/local/bin/nightly-backup.sh
Step 3: Capture stdout and stderr — don't let them disappear
cron mails the output of each job to the user (if a local MTA exists). For production hosts, redirect to a real log file:
0 3 * * * /usr/local/bin/nightly-backup.sh >> /var/log/backup.log 2>&1
Rotate that log with logrotate (/etc/logrotate.d/backup):
/var/log/backup.log {
weekly
rotate 8
compress
missingok
notifempty
}
Step 4: Beat the PATH/env trap
cron jobs run with a minimal environment. The number one cause of "works in my shell, breaks in cron" is PATH and locale. Three habits avoid 90 percent of these:
- Use absolute paths for every binary (
/usr/bin/psql, notpsql). - Set
PATHandLANGat the top of the crontab:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=en_US.UTF-8
- Wrap complex logic in a shell script and call that — don't put pipelines in the crontab itself.
Step 5: Quick syntax reference
* * * * * command
│ │ │ │ └── day of week (0-6, 0 = Sunday)
│ │ │ └───── month (1-12)
│ │ └──────── day of month (1-31)
│ └─────────── hour (0-23)
└────────────── minute (0-59)
Useful shorthands: @reboot, @daily, @hourly, @weekly, @monthly, @yearly.
Test before committing — crontab.guru is a great validator; locally just look at the next runtime:
# After saving:
crontab -l | tail
sudo systemctl status cron
sudo tail -f /var/log/syslog | grep CRON
Verify
sudo grep CRON /var/log/syslog | tail -20
ls -la /var/log/backup.log
Conclusion
cron is the right answer for ~80 percent of scheduled jobs on a Linux server. When you need calendar-aware scheduling, dependency on a unit being up, or a randomized delay, reach for systemd timers instead — but learn cron first.
Next steps
- For unit-aware scheduling see managing systemd services.
- Pair with unattended security upgrades so the OS patches itself overnight.
- Schedule nightly TLS-cert renewals — but
certbotalready installs a systemd timer for that.
Comments
0 total · 0 threads