How to Use Cron Expressions for Scheduled Data Syncs

How to Use Cron Expressions for Scheduled Data Syncs

Learn cron expressions for scheduled data syncs: the 5 fields, special characters, common sync schedules, the day-of-week OR trap, and timezone gotchas.

Ilshaad Kheerdali·Jun 26, 2026·10 min read

You wanted one simple thing: keep a Postgres copy of your Stripe, QuickBooks, or Xero data fresh on a schedule. Someone on your team said "just use cron." Now you are staring at five asterisks in a terminal, unsure whether 0 9 * * 1-5 means 9am your time or 9am somewhere else, and whether you just told a server to run a job every minute by accident.

Cron is a genuinely good tool, and once the syntax clicks it stops being scary. This guide walks through the standard 5-field format, the special characters, real sync schedules you will actually use, and the handful of gotchas that quietly break jobs in production. If you want to skip ahead and just see an expression broken down with its next run times, paste it into the free Cron Expression Generator while you read.

How cron expressions work for scheduled data syncs

A standard cron expression is five space-separated fields that together describe when a job should run. Read left to right, they are: minute, hour, day of month, month, and day of week. There is no seconds field in standard cron, which is the single most common source of confusion (more on that later).

Building cron expressions for scheduled data syncs really comes down to answering one question per field: at which minutes, hours, days, months, and weekdays should this sync fire? The cron daemon checks the clock once a minute and runs any entry whose five fields all match the current time. The authoritative reference is the Linux crontab(5) man page, which is worth a bookmark.

Reading the five fields: minute, hour, day-of-month, month, day-of-week

Here is the layout, with the valid range for each field:

* * * * *
| | | | |
| | | | +-- day of week  (0-7, both 0 and 7 are Sunday; names allowed)
| | | +---- month         (1-12, names allowed)
| | +------ day of month  (1-31)
| +-------- hour          (0-23)
+---------- minute        (0-59)

A couple of details matter. The day-of-week field treats both 0 and 7 as Sunday under the Vixie/Linux convention used by most Linux servers. Strict POSIX only defines 0-6 with 0 as Sunday and does not include 7, so do not assume 7=Sunday works everywhere. Month and day-of-week also accept three-letter names like JAN or MON, but numbers are more portable across schedulers.

Special characters: asterisk, comma, hyphen, and the step operator

Four characters do most of the work:

  • Asterisk (*) means "every value" for that field, literally first through last. * * * * * runs every minute.
  • Hyphen (-) defines an inclusive range. 1-5 in the day-of-week field is Monday through Friday.
  • Comma (,) defines a list. 1,3,5 is Monday, Wednesday, Friday.
  • Slash (/) is the step operator. */15 in the minute field means every 15 units, so minutes 0, 15, 30, and 45.

You can combine them. 0,30 9-17 * * 1-5 means at minute 0 and 30, during hours 9 through 17, Monday through Friday. Keep steps in the portable */n form; some implementations treat 0/15 and */15 differently, so stick with */n to avoid surprises.

Cron expression examples for common sync schedules

Most data-sync jobs fall into a few recurring shapes. Here are the ones you will reach for, each tied to a real use case:

*/5 * * * *      Every 5 minutes        Near-real-time mirror of fast-moving data
0 * * * *        Every hour at :00       Hourly refresh of invoices or subscriptions
0 9 * * 1-5      09:00 Mon-Fri           Business-hours-only sync for a reporting DB
0 2 * * *        02:00 every day          Nightly off-peak full sync
0 0 1 * *        Midnight on the 1st     Monthly reconciliation snapshot
0 0 * * 0        Midnight every Sunday    Weekly rollup before Monday standup

A few notes on intent. */5 * * * * is the workhorse for keeping a Postgres copy of Stripe or QuickBooks reasonably fresh without hammering the API. 0 2 * * * runs at 2am, which on a UTC server is genuinely off-peak for most US and EU traffic, making it ideal for a heavier full sync. If you want to confirm any of these or build your own, drop it into the Cron Expression Generator: it parses the five fields, gives you a plain-English description, and lists the next five run times in UTC.

The day-of-month vs day-of-week OR rule that breaks schedules

This is the gotcha that catches even experienced developers. Suppose you want "Friday the 13th" and write 0 0 13 * 5, expecting day-of-month 13 and day-of-week Friday. It does not do that. The crontab(5) man page is explicit:

If both fields are restricted (i.e., do not contain the * character), the command will be run when either field matches the current time.

So 0 0 13 * 5 actually runs at midnight on the 13th of every month and every Friday. POSIX confirms the same OR behaviour. The two day fields are combined with OR, not AND, whenever both are restricted. The practical fix: keep one of the two fields as * and test the other condition inside your command, or use a scheduler that does AND matching. If your sync only needs to skip weekends, 0 2 * * 1-5 is safe because the day-of-month field stays *.

5 fields vs 6: why your cron expression has the wrong number of fields

The other classic failure is field count. Standard cron has exactly 5 fields. Quartz Scheduler, used by a lot of Java and Spring apps, uses 6 or 7 fields: it adds a leading Seconds field (0-59) and an optional trailing Year (1970-2099). The Quartz CronTrigger tutorial documents this format.

The trap is copy-paste. If you lift a 6-field Quartz expression like 0 0 9 * * ? into a 5-field crontab, every field shifts one position to the left and you silently schedule the wrong time. Going the other direction is just as bad. Before pasting any expression, confirm whether the target system expects 5 fields or 6. The Cron Expression Generator validates standard 5-field cron only, so a 6-field expression will flag immediately rather than fail at runtime.

@hourly, @daily, @weekly: cron macros that save typing

Vixie cron, the implementation on most Linux boxes, supports nickname macros so you do not have to memorise the field layout for common cases:

@hourly   ->  0 * * * *
@daily    ->  0 0 * * *
@weekly   ->  0 0 * * 0
@monthly  ->  0 0 1 * *
@yearly   ->  0 0 1 1 *
@reboot   ->  runs once at daemon startup

These are a cron extension, not POSIX, so they will not exist on every scheduler. But on a standard Linux server @daily and 0 0 * * * are identical, and the macro is harder to typo.

Cron, UTC, and daylight saving time pitfalls

Cron evaluates schedules in the daemon's timezone, which is the system local time and is commonly UTC on servers. That means 0 9 * * * is 9am in the server's timezone, not your wall clock. If your laptop is in London and your server is on UTC, those happen to line up in winter and drift by an hour in summer. Vixie/cronie supports a CRON_TZ variable to pin a timezone for crontab entries if you really need local time.

Daylight saving is where this gets nasty, and the exact behaviour depends on your scheduler. The cronie/Vixie cron on most Linux servers actually tries to compensate for shifts under three hours: at spring-forward, a fixed-time job whose hour is skipped is run immediately instead of being lost, and at fall-back, cron avoids running the same fixed-time job twice. Clock changes larger than three hours are treated as a correction and the new time is just adopted. The catch is that this only covers fixed-time entries on cronie. Other schedulers, container cron, and managed platforms each handle the transition differently, so you cannot assume the missed or doubled run is handled for you. The clean fix is to keep your servers on UTC and make your syncs idempotent, so a double-run or a skipped-run never corrupts your data. This is also why the generator tool reports its next run times in UTC: it removes the ambiguity rather than guessing your local offset.

From cron jobs to managed scheduled syncs with Codeless Sync

Here is the honest part. Cron syntax is the easy bit. Running a reliable sync on cron means owning everything around it: a server to host the job, monitoring so you know it actually ran, retries when the provider API times out, overlap handling when a 5-minute schedule meets an 8-minute run, and alerting for the day it silently stops. That operational tail is exactly why a self-rolled Stripe to Postgres sync keeps breaking.

Codeless Sync takes that whole layer off your plate. Instead of writing cron and babysitting a worker, you pick a frequency (Hourly, Daily, Weekly, or Monthly) plus a sync mode (full or incremental), and CLS runs and monitors the sync for Stripe, QuickBooks, Xero, and Paddle into your own Postgres database. There is no cron server to keep alive and no DST math to get wrong. See how schedules work in the docs, and if you still want to understand or hand-tune a raw expression for your own jobs, the Cron Expression Generator is free and entirely client-side. When you are ready to stop maintaining pipelines, the pricing page lays out the managed option.

Frequently Asked Questions

What are the 5 fields in a cron expression?

Standard cron uses five fields in this order: minute (0-59), hour (0-23), day of month (1-31), month (1-12), and day of week (0-7, where both 0 and 7 are Sunday). The daemon runs your command when all five fields match the current time. Month and day-of-week also accept names like JAN or MON.

Does cron have a seconds field?

No. Standard, POSIX, and Vixie cron all use exactly five fields with no seconds, so the smallest interval you can schedule is one minute. Schedulers like Quartz add a leading Seconds field and an optional Year, giving 6 or 7 fields. That difference is the most common reason a copied expression schedules the wrong time.

Why does my Friday the 13th cron job run on the wrong days?

Because cron ORs the day-of-month and day-of-week fields when both are restricted. An expression like 0 0 13 * 5 runs on the 13th of every month AND every Friday, not only on Friday the 13th. To require both conditions, keep one field as * and test the other inside your command, or use a scheduler that does AND matching.

What timezone do cron jobs run in?

Cron runs in the daemon's timezone, which is the system local time and is usually UTC on servers. So 0 9 * * * is 9am in that timezone, not necessarily your local 9am. Vixie/cronie supports CRON_TZ to override per crontab, but keeping servers on UTC is the simplest way to avoid daylight saving bugs.

What does */5 * * * * mean?

It means "every 5 minutes." The */5 in the minute field is the step operator, firing at minutes 0, 5, 10, and so on through 55, while the four * fields match every hour, day, month, and weekday. It is the most common schedule for keeping a near-real-time copy of fast-moving data.

Questions or feedback? Feel free to reach out. If you found this helpful, you can try Codeless Sync for free.