macOS 上で cron を使ってバッチ処理や監視スクリプトを動かしている開発者・SRE 担当者に向けた内容です。
個人の自動化環境だけでなく、開発マシンや検証用サーバーとして Mac を使っているチームにも関係する落とし穴を整理します。
macOS を Ventura 以降、特に Sequoia(15.x)や Tahoe に上げたあと、crontab に登録したジョブが「エラーも出さず」に動かなくなる事象が報告されています。crontab -l を実行すると登録内容はそのまま表示されるのに、実際のジョブは実行されていない、という状態です。
通知メールも届かず、ダッシュボードが更新されないなど、実害が出てから初めて気づくケースが目立ちます。
何が起きているのか:静かに止まる cron
最も厄介なのは、この現象がエラーとして表面化しない点です。
従来 cron の実行に失敗した場合、標準出力や標準エラーがローカルの /var/mail/<username> に届く仕組みがありました。
cron 経由のジョブに問題があれば、メールボックスを見ればログが残っている、という前提です。
ところが Sequoia 環境ではこの仕組みがデフォルトで機能しないケースがあり、失敗の痕跡自体が残らないことがあります。
つまり「エラーが来ないから正常に動いている」という判断は成立しません。
毎朝配信されるはずの日次レポートが届かない、定期実行のはずのデータ集計が止まっている、といった形で初めて異常に気づく構図です。
SRE の視点で見ると、これは典型的な「サイレント障害」です。
アラートが鳴らない障害は、監視対象そのものが「動いていること」を能動的に確認する仕組みがない限り検知できません。
cron のジョブ実行有無をヘルスチェック(定期的に生存確認する仕組み)として外部から観測していなければ、気づくのは影響が出たあとになります。
なぜ起きるのか:cron と launchd の力関係の変化
原因を段階的に見ていきます。
まず macOS のプロセス管理の主体は、Ventura 以降 launchd(PID 1 で動く macOS 標準のサービス管理デーモン)に一本化される方向に進んでいます。cron はもともと Unix 系 OS の伝統的なジョブスケジューラですが、Apple は互換性維持のためだけに残しており、積極的な保守対象ではなくなっています。
次に、この変化がユーザーセッションとの結びつき方に影響します。
macOS はセキュリティ・省電力の観点から、バックグラウンドデーモンとユーザーセッションの結合を段階的に緩めてきました。
その結果、/usr/sbin/cron というバイナリ自体は存在していても、実際には起動していない、という状態が普通に起こり得ます。
さらに、通知経路の変化も重なります。
かつては cron ジョブの標準出力・標準エラーがローカルメールに転送される設定が生きていましたが、Sequoia ではこの転送が既定で機能しないことがあります。
「エラーが出たら気づける」という前提そのものが崩れているため、二重に気づきにくい状態になっています。
自分の環境が該当するか確認する方法
次のコマンドで、cron デーモンが実際に動作しているかを確認できます。
# crontab の登録内容そのものは残っているかを確認
crontab -l
# cron デーモンが launchd のサービスとして実際に起動しているか確認
launchctl list | grep cron
# macOS のバージョンを確認(Ventura=13.x, Sonoma=14.x, Sequoia=15.x)
sw_vers -productVersioncrontab -l にはジョブが表示されるのに、launchctl list | grep cron の結果が空、という組み合わせが今回の問題の典型パターンです。
この状態であれば、crontab に登録したジョブは事実上「誰にも実行されない設定」になっています。
あわせて、直近のジョブが本当に走っているかどうかは、ジョブ内で明示的にログファイルへ出力しているかどうかで確認できます。
ログの最終更新日時が想定より古ければ、それだけでも実行停止のサインになります。
# ジョブが出力しているはずのログの最終更新時刻を確認
ls -la ~/path/to/job.logもしログ出力の仕組み自体を用意していない場合、この機会に「実行された証拠を残す」設計に変えておくことをおすすめします。
対策:launchd への移行手順
cron からの脱却先は launchd の plist(XML 形式の設定ファイル)です。
plist は 1 ジョブあたり 20〜30 行程度になり、*/5 * * * * cmd のような 1 行で済む cron の記法に比べると冗長です。
この冗長さが移行の心理的なハードルになりますが、ジョブ数が多い場合は手作業ではなくスクリプトで crontab の内容を一括変換するのが現実的です。
移行時に必ず確認すべきポイントは次の3点です。
- PATH の明示指定: launchd はシェルの設定ファイル(
.zshrcや.bashrc)を読み込まないため、nvm 経由の node など PATH に依存するコマンドはEnvironmentVariablesキーで明示しないとcommand not foundになります - 実行タイミングの指定方法: cron の
*/5 * * * *形式ではなく、StartCalendarInterval(時刻ベースでの起動指定)やStartInterval(秒間隔での起動指定)を使う書式に変換する必要があります - 標準出力・標準エラーの出力先:
StandardOutPathとStandardErrorPathを明示しておかないと、実行結果がどこにも残らず、失敗しても気づけません
plist を配置する場所は ~/Library/LaunchAgents/ 配下です。
配置後は次のコマンドで読み込み・起動状態を確認します。
# plist を launchd に読み込ませる
launchctl load ~/Library/LaunchAgents/com.example.myjob.plist
# 登録されたジョブが認識されているか確認
launchctl list | grep com.example.myjobRunAtLoad を true にしておくと、Mac 起動直後や plist 読み込み直後に一度実行されるため、設定ミスの有無をすぐに確認できます。
併せて LowPriorityIO や Nice(プロセスの優先度指定)を設定しておくと、バックグラウンドジョブが他の作業を圧迫しにくくなります。
運用として仕組み化しておきたいこと
個人の自動化スクリプトであっても、開発環境の CI 補助やデータ集計を Mac 上の定期ジョブに頼っている場合、これは立派な可用性の問題です。
SRE の文脈で言えば、この種の障害は SLO(サービスレベル目標)を設定していない対象ほど気づくのが遅れます。
「毎朝8時までにレポートが生成される」という期待値自体が、暗黙の SLO だったと捉えると仕組み化の方向が見えてきます。
- ジョブの実行有無を外形監視する仕組みを用意する(例: ジョブの最後に外部のヘルスチェック URL を叩く、ログの更新時刻を別プロセスで監視する)
- OS アップデート後は
launchctl listでサービス起動状態を必ず確認する運用ルールを決めておく - crontab に依存した個人スクリプトを、チームで使う CI/CD や IaC(Terraform などによるインフラのコード管理)の管轄下に移せないか棚卸しする
- ログや実行結果の出力先をローカルメールだけに依存させない
Mac 単体での定期実行に頼る設計は、OS のアップデート方針に振り回されやすい構造を抱えています。
恒久対応として、重要なバッチ処理はクラウド上のスケジューラ(例: GitHub Actions のスケジュールトリガーやクラウドの Cron サービス)に移す選択肢も検討する価値があります。
まとめ
macOS の cron は Ventura 以降、launchd に主役を譲る方向で扱いが変わってきています。
Sequoia 環境では cron デーモンが起動していなくてもエラーが表面化しないため、まず launchctl list | grep cron で自分の環境の実態を確認してください。
該当する場合は、ジョブを launchd の plist に移行し、PATH の明示・実行タイミングの書式変換・ログ出力先の指定を一つずつ潰していく作業になります。
あわせて、ジョブが実際に動いているかを外形的に確認する監視を用意しておくと、次に似たサイレント障害が起きても早期に気づけます。