Skip to main content
Technology

cron Timezone Pitfalls - Avoiding Drift in Scheduled Jobs

cron's Time Zone Behavior - Hidden Dependence on System Settings

Linux's cron daemon by default interprets schedules using the system's time zone (/etc/localtime or the TZ environment variable). Writing "0 2 * * *" means 2:00 AM in the system's local time. This works intuitively on a single server, but in environments where multiple servers run different time zones or cloud instances default to UTC, jobs run at unintended times.

AWS EC2 instances default to UTC. To run a batch job at 2:00 AM Japan time, you must write "0 17 * * *" (UTC 17:00 = JST 2:00 the next day). Manual conversion is error-prone, and DST regions add another layer of complexity that further confuses the calculation.

DST Hazards - Double Execution and Skips

When cron runs on local time in a DST region, problems arise twice a year. At spring transitions (e.g., 2:00 AM jumping to 3:00 AM), jobs scheduled for 2:00-2:59 are skipped entirely. At fall transitions (e.g., 2:00 AM falling back to 1:00 AM), jobs scheduled for 1:00-1:59 run twice.

A database backup running twice may cause little real harm, but a billing or aggregation batch running twice is a serious problem. Conversely, a critical daily job that gets skipped may go unnoticed until the next day. The fundamental solution is to run cron in UTC, sidestepping the issue entirely.

UTC Operation - The Safest Default

Setting the system time zone to UTC and writing cron schedules in UTC is the safest operational pattern. UTC has no DST, so neither double execution nor skipping can occur in principle. To run something at "2:00 AM Japan time," write "0 17 * * *" once and forget it; the job runs at the same UTC moment year-round.

UTC operation has caveats too. A job intended to run at "midnight on the first of every month (JST)" becomes "15:00 on the last day of the previous month" in UTC. Since the last day varies between 28 and 31, no fixed cron expression captures it perfectly. Such jobs need to start at a fixed UTC time and have their internal logic verify whether the JST month has changed.

Kubernetes CronJob - The timeZone Field

Kubernetes CronJob has supported the timeZone field since v1.27 (GA). Setting spec.timeZone to an IANA name like Asia/Tokyo causes the schedule to be interpreted in that zone, eliminating manual UTC conversion and improving readability. Engineers can write the schedule the way they think about it.

Even with timeZone, DST behavior still warrants attention. The Kubernetes controller acknowledges DST transitions, but its handling of skipped or duplicated times can vary by version. For mission-critical jobs, defining schedules in UTC and reserving timeZone for cases where readability matters most is the safer compromise.

Cloud Schedulers - Built-In Time Zone Support

AWS EventBridge Scheduler, Google Cloud Scheduler, and Azure Logic Apps all support time zone specification natively. AWS EventBridge accepts a time zone in the ScheduleExpression and handles DST transitions automatically. Each service handles "non-existent time during spring forward" differently, however, so check the documentation for the specific service in use.

Whatever scheduler you use, document the intent of every schedule in comments. "0 17 * * *" alone tells future maintainers nothing about whether this is meant to be UTC 17:00 or JST 2:00 expressed in UTC. Even infrastructure-as-code benefits from explicit comments documenting the intended time zone, the same way you would document business logic in application code.

XB!LINE

Was this article helpful?

Related Articles