The five fields
A cron expression is five fields separated by spaces. Each field controls one unit of time:
┌───────── minute (0 - 59)
│ ┌─────── hour (0 - 23)
│ │ ┌───── day of month (1 - 31)
│ │ │ ┌─── month (1 - 12)
│ │ │ │ ┌─ day of week (0 - 7, where 0 and 7 are both Sunday)
│ │ │ │ │
* * * * *That's the whole foundation. A * means "every value" for that field. So * * * * * means "every minute of every hour of every day", the most frequent standard cron schedule.
Reading a real example
Take 30 2 * * 1:
- minute
30 - hour
2 - day of month
*(any) - month
*(any) - day of week
1(Monday)
Result: 2:30 AM every Monday. Read it field by field and any expression decodes.
The special characters
Four operators let you express almost any schedule:
| Character | Meaning | Example | Reads as |
|---|---|---|---|
* | every value | * * * * * | every minute |
, | list | 0 9,17 * * * | 9 AM and 5 PM |
- | range | 0 9-17 * * * | every hour 9 AM-5 PM |
/ | step | */15 * * * * | every 15 minutes |
You can combine them: 0 0 1,15 * * runs at midnight on the 1st and 15th of every month. */10 9-17 * * 1-5 runs every 10 minutes, 9 AM-5 PM, Monday-Friday.
A cheat sheet of common schedules
| Expression | When it runs |
|---|---|
* * * * * | Every minute |
*/5 * * * * | Every 5 minutes |
0 * * * * | Top of every hour |
0 0 * * * | Every day at midnight |
0 2 * * * | Every day at 2 AM |
0 0 * * 0 | Every Sunday at midnight |
0 0 1 * * | First day of every month, midnight |
0 9 * * 1-5 | 9 AM every weekday |
30 23 * * 6 | 11:30 PM every Saturday |
0 0 1 1 * | Midnight on January 1st (yearly) |
Bookmark this. Most real schedules are minor variations of these rows.
The day-of-month / day-of-week gotcha
Here's the one that confuses everyone. When you specify both day-of-month and day-of-week (and neither is *), most cron implementations treat it as OR, not AND. So 0 0 13 * 5 means "midnight on the 13th of the month OR any Friday," not "only on Friday the 13th."
If you want a true AND, you typically check one condition inside the script and let cron handle the other. Be deliberate here, this quirk causes jobs to run far more often than intended.
Timezones: the silent bug factory
Cron runs in whatever timezone the *system* (or scheduler) is configured for, often UTC on servers. A job scheduled for 0 9 * * * runs at 9 AM server time, which may be the middle of the night for you. Always:
- 1Know your scheduler's timezone (assume UTC unless told otherwise).
- 2Convert your intended local time to that timezone when writing the expression.
- 3Remember daylight saving time: a job scheduled for 2:30 AM may run twice or not at all on DST transition days. For DST-sensitive jobs, avoid the 1-3 AM window or use a scheduler with explicit timezone support.
Validate before you trust
Never deploy a cron expression you haven't verified. Use a tool like crontab.guru to see the human-readable meaning and the next run times. A thirty-second check prevents a job that silently runs at the wrong time for months.
Beyond standard cron: seconds and extensions
Standard Unix cron's finest granularity is one minute. Some schedulers (Quartz, many language libraries, some cloud platforms) add a seconds field as a sixth field at the front, or support shorthand like @daily, @hourly, @reboot. These are convenient but not portable, an expression with a seconds field will be misread by standard cron. Know which dialect your platform speaks before copying expressions between systems.
Two operational rules that save you
Prevent overlapping runs
If a job sometimes takes longer than its interval (a */5 job that occasionally runs 7 minutes), the next run can start before the previous finished, causing double-processing or resource contention. Guard against it with a lock:
# flock prevents a second copy from starting while one is running
*/5 * * * * /usr/bin/flock -n /tmp/sync.lock /opt/app/sync.shMake scheduled tasks idempotent
Like background jobs, scheduled tasks can run twice (retries, overlapping schedulers). Design them so a double run is harmless.
Running cron jobs on PandaStack
You don't need a server with a crontab to run scheduled work. PandaStack supports cronjobs natively, you provide the schedule (standard cron syntax) and the task, and the platform runs it on schedule. Common uses: nightly database backups, sending daily digests, cleanup tasks, periodic syncs, and report generation.
Because cronjobs run as containers on the same platform as your apps, they can share the same environment, your cronjob can read the injected DATABASE_URL to operate on your managed database, with live logs so you can confirm each run.
# Example schedules for PandaStack cronjobs:
0 2 * * * # nightly backup at 2 AM (mind the timezone)
*/15 * * * * # poll an external API every 15 minutes
0 8 * * 1 # weekly Monday-morning report at 8 AMConclusion
Cron is five fields and four operators, decode it left to right and it stops looking cryptic. The real traps aren't the syntax; they're the day-of-month/day-of-week OR behavior, timezones and DST, and overlapping runs. Validate every expression, design for idempotency, and you'll schedule with confidence.
PandaStack runs your cronjobs as managed containers with live logs and access to your injected database, no crontab server required. Schedule your first task on the free tier at https://dashboard.pandastack.io.
References
- crontab.guru (expression tester): https://crontab.guru/
- crontab(5) man page: https://man7.org/linux/man-pages/man5/crontab.5.html
- Wikipedia, cron: https://en.wikipedia.org/wiki/Cron
- Quartz cron format (seconds field): https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html