Crontab: Syntax, Examples & How to Schedule Jobs


Our Linux server support team audits crontabs on every server it takes over, because scheduled jobs are where backups, log rotation, and certificate renewals live. This page documents crontab: the five-field syntax, the special strings, worked schedule examples, the difference between per-user and system crontabs, the environment behavior that causes most failures, and how to verify that a job actually ran.
What cron and crontab are
cron is the daemon that runs commands at scheduled times on Unix-like systems. A crontab (cron table) is the file that lists those schedules. Each user can have one crontab; the system has additional crontab files of its own. The daemon wakes every minute, checks all loaded crontabs, and runs any command whose schedule matches the current minute. The format is standardized in POSIX (crontab), with extensions documented in crontab(5). The common implementations are Vixie cron and its descendants (cronie on Red Hat-family systems, the cron package on Debian and Ubuntu). One-minute granularity is the floor: cron cannot schedule anything more frequent than every minute, and it does not run jobs that were missed while the machine was off — see the anacron section below.
The five fields
Each crontab line is five time-and-date fields followed by the command to run:
┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12 or JAN–DEC)
│ │ │ │ ┌───── day of week (0–7; 0 and 7 are Sunday, or SUN–SAT)
│ │ │ │ │
* * * * * command to execute
Field values:
| Notation | Meaning | Example |
|---|---|---|
* | Every value in the field's range | * in the hour field = every hour |
n | A single value | 30 in the minute field = minute 30 |
a,b,c | A list of values | 1,15 in day-of-month = the 1st and 15th |
a-b | An inclusive range | 9-17 in the hour field = 09:00 through 17:00 |
*/n or a-b/n | Step values through a range | */5 in the minute field = every 5 minutes |
One rule catches people out, and it is stated in crontab(5): if both day-of-month and day-of-week are restricted (neither is *), the command runs when either field matches — an OR, not an AND. 0 0 13 * 5 runs at midnight on the 13th of every month AND on every Friday, not only on Friday the 13th.
Special strings
Vixie cron and cronie accept shorthand strings in place of the five fields:
| String | Equivalent | Meaning |
|---|---|---|
@reboot | — | Run once after the cron daemon starts (in practice, at boot) |
@yearly / @annually | 0 0 1 1 * | Once a year, midnight Jan 1 |
@monthly | 0 0 1 * * | Once a month, midnight on the 1st |
@weekly | 0 0 * * 0 | Once a week, midnight Sunday |
@daily / @midnight | 0 0 * * * | Once a day at midnight |
@hourly | 0 * * * * | Once an hour, on the hour |
These are extensions, not POSIX; portable crontabs use the five-field form. @reboot has no five-field equivalent and is the standard way to start a user-level process at boot without root access.
Schedule examples
| Expression | Runs |
|---|---|
*/5 * * * * | Every 5 minutes |
0 * * * * | Every hour, on the hour |
0 2 * * * | Nightly at 02:00 |
30 4 * * * | Daily at 04:30 |
0 0 1 * * | First of every month at midnight |
0 9-17 * * 1-5 | Hourly 09:00–17:00, Monday through Friday |
*/15 9-17 * * 1-5 | Every 15 minutes during business hours, weekdays |
0 0 * * 0 | Weekly, midnight Sunday |
0 3 * * 6 | Saturdays at 03:00 |
0 6,18 * * * | Twice a day, 06:00 and 18:00 |
15 2 1,15 * * | 02:15 on the 1st and 15th of the month |
0 22 * * 1-5 | 22:00 every weekday |
@reboot /home/user/bin/start-agent.sh | Once at boot |
Complete example lines as they appear in a crontab:
# Nightly database dump at 02:00, log output
0 2 * * * /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1
# Sync every 5 minutes, discard output
*/5 * * * * /usr/local/bin/sync.sh >/dev/null 2>&1
# Report on the first of the month at 06:00
0 6 1 * * /usr/local/bin/monthly-report.sh
Managing crontabs
The crontab(1) command is the interface to per-user crontabs:
| Command | Action |
|---|---|
crontab -e | Edit your crontab in the editor set by VISUAL or EDITOR. On save, cron validates the syntax and installs it. This is the correct way to edit — the installed files under /var/spool/cron are not meant to be edited directly. |
crontab -l | List (print) your current crontab. |
crontab -r | Remove your entire crontab. No confirmation prompt on most systems, and -r sits next to -e on the keyboard. Keep a copy: crontab -l > crontab.bak. |
crontab file | Replace your crontab with the contents of the named file. |
crontab -u user -e | Edit another user's crontab (root only). |
Per-user crontab vs /etc/crontab vs /etc/cron.d
| Location | Format | Use |
|---|---|---|
Per-user (via crontab -e) | 5 fields + command. Jobs run as that user. | Jobs belonging to one user or service account. Managed only through the crontab command. |
/etc/crontab | 6 fields: a username field sits between the schedule and the command. | The system crontab. On Debian and Ubuntu it also drives the run-parts entries for /etc/cron.hourly, cron.daily, cron.weekly, cron.monthly. |
/etc/cron.d/ | Same 6-field format as /etc/crontab, one file per job or package. | Package-installed and admin-deployed system jobs. Files must be owned by root, not writable by group or other, and the filename must not contain a dot on run-parts-based systems. |
The username field is the classic cross-format mistake in both directions: pasting a per-user line into /etc/cron.d makes cron read the first word of the command as a username; pasting a system line into crontab -e makes cron try to execute the username as a command. Red Hat documents the system crontab formats in the RHEL system administration documentation.
Access control: if /etc/cron.allow exists, only users listed in it may use crontab; otherwise /etc/cron.deny is consulted. Details are in crontab(1).
Environment: why the command works in your shell and fails in cron
cron runs commands with a minimal environment, not your login environment. Per crontab(5): SHELL is /bin/sh, PATH defaults to /usr/bin:/bin on most implementations, and HOME and LOGNAME are set from /etc/passwd. Your .bashrc and .profile are not read. The consequences:
- PATH. Commands that live in /usr/local/bin, /usr/sbin, /opt, or a language version manager are not found. Use absolute paths (
/usr/local/bin/restic, notrestic), or set PATH at the top of the crontab:PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - No shell profile. Aliases, functions, ssh-agent sockets, and environment variables exported in your profile do not exist. Anything the job needs must be set in the crontab or inside the script.
- Percent signs. In the command field, an unescaped
%is converted to a newline, and everything after the first%is fed to the command as standard input.date +%Y%m%dfails silently in a crontab; writedate +\%Y\%m\%dor move the command into a script. - MAILTO. Any output (stdout or stderr) is mailed to the crontab owner, or to the address in a
MAILTO=line.MAILTO=""disables mail. On servers with no local mail delivery configured, this output goes nowhere — redirect it to a log file instead, as in the examples above. - SHELL. Because the shell is /bin/sh, bash-specific syntax in the command field can fail. Set
SHELL=/bin/bashin the crontab or put the logic in a script with a proper shebang.
Logging and verification
cron logs each job execution through syslog. To confirm a job fired:
# Debian / Ubuntu
grep CRON /var/log/syslog
# RHEL / CentOS / Rocky / Alma
grep CRON /var/log/cron
# Any systemd distribution
journalctl -u cron # Debian/Ubuntu service name
journalctl -u crond # RHEL-family service name
The log records that cron started the command, with the user and command line. It does not record the exit status or output on most default configurations — the job can start and still fail. For output, redirect it in the crontab line; for exit status, have the script log its own result or use run-one/wrapper tooling. A quick end-to-end test is a one-minute canary: * * * * * date >> /tmp/cron-test.log, wait two minutes, check the file, remove the line.
Troubleshooting: the job does not run
- Cron daemon not running. Check
systemctl status cron(Debian/Ubuntu) orsystemctl status crond(RHEL family) first. - PATH or environment — the most common cause. Test with the cron environment, not your shell:
env -i /bin/sh -c 'your command'. Fix with absolute paths. - Script not executable or wrong permissions. The user the job runs as needs execute permission on the script and read access to everything it touches — see chmod for the permission model. Files in /etc/cron.d additionally must be root-owned and not group/other-writable, or cron refuses to load them.
- Missing newline at end of file. crontab(5) requires each entry, including the last, to be terminated by a newline. A crontab whose final line has no trailing newline can have that line ignored;
crontab -enormally protects you, but files installed withcrontab fileor dropped into /etc/cron.d do not get fixed automatically. - Unescaped % in the command — the command is truncated at the first percent sign, as described above.
- Wrong crontab format for the location — a missing or extra username field, per the table above.
- User not permitted — check /etc/cron.allow and /etc/cron.deny.
- Schedule misread — the day-of-month/day-of-week OR rule, or a timezone assumption. cron uses the system time zone; a job scheduled for 02:00 UTC on a server set to another zone runs at the wrong wall-clock time. Note also that jobs scheduled between 02:00 and 03:00 can be skipped or doubled on DST transitions on implementations without DST handling.
- Job ran and failed silently — output was mailed to a dead mailbox or discarded. Add redirection to a log file and re-test.
- Server was off at the scheduled time — cron does not run missed jobs. That is anacron's problem to solve, below.
systemd timers: the alternative
On systemd distributions, systemd timers are the other native scheduler. A timer is a pair of unit files: a .timer unit holding the schedule (OnCalendar= for calendar times, OnBootSec=/OnUnitActiveSec= for monotonic intervals) and a matching .service unit holding the command. Factual differences from cron:
| Aspect | cron | systemd timer |
|---|---|---|
| Definition | One line in a crontab | Two unit files (.timer + .service) |
| Logging | Start line via syslog; output via mail/redirection | Full stdout/stderr and exit status in the journal (journalctl -u name.service) |
| Missed runs | Skipped | Persistent=true runs the job after boot if a scheduled run was missed |
| Sub-minute schedules | Not possible | Supported (OnCalendar accepts seconds) |
| Randomized delay, resource limits, dependencies, sandboxing | Not built in | Native (RandomizedDelaySec=, service unit directives) |
| Manual test run | Run the command yourself | systemctl start name.service runs exactly what the timer runs |
| Inspection | crontab -l per user | systemctl list-timers shows next/last run for all timers |
Ubuntu itself has moved several stock maintenance jobs (apt update checks, fstrim, logrotate on some releases) from cron to timers. For a one-line job on a server that already has cron, a crontab entry remains the smaller change; for jobs that need logs, catch-up runs, or dependencies, timers carry less scripting.
anacron: laptops and machines that are not always on
cron assumes the machine is running at the scheduled time. anacron assumes it is not: jobs in /etc/anacrontab are defined by period in days (1, 7, 30), a delay in minutes, and a command. On each run, anacron compares the timestamp of a job's last execution against its period and runs the job if it is overdue — so a daily job still runs once per day on a laptop that is only powered up during working hours. Granularity is daily at best; anacron is a complement to cron, not a replacement. On Red Hat-family and Debian-family systems the stock cron.daily/weekly/monthly jobs are executed through anacron where it is installed, which is why those directories work correctly on machines with irregular uptime. The general background is summarized on Wikipedia's cron article; distribution specifics are in the Ubuntu Server documentation.
When to hand it over
A missed cron job is usually discovered as its consequence: a backup that does not exist, a certificate that expired, a disk full of logs that never rotated. Our Linux server management team audits scheduled jobs, converts the fragile ones to monitored timers, and puts alerting on the jobs that matter — the related command-line references are ssh command and chmod. An engineer is available on live chat 24/7.
Topics

Sreenivasa Reddy G
Founder & CEO • 15+ years
Sreenivasa Reddy is the Founder and CEO of Medha Cloud, recognized as "Startup of the Year 2024" by The CEO Magazine. With over 15 years of experience in cloud infrastructure and IT services, he leads the company's vision to deliver enterprise-grade cloud solutions to businesses worldwide.
More in Company & Careers
View all
Tech Layoffs 2026: 55,775 Jobs Cut So Far — Full Tracker With Data
14 min read

Meta Plans 20% Layoffs — Up to 16,000 Jobs — to Fund AI Infrastructure
10 min read

Valuepoint Systems Cuts Jobs After Noventiq Acquisition — IT Services Consolidation Continues
7 min read
We are hiring Security Consultant
2 min read
We are hiring Penetration Tester / Ethical Hacker
2 min read
We are hiring Security Operations Center (SOC) Analyst – Tier 3 (L3)
2 min read