Troubleshooting Time Zone Issues in Cron Jobs
How to deal with different time zones when configuring crontab jobs.
Adding a job to a crontab seems simple at first glance, but problems can arise when the job is scheduled to run at the same time but in different time zones.
Jobs suddenly start an hour early, reports are generated overnight, or backups overlap after daylight saving time or winter time.
If you've ever asked yourself:
- "Why does the job run twice?"
- "Why doesn't the job run at all?"
- "Why does the production environment behave differently than the test environment?"
—this article is for you.
1. The Main Problem: Cron Runs in Server Time, Not Real Time
By default, cron uses the server's time zone.
$ date
Thursday, January 2, 2026, 7:00 PM CET
If your server is in the UTC, CET, PST time zone... but the application's business logic assumes a completely different time zone.
2. The Silent Killer: Daylight Saving Time (DST)
DST causes two critical problems:
❌ The job runs twice
When we turn back the clocks:
02:00 → 01:00
A cron job scheduled for 01:30 may run twice.
❌ The job never runs
When we set the clocks forward:
02:00 → 03:00
The cron job scheduled for 02:30 never runs.
⚠️ Cron doesn't have built-in daylight saving time (DST) support.
3. Anti-Patterns to Avoid
❌ Scheduling Business Logic Directly in Cron
0 9 * * * send_daily_report
This assumes:
- server time zone = company time zone
- daylight saving time never changes
- the environment is consistent
All assumptions are incorrect.
❌ Changing the server time zone for each application
timedatectl set-timezone Europe/Warsaw
This breaks:
- logs
- monitoring
- containers
- multi-tenant servers
4. The Golden Rule (IT Standard)
Always run cron in UTC. Handling time zones in application code.
How they do it:
- Cloud providers
- Banks
- SaaS platforms
- Monitoring tools
Do it.
5. Correct architecture for time zone-safe cron jobs
✅ Step 1: Server and cron → UTC
timedatectl set-timezone UTC
Cron:
* * * * * php bin/console cron:runner
Cron runs frequently, not "on a schedule."
✅ Step 2: The application decides when to run
In your application:
$nowUtc = new DateTimeImmutable('now', new DateTimeZone('UTC'));
$jobTime = new DateTimeImmutable('09:00', new DateTimeZone('Europa/Warszawa'));
if ($nowUtc->setTimezone($jobTime->getTimezone())->format('H:i') === '09:00') {
runJob();
}
✔ Daylight Saving Time
✔ Predictable
✔ Testable
6. Store Schedules with Time Zone Metadata
Never store just cron_expression.
❌ Bad
{
"cron": "0 9 * * *"
}
✅ Good
{
"cron": "0 9 * * *",
"timezone": "Europe/Warsaw"
}
Your scheduler must:
- Convert
now()→ job timezone - Run the cron job
- Run once
- Block execution
7. Idempotence: Protecting against duplicate executions
Daylight saving time or retries can still cause duplicates.
Always use execution locks:
UNIQUE(job_id, schedule_at)
Or distributed locks:
- Redis
- Database lock
This guarantees: The job will run at most once on schedule.
8. Logging: Always log in UTC time
[2026-01-02T18:00:00Z] Job executed
Convert only in the UI:
18:00 UTC → 19:00 CET
This saves hours of debugging.
9. Monitoring and Alerting
Timezone errors are invisible without monitoring.
You should detect:
- ❌ Task not executed
- ❌ Task executed twice
- ❌ Task executed late
And notify about it via:
- Slack/Discord
10. Checklist: Configuring cron for production
- ✅ Server time zone = UTC
- ✅ Application decides execution time
- ✅ Time zone saved for each task
- ✅ Logic for daylight saving time (DST)
- ✅ Idempotent execution
- ✅ UTC logs
- ✅ Monitoring and alerting
Summary
Cron itself is inherently "dumb." This isn't a flaw—it's your responsibility as an engineer.
If your system:
- Operates in multiple countries
- Has users in different time zones
- Performs critical tasks
➡️ Time zone-aware scheduling is not optional.
Want to monitor cron jobs and get notifications when they fail? Check out CronMonitor.app—simple monitoring that notifies you when something goes wrong.
Top comments (2)
This is exactly why scheduling should never be the decision layer.
Cron should wake the system.
The application should decide whether execution is justified at all.
Same principle applies to AI outputs.
Exactly! I agree 100%. Cron itself is, to put it mildly, "stupid" :)
A reliable trigger, but completely unaware of context.