Crontab on Linux: how to schedule tasks automatically

A backup that should run every day at 3am, but only runs when somebody remembers to execute the script by hand. A log that grows without rotation until it fills the disk. A certificate that expires because nobody automated the renewal. All of these problems share the same cause: a task that ought to be automatic still depends on a human remembering. crontab has existed since the earliest Unix systems for exactly this — and most sysadmins use only a fraction of what it offers.
What cron is and what it's for
cron is a daemon that runs in the background on practically every Linux distribution, checking minute by minute whether any schedule matches the current time. The crontab ("cron table") is the text file where you list those schedules — one per line, each saying when to run and what to run. There's nothing to install: cron and crontab ship with Debian, Ubuntu, RHEL, Alpine and practically any server image.
To edit the current user's crontab:
crontab -eTo list what's already scheduled (do this before any change — guessing what's running is how things break in the middle of the night):
crontab -lThe 5-field syntax
Every crontab line follows the same format: five time fields, then the command.
# ┌───────────── minute (0-59)
# │ ┌───────────── hour (0-23)
# │ │ ┌───────────── day of month (1-31)
# │ │ │ ┌───────────── month (1-12)
# │ │ │ │ ┌───────────── day of week (0-6, 0 = Sunday)
# │ │ │ │ │
* * * * * command-to-runFour special characters cover 90% of cases:
- Asterisk (
*): "any value".* * * * *runs every minute. - Slash (
/): defines an interval.*/15in the minute field runs every 15 minutes. - Comma (
,): a list of values.1,15in the day field runs on the 1st and the 15th. - Hyphen (
-): a range.1-5in the day-of-week field covers Monday to Friday.
How do I run a script every day at 3am?
Use 0 3 * * * followed by the full path to the script. The zero in the minute field and the 3 in the hour field pin the time; the three remaining asterisks say "every day, every month, any day of the week".
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1Practical examples to copy and adapt
# Daily backup at 3am, output and errors in the same file
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# Log cleanup every Sunday at 4am
0 4 * * 0 find /var/log/app -name "*.log" -mtime +30 -delete
# Let's Encrypt certificate renewal, twice a day (the official recommendation)
0 0,12 * * * certbot renew --quiet
# Sync every 15 minutes, weekdays only
*/15 * * * 1-5 /usr/local/bin/sync-data.sh
# Restart a service on the first day of every month at midnight
0 0 1 * * systemctl restart app-staleThere are also shortcuts so you don't have to write the 5 fields by hand: @reboot (once, at system boot), @daily (equivalent to 0 0 * * *), @weekly, @monthly and @hourly. They're useful for obvious schedules where readability matters more than precision.
The 3 mistakes that most often break a cron job in production
It works perfectly when you test it by running the script straight from the terminal, and fails silently when cron fires it. It's almost always one of these three problems:
- An incomplete PATH: cron runs with a minimal environment, without your interactive shell's
PATH. A script callingpython,nodeorawswithout a full path fails with "command not found". The fix: use absolute paths (/usr/bin/python3) or setPATH=at the top of the crontab. - No output redirection: by default cron tries to send stdout/stderr by local email — which on most servers isn't configured, so the output simply vanishes. Always redirect with
>> file.log 2>&1so you have a trace of what happened. - Shell environment variables don't load:
.bashrc,.profileand variables exported in your interactive session don't exist in cron's environment. If the script depends on a variable, export it explicitly inside the script itself or in the crontab.
When to swap crontab for a systemd timer
Crontab or systemd timer: which should I use?
For simple scheduling on any Linux, cron is still enough. For critical jobs that need centralised logging, dependencies between services or recovery of a missed run, a systemd timer is the more robust option on distros already running systemd (Ubuntu, Debian, RHEL, Fedora).
A systemd timer splits the schedule across two files: a .service unit (what to run) and a .timer unit (when to run). Here's the equivalent of the 3am backup:
# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup
[Service]
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup schedule
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl enable --now backup.timerThe practical advantage of Persistent=true: if the server was switched off at 3am, the timer fires the job as soon as systemd is running again — something traditional cron does not do. Logs are also centralised in the journal (journalctl -u backup.service), with no need for manual redirection. In exchange, it's more work to write (two files instead of one line) — which is why cron still wins for one-off scripts and simpler environments.
Security: cron as root is root power
A job scheduled with crontab -e inside a root session, or dropped straight into /etc/cron.d/, executes with full privilege — no password prompt, no log of who ran the command manually. It's a classic persistence vector after an initial compromise: an attacker with write access to any cron directory can run code periodically without having to keep a session open. If you've already applied the changes in SSH hardening on the server, the same care applies here: audit crontab -l as root periodically, restrict who can edit cron via /etc/cron.allow, and never run as root a job that could run under a dedicated service user.
The Windows equivalent is Task Scheduler, and the auditing principle is identical: if you administer a mixed environment, the post on auditing Windows security logs with PowerShell covers the other end of this routine. And if your Windows environment still depends on Windows PowerShell 5.1 to run those scheduled scripts, we've already documented what changes when migrating to PowerShell 7.6 LTS.
Cron isn't glamorous, but it's probably the tool that most quietly holds production infrastructure together — backups, log rotation, certificate renewal, data synchronisation. Audit what's running on your server right now with crontab -l and ls /etc/cron.d/: if you find something nobody remembers scheduling, that's the moment to investigate before assuming it's just a forgotten task.




Comments