cronのタイムゾーンの罠 - サーバー時刻と日本時間のズレ
cronは通常、OS(システム)に設定されたタイムゾーンを基準にスケジュールを解釈します。それが利用者の期待するタイムゾーンとは限らないため、式が正しくても実行時刻がずれることがあります。
Cron normally interprets schedules in the system timezone, which may differ from the timezone you expect. UTC defaults in servers and containers are a common reason a job runs nine hours late in Japan.
1. UTCサーバーではJSTのつもりの式が9時間ずれる
# 毎日9:00(cronが解釈するタイムゾーン)
0 9 * * * /path/to/job.shクラウド環境、最小構成のLinuxイメージ、Dockerコンテナなどは、デフォルトがUTCであることが多いです。上の式を「日本時間の9時」のつもりで書いても、サーバーがUTCならUTC 9:00、つまりJST 18:00に実行されます。
2. システムのタイムゾーンを確認する
# systemd環境
timedatectl
cat /etc/timezone
datetimedatectl の「Time zone」、/etc/timezone の地域名、date の出力に含まれるUTCやJSTなどを確認します。環境によっては /etc/timezone が存在しないため、複数の情報を照合してください。
3. CRON_TZでスケジュールの解釈を変える
CRON_TZ=Asia/Tokyo
0 9 * * * /path/to/job.shVixie cron系やcronieなどには、CRON_TZ=Asia/Tokyo をcrontabの先頭やジョブの前に指定し、そのジョブまたは以降の行のスケジュール解釈を変更できる実装があります。ただしCRON_TZはPOSIX標準ではない実装依存の拡張です。利用中の環境で man 5 crontab を確認してください。
4. システム全体のタイムゾーンを変更する
sudo timedatectl set-timezone Asia/Tokyo
timedatectlsystemd環境ではシステム全体をJSTへ変更できます。cron以外のログやアプリケーションにも影響するため、運用方針を確認してから変更します。UTCで統一する方針なら、式をUTCへ換算する方法もあります。
5. DockerではtzdataとTZ設定を確認する
# Dockerfileの例(Debian/Ubuntu系)
RUN apt-get update && apt-get install -y --no-install-recommends tzdata
ENV TZ=Asia/Tokyo
# ホスト設定を使う例
docker run -v /etc/localtime:/etc/localtime:ro ...コンテナは素の状態ではtzdataがなく、UTCのままになることがあります。ベースイメージに合ったtzdataパッケージの導入と TZ 環境変数の設定、または /etc/localtime のマウントが必要になる場合があります。イメージやcron実装ごとに挙動を確認してください。
6. クラウド・コンテナスケジューラを比較する
次は2026年7月の執筆時点における代表例です。サービスの世代やバージョンで仕様が異なるため、利用中の公式ドキュメントを最終確認してください。
| サービス | タイムゾーンの扱い | 確認ポイント |
|---|---|---|
| AWS EventBridge scheduled rule(従来機能) | UTC基準 | EventBridge Schedulerとは別機能 |
| AWS EventBridge Scheduler | UTCまたはIANA名を指定可能 | --schedule-expression-timezone と夏時間 |
GitHub Actions on.schedule | UTC基準 | 高負荷時の遅延にも注意 |
| Kubernetes CronJob | v1.27以降は .spec.timeZone がstable | 未指定時はcontroller側のローカル時刻 |
| Google Cloud Scheduler | IANA名を指定可能、既定UTC | 夏時間による重複・欠落 |
クラウドのcron構文はLinuxの5フィールド形式と一致するとは限りません。AWSの一部機能は年フィールドや ? を使います。Linux用の式をそのまま貼らず、サービスが表示する次回実行予定も確認します。
AWS EventBridgeなどのクラウドスケジューラでは、cron式がUTC基準であることが一般的です。一方、タイムゾーンを指定できる機能や対象サービスもあり、構文もLinux cronとは異なる場合があります。利用サービスのドキュメントで、基準時刻と夏時間の扱いを確認してください。
7. 9時間ずれをログから切り分ける実例
「JST 9:00の集計が18:00に届いた」場合、まず設定変更をせず、予定時刻、cronの起動記録、ジョブ自身のログを同じ基準へ変換して並べます。
date -Is
TZ=UTC date -Is
TZ=Asia/Tokyo date -Is
journalctl -u cron --since '2026-07-22 00:00:00'
printf 'job_start=%s epoch=%s
' "$(date -Is)" "$(date +%s)"- crontabが
0 9 * * *であることを確認します。 timedatectlがUTCなら、予定はUTC 9:00だと仮説を立てます。- システムログとジョブログのUNIX時刻を照合します。
- UTC 9:00がJST 18:00になることで差を説明できるか確認します。
CRON_TZ、UTC換算、システム変更から運用方針に合う方法を選びます。
ログ表示だけがUTCで、実行自体は予定どおりの場合もあります。UNIX時刻はタイムゾーンに依存しない比較軸になるため、ISO形式と併記すると誤認を減らせます。
8. systemd timerを代替として使う
systemd環境では、cronの代わりにtimerユニットの OnCalendar= で定期処理を起動できます。依存関係、実行ユーザー、journalへの記録をユニットとして管理でき、次回時刻を確認できる点が便利です。
# daily-report.timer の概念例
[Timer]
OnCalendar=*-*-* 09:00:00 Asia/Tokyo
Persistent=true
systemd-analyze calendar '*-*-* 09:00:00 Asia/Tokyo'対応構文やタイムゾーン指定はsystemdのバージョンで差があり得ます。man systemd.time と systemd-analyze calendar で確認してください。停止中に逃した実行の補完を使う場合はジョブの冪等性も検討します。
9. 次回実行時刻を事前に確認する
設定後はCron Parserで次回実行予定を確認し、表示と実行環境のタイムゾーンが一致しているかも確認します。UTCとJSTの対応はTimezone Converterで変換できます。
よくある間違い
- cronは自分のローカル時刻で動くと思う: 基準は通常、実行システム側です。
- CRON_TZは全てのcronで使えると思う: POSIX標準ではなく、manページで確認します。
- TZだけで必ず直ると思う: スケジュール解釈と表示時刻が別の場合もあります。
- クラウドとLinux cronを同一視する: フィールド数、基準時刻、夏時間が異なります。
- ログ表示を実行時刻そのものと思う: journalやアプリが別のタイムゾーンで表示する場合があります。
- 9時間差を全地域へ当てはめる: 夏時間のある地域は固定換算では不十分です。
- コンテナはホストと同じ設定だと思う: tzdata、
TZ、/etc/localtimeを確認します。 - 変更後すぐ本番だけで試す: 無害な短周期ジョブで検証します。
English summary
Cron usually evaluates schedules in the system timezone. On a UTC server, 0 9 * * * runs at 18:00 JST. Check the host with timedatectl, cat /etc/timezone, or date. Some Vixie-cron-derived implementations support CRON_TZ=Asia/Tokyo, but it is not POSIX and must be verified in man 5 crontab. Containers may also need tzdata and explicit timezone configuration.