It's 9:00 AM. Your morning report should have generated. You check the folder. Nothing. You check the cron logs. The job ran at 5:00 AM. Four hours early.
Or maybe it's the opposite. You schedule a backup for 2:00 AM during low traffic. You wake up to complaints that the site was slow at 7:00 PM—peak traffic time. The backup ran during the day instead of at night.
What happened?
Timezones. The silent killer of cron schedules.
This is one of the most frustrating and confusing problems in cron. You set a schedule for "9 AM," but whose 9 AM? Your local time? The server's time? UTC?
This guide explains exactly what's happening and how to fix it permanently. No more mysterious schedule shifts. No more debugging timezone issues at 2 AM.
The Myth: "I Set It for 9 AM, Why Did It Run at 5 AM?"
You're on the East Coast (New York). You create a cron job to run at 9:00 AM:
0 9 * * * /usr/local/bin/generate-report.sh
You expect this to run at 9:00 AM your time (EST/EDT).
What actually happens: The job runs at 5:00 AM your time (or 2:00 PM your time, depending on the server location).
Or worse: It runs at the right time for months, then suddenly shifts by an hour when daylight saving time changes.
The Confusion
The problem stems from three different "9 AMs":
- Your local 9 AM - What you meant when you wrote "9"
- The server's 9 AM - What time it is where the physical server is located
- UTC 9 AM - Universal Coordinated Time (often what the server actually uses)
When you write 0 9 * * *, you're not specifying which 9 AM. Cron picks one for you—and it's probably not the one you want.
Real Examples of This Going Wrong
Scenario 1: The 5 AM Report
You: In New York (EST, UTC-5)
Server: In AWS us-east-1 (configured to UTC)
Cron job: 0 9 * * *
Expected: Report runs at 9:00 AM New York time
Reality: Report runs at 9:00 AM UTC = 4:00 AM EST (5:00 AM EDT)
Scenario 2: The Evening Backup
You: In California (PST, UTC-8)
Server: In Europe (CET, UTC+1)
Cron job: 0 2 * * *
Expected: Backup runs at 2:00 AM California time (low traffic)
Reality: Backup runs at 2:00 AM CET = 5:00 PM PST (peak traffic)
Scenario 3: The Daylight Saving Time Shift
You: In New York
Server: Set to America/New_York
Cron job: 0 9 * * *
Winter: Runs at 9:00 AM EST ✓
Spring (DST starts): Still runs at 9:00 AM EDT ✓
BUT: If server is UTC, it never shifts - now runs at 5:00 AM EDT ✗
The Truth: Cron Runs on Server System Time
Cron doesn't care about your timezone. Cron only cares about the server's system time.
Check Your Server's Timezone
Before fixing anything, know what timezone your server is using:
date
Example output:
Thu Jan 9 14:23:45 UTC 2025
The UTC at the end tells you the server is using UTC (Coordinated Universal Time).
Or check more explicitly:
timedatectl
Example output:
Local time: Thu 2025-01-09 14:23:45 UTC
Universal time: Thu 2025-01-09 14:23:45 UTC
RTC time: Thu 2025-01-09 14:23:45
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
Key line: Time zone: Etc/UTC (UTC, +0000)
Or just get the timezone:
cat /etc/timezone
Example output:
Etc/UTC
Common Server Timezone Configurations
| Environment | Typical Timezone | Offset from UTC | |-------------|------------------|-----------------| | AWS EC2 (default) | UTC | +0000 | | DigitalOcean (default) | UTC | +0000 | | Google Cloud (default) | UTC | +0000 | | Azure (default) | UTC | +0000 | | Heroku | UTC | +0000 | | Most Docker containers | UTC | +0000 | | Ubuntu (default install) | UTC | +0000 | | macOS (local) | Local timezone | Varies |
Notice a pattern? Almost all cloud servers default to UTC.
Why UTC Is the Default
UTC is timezone-neutral. It never observes daylight saving time. It's the same everywhere.
Benefits for servers:
- ✅ No DST complications
- ✅ Logs from multiple servers align perfectly
- ✅ Timestamps are unambiguous
- ✅ Works across global infrastructure
Problems for cron schedules:
- ❌ Not intuitive for humans ("What time is 14:00 UTC in New York?")
- ❌ Requires mental timezone math
- ❌ Easy to make mistakes
- ❌ Confusing when server and user are in different timezones
Understanding Timezone Offsets
When you see a timezone, here's what the offset means:
| Timezone | Offset | Example Cities | Math to UTC | |----------|--------|----------------|-------------| | UTC | +0000 | London (winter), Reykjavik | No conversion needed | | EST | -0500 | New York (winter) | UTC - 5 hours | | EDT | -0400 | New York (summer) | UTC - 4 hours | | PST | -0800 | Los Angeles (winter) | UTC - 8 hours | | PDT | -0700 | Los Angeles (summer) | UTC - 7 hours | | CET | +0100 | Paris (winter) | UTC + 1 hour | | CEST | +0200 | Paris (summer) | UTC + 2 hours | | IST | +0530 | Mumbai (no DST) | UTC + 5:30 hours | | JST | +0900 | Tokyo (no DST) | UTC + 9 hours | | AEST | +1000 | Sydney (winter) | UTC + 10 hours |
Converting Timezone to UTC
You want: 9:00 AM New York time (EST, UTC-5)
9:00 AM EST = 9:00 + 5 = 14:00 UTC (2:00 PM UTC)
Cron job:
0 14 * * * # Runs at 2 PM UTC = 9 AM EST
But wait! When daylight saving time starts:
9:00 AM EDT = 9:00 + 4 = 13:00 UTC (1:00 PM UTC)
Now your cron job is wrong. It runs at 2 PM UTC = 10 AM EDT (one hour late).
This is why manual UTC conversion is painful.
The Fix: CRON_TZ Variable
Modern cron implementations support the CRON_TZ variable, which lets you specify a timezone for your cron jobs.
Basic Syntax
# In crontab -e
CRON_TZ=America/New_York
0 9 * * * /usr/local/bin/generate-report.sh
What this does:
- The cron job runs at 9:00 AM in the America/New_York timezone
- Automatically handles daylight saving time
- No manual UTC conversion needed
- Works regardless of server timezone
Supported Timezones
Use IANA timezone database names (also called "tz database" or "Olson database").
Format: Continent/City
Common timezones:
| Timezone Name | Description |
|---------------|-------------|
| America/New_York | Eastern Time (EST/EDT) |
| America/Chicago | Central Time (CST/CDT) |
| America/Denver | Mountain Time (MST/MDT) |
| America/Los_Angeles | Pacific Time (PST/PDT) |
| America/Phoenix | Arizona (no DST) |
| America/Toronto | Eastern Time (Canada) |
| America/Sao_Paulo | Brazil |
| Europe/London | GMT/BST |
| Europe/Paris | CET/CEST |
| Europe/Berlin | CET/CEST |
| Asia/Tokyo | Japan (no DST) |
| Asia/Shanghai | China (no DST) |
| Asia/Kolkata | India (no DST) |
| Australia/Sydney | Australian Eastern Time |
| UTC | Coordinated Universal Time |
Find your timezone:
timedatectl list-timezones
Or see the full list: IANA Timezone Database
Important: Don't Use Abbreviations
❌ Wrong (don't use):
CRON_TZ=EST # Don't use abbreviations
CRON_TZ=PST
CRON_TZ=GMT
✅ Correct (use full names):
CRON_TZ=America/New_York
CRON_TZ=America/Los_Angeles
CRON_TZ=Europe/London
Why? Abbreviations are ambiguous:
- EST could be US Eastern or Australian Eastern
- CST could be US Central, China, or Cuba
- Full names are precise and unambiguous
Check if Your Cron Supports CRON_TZ
Not all cron implementations support CRON_TZ. Check which cron you're running:
crontab -V
Cron implementations that support CRON_TZ:
- ✅ Vixie Cron (most Linux distributions)
- ✅ ISC Cron
- ✅ cronie (Red Hat, CentOS, Fedora)
- ✅ bcron
- ✅ systemd timers (different syntax)
If not supported:
- ❌ Very old cron versions
- ❌ Some BSD variants (use different syntax)
Test it:
# Add a test job with CRON_TZ
crontab -e
CRON_TZ=America/New_York
* * * * * date >> /tmp/cron-tz-test.txt
Wait a few minutes, then check:
cat /tmp/cron-tz-test.txt
If you see timestamps in New York time, it works!
If you see UTC timestamps, CRON_TZ isn't supported (see workarounds below).
Real-World Example: New York Business Hours
You want to run a report every weekday at 9:00 AM Eastern Time.
The Wrong Way (Without CRON_TZ)
# Server is on UTC
# You calculate: 9 AM EST = 2 PM UTC
0 14 * * 1-5 /usr/local/bin/generate-report.sh
Problems:
-
Winter (EST, UTC-5):
- 2 PM UTC = 9 AM EST ✓ Correct
-
Summer (EDT, UTC-4):
- 2 PM UTC = 10 AM EDT ✗ One hour late
-
DST transitions:
- Spring forward: Report runs late for 6 months
- Fall back: Report runs early for 6 months
You have to manually update the cron job twice a year or accept the wrong time half the year.
The Right Way (With CRON_TZ)
# In crontab -e
CRON_TZ=America/New_York
# Run every weekday at 9 AM Eastern Time
0 9 * * 1-5 /usr/local/bin/generate-report.sh
Result:
- ✅ Winter: Runs at 9:00 AM EST
- ✅ Summer: Runs at 9:00 AM EDT
- ✅ DST transitions: Automatically adjusts
- ✅ No manual updates needed
Set it once, forget it forever.
Complete Example: Multiple Timezones
You have users in different timezones and need to run jobs at local times for each region.
Multi-Timezone Crontab
# File: crontab -e
# Default timezone for most jobs
CRON_TZ=America/New_York
# Eastern time jobs
0 9 * * 1-5 /usr/local/bin/east-coast-report.sh
0 17 * * 1-5 /usr/local/bin/east-coast-eod-summary.sh
# Pacific time jobs (override with new CRON_TZ)
CRON_TZ=America/Los_Angeles
0 9 * * 1-5 /usr/local/bin/west-coast-report.sh
0 17 * * 1-5 /usr/local/bin/west-coast-eod-summary.sh
# European jobs
CRON_TZ=Europe/London
0 9 * * 1-5 /usr/local/bin/uk-report.sh
CRON_TZ=Europe/Paris
0 9 * * 1-5 /usr/local/bin/eu-report.sh
# UTC jobs (monitoring, backups)
CRON_TZ=UTC
0 */6 * * * /usr/local/bin/health-check.sh
0 3 * * * /usr/local/bin/backup-database.sh
How this works:
- Each
CRON_TZline sets the timezone for all jobs below it until the nextCRON_TZ - You can change timezone multiple times in one crontab
- Jobs run at the correct local time for their region
- No manual UTC conversion needed
Per-Job Timezone (Alternative Syntax)
Some cron implementations allow per-job timezone specification:
# Timezone specified inline (not all crons support this)
CRON_TZ=America/New_York 0 9 * * * /usr/local/bin/report.sh
Or using environment variable in the command:
0 9 * * * TZ=America/New_York /usr/local/bin/report.sh
Test this on your system as support varies.
Workarounds When CRON_TZ Isn't Supported
If your cron doesn't support CRON_TZ, here are alternatives:
Option 1: Change Server Timezone
Set the entire server to your timezone:
# List available timezones
timedatectl list-timezones
# Set server timezone
sudo timedatectl set-timezone America/New_York
# Verify
timedatectl
Now all cron jobs run in that timezone.
Pros:
- ✅ Simple to understand
- ✅ Works with any cron version
- ✅ Automatic DST handling
Cons:
- ❌ Affects entire system (may break other things)
- ❌ Not practical for multi-timezone applications
- ❌ Server logs now in local time (harder to correlate across servers)
Option 2: Use TZ Environment Variable in Script
Set timezone inside your script:
#!/bin/bash
# File: /usr/local/bin/generate-report.sh
# Set timezone for this script
export TZ=America/New_York
# Now all date commands use New York time
echo "Report generated at $(date)" >> /var/log/report.log
# Your script logic here
Cron job (runs at UTC time, but script uses NY time):
# This still runs at UTC 2 PM
0 14 * * * /usr/local/bin/generate-report.sh
Pros:
- ✅ Works with any cron version
- ✅ Different scripts can use different timezones
Cons:
- ❌ You still have to convert schedule to UTC manually
- ❌ Schedule breaks during DST transitions
- ❌ Script knows its timezone, but cron schedule doesn't match
Option 3: Use systemd Timers (Modern Alternative)
If you're on a systemd-based Linux distribution, use timers instead of cron:
Create timer file:
# File: /etc/systemd/system/report.timer
[Unit]
Description=Run daily report at 9 AM New York time
[Timer]
OnCalendar=09:00:00
Timezone=America/New_York
Persistent=true
[Install]
WantedBy=timers.target
Create service file:
# File: /etc/systemd/system/report.service
[Unit]
Description=Generate daily report
[Service]
Type=oneshot
ExecStart=/usr/local/bin/generate-report.sh
Enable and start:
sudo systemctl enable report.timer
sudo systemctl start report.timer
# Check status
systemctl list-timers
Pros:
- ✅ Native timezone support
- ✅ More powerful than cron
- ✅ Better logging
- ✅ Dependency management
Cons:
- ❌ Different syntax to learn
- ❌ Not available on all systems
- ❌ More complex setup
Daylight Saving Time: The Double-Edged Sword
When you use CRON_TZ with a timezone that observes DST, be aware of two special moments each year:
Spring Forward (DST Starts)
Example: America/New_York in Spring
At 2:00 AM, clocks jump to 3:00 AM. The 2:00 AM hour doesn't exist.
CRON_TZ=America/New_York
0 2 * * * /usr/local/bin/backup.sh
What happens on DST transition day?
Most cron implementations skip the job since 2:00 AM never occurs.
Solution: Schedule critical jobs outside the 2:00-3:00 AM window:
# Safe: Runs at 1:00 AM or 4:00 AM
0 1 * * * /usr/local/bin/backup.sh
0 4 * * * /usr/local/bin/backup.sh
Or use a timezone that doesn't observe DST:
CRON_TZ=America/Phoenix # Arizona - no DST
0 2 * * * /usr/local/bin/backup.sh
Fall Back (DST Ends)
Example: America/New_York in Fall
At 2:00 AM, clocks jump back to 1:00 AM. The 1:00-2:00 AM hour happens twice.
CRON_TZ=America/New_York
0 1 * * * /usr/local/bin/backup.sh
What happens on DST transition day?
Most cron implementations run the job twice—once at "first 1:00 AM" and again at "second 1:00 AM."
If your job isn't idempotent, this could cause problems:
- Database backup runs twice (wastes space)
- Email report sent twice (users get duplicates)
- Payment processing runs twice (charges customers twice!)
Solution 1: Make your script idempotent:
#!/bin/bash
LOCK_FILE=/var/run/backup.lock
# Check if already running
if [ -f "$LOCK_FILE" ]; then
echo "Backup already running, exiting"
exit 0
fi
# Create lock
touch "$LOCK_FILE"
# Do backup
# ...
# Remove lock
rm "$LOCK_FILE"
Solution 2: Avoid the 1:00-2:00 AM window on DST transition days.
Solution 3: Use UTC for critical jobs:
CRON_TZ=UTC
0 6 * * * /usr/local/bin/backup.sh # Always runs exactly once
Testing Your Timezone Configuration
Before trusting your timezone setup in production, test it.
Test 1: Verify Timezone Recognition
# Add a test job that prints date
crontab -e
CRON_TZ=America/New_York
* * * * * date >> /tmp/tz-test.txt
Wait 2 minutes, then check:
cat /tmp/tz-test.txt
Expected output:
Thu Jan 9 09:15:01 EST 2025
Thu Jan 9 09:16:01 EST 2025
If you see UTC or wrong timezone, CRON_TZ isn't working.
Test 2: Compare Multiple Timezones
CRON_TZ=UTC
* * * * * echo "UTC: $(date)" >> /tmp/multi-tz-test.txt
CRON_TZ=America/New_York
* * * * * echo "NY: $(date)" >> /tmp/multi-tz-test.txt
CRON_TZ=America/Los_Angeles
* * * * * echo "LA: $(date)" >> /tmp/multi-tz-test.txt
Expected output:
UTC: Thu Jan 9 14:20:01 UTC 2025
NY: Thu Jan 9 09:20:01 EST 2025
LA: Thu Jan 9 06:20:01 PST 2025
All three timestamps represent the same moment, just displayed in different timezones.
Test 3: Verify DST Handling
Test near a DST transition (spring or fall). Check that jobs run at the correct local time before and after the transition.
Or force a date change (if you have a test server):
# Set date to day before DST
sudo date -s "2025-03-08 23:00:00"
# Verify your cron runs correctly at transition
Common Timezone Mistakes
Mistake 1: Using Abbreviations
❌ Wrong:
CRON_TZ=PST
0 9 * * * /usr/local/bin/report.sh
✅ Correct:
CRON_TZ=America/Los_Angeles
0 9 * * * /usr/local/bin/report.sh
Mistake 2: Assuming Server Timezone
❌ Wrong assumption:
# "The server is in Virginia, so it must be Eastern Time"
0 9 * * * /usr/local/bin/report.sh
Reality: Most cloud servers are UTC regardless of physical location.
✅ Correct:
# Explicitly set timezone
CRON_TZ=America/New_York
0 9 * * * /usr/local/bin/report.sh
Mistake 3: Not Testing DST Transitions
❌ Wrong:
# Looks good in January...
CRON_TZ=America/New_York
0 2 * * * /usr/local/bin/critical-backup.sh
Problem: Skipped on DST spring-forward day.
✅ Correct:
# Avoid 2 AM for critical jobs
CRON_TZ=America/New_York
0 1 * * * /usr/local/bin/critical-backup.sh
Mistake 4: Mixing Timezone Strategies
❌ Wrong:
# Some jobs use CRON_TZ, others use manual UTC conversion
CRON_TZ=America/New_York
0 9 * * * /usr/local/bin/report.sh
# This is UTC 2 PM... wait, that's 9 AM EST in winter but 10 AM EDT in summer!
0 14 * * * /usr/local/bin/backup.sh
✅ Correct (be consistent):
CRON_TZ=America/New_York
0 9 * * * /usr/local/bin/report.sh
0 14 * * * /usr/local/bin/backup.sh # 2 PM Eastern Time year-round
Or:
# All jobs explicitly set to UTC
CRON_TZ=UTC
0 14 * * * /usr/local/bin/report.sh # 9 AM EST / 10 AM EDT
0 19 * * * /usr/local/bin/backup.sh # 2 PM EST / 3 PM EDT
Pick one strategy and stick to it.
Quick Reference: Timezone Commands
| Task | Command |
|------|---------|
| Check server timezone | date or timedatectl |
| List all timezones | timedatectl list-timezones |
| Change server timezone | sudo timedatectl set-timezone America/New_York |
| Test CRON_TZ support | Add test job and verify output |
| Find your timezone name | IANA Timezone List |
When to Use Each Approach
| Scenario | Best Solution |
|----------|---------------|
| Single timezone, all jobs same local time | Set CRON_TZ once at top of crontab |
| Multiple timezones, different jobs | Multiple CRON_TZ declarations |
| Critical 24/7 jobs, no DST complications | CRON_TZ=UTC |
| Server supports systemd | Use systemd timers with Timezone= |
| Old cron, no CRON_TZ support | Change server timezone or use workaround scripts |
| Job must run at exact UTC time | CRON_TZ=UTC or don't set CRON_TZ at all |
Conclusion: Timezones Don't Have to Be Silent Killers
The timezone problem:
- Cron uses server time (usually UTC)
- You think in local time (EST, PST, etc.)
- Schedules break during DST transitions
- Debugging is painful and time-consuming
The solution:
- Use
CRON_TZto explicitly set timezone - Use full timezone names (America/New_York, not EST)
- Test your configuration before relying on it
- Be aware of DST transition edge cases
- Make critical jobs idempotent
Your checklist:
- ✅ Check your server's current timezone:
date - ✅ Determine which timezone your jobs should use
- ✅ Add
CRON_TZ=Your/Timezoneto crontab - ✅ Test with a frequent job to verify it works
- ✅ Avoid scheduling critical jobs at 1-2 AM (DST risk)
- ✅ Document why you chose that timezone
Before (painful):
# Is this UTC? Server time? My time? Who knows!
0 9 * * * /usr/local/bin/report.sh
After (clear):
# Explicitly runs at 9 AM New York time, handles DST automatically
CRON_TZ=America/New_York
0 9 * * * /usr/local/bin/report.sh
Stop debugging timezone issues at 2 AM. Set CRON_TZ and sleep soundly knowing your jobs run when they should.
Ready to create timezone-aware cron schedules? Use our Cron Expression Generator to build and test your schedules with confidence.
Related Articles
Essential Troubleshooting:
- Why Your Cron Job Isn't Running - Complete debugging guide
- Environment Variables in Cron Jobs - Fix environment issues
- Where Did My Cron Job Output Go? - Logging guide
Master the Basics:
- The Ultimate Guide to Cron Jobs - Comprehensive tutorial
- Cron Operators Explained: *, /, -, and , - Syntax deep dive
- Organize Your Crontab Like a Pro - Best practices
Production-Ready Examples:
- Automate Database Backups with Cron - Backup automation
- 10 Essential Cron Jobs for Web Developers - Must-have tasks
- Secure Your Cron Jobs from Attacks - Security hardening
Keywords: cron timezone, crontab timezone, cron job wrong time, cron_tz, cron utc, cron daylight saving time, cron dst, timezone cron jobs, cron server time, cron local time