cron の限界と systemd timer が選ばれる背景
サーバー運用の現場では、長年にわたって cron がバッチスケジューラーの事実上の標準として使われてきました。しかし systemd が主要ディストリビューションの init として定着した現在、cron には見えにくいいくつかの課題が顕在化しています。
代表的な課題として挙げられるのは次の3点です。まず、実行ログが標準ではメールでしか取れず、/var/log/syslog へ断片的に流れるため、後から調査しにくい点。次に、前回の実行が終わっていないのに次回が起動する「多重起動」を防ぐ仕組みが cron 自体には備わっていない点。そして、実行順序や依存するサービスの起動を制御するには自前のラッパースクリプトに頼らざるを得ない点です。
systemd timer はこれらをユニットファイルの宣言だけで解消します。ログは journald に統合され、依存関係は After= や Wants= で記述でき、多重起動は ExecStart= が1プロセスに限定される .service の仕組みでそもそも起きません。移行コストは初期設定に集中しますが、一度整備すると可観測性と保守性が大きく向上します。
.service ファイルと .timer ファイルの基本構成
systemd timer は「何を実行するか」を記述する .service ユニットと、「いつ実行するか」を記述する .timer ユニットの2ファイルで構成されます。同名にすることで自動的に紐付けられるのが設計上の特徴です。
以下は在庫データを同期するバッチを想定した最小構成です(想定例)。
# /etc/systemd/system/sync-inventory.service
[Unit]
Description=Sync inventory data
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=oneshot
User=appuser
ExecStart=/opt/batch/sync_inventory.sh
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/sync-inventory.timer
[Unit]
Description=Run sync-inventory every day at 02:00
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
Type=oneshot はコマンドが終了するまで systemd がそのプロセスを「active」とみなす設定で、バッチ処理に適しています。Persistent=true を指定すると、タイマーが停止していた期間中に実行されるはずだったジョブを、次回の起動時に一度だけ補完実行します。システム停止中に時刻をまたいだ場合のカバーとして有効です。
ファイルを配置したら systemctl daemon-reload でユニット定義を再読み込みし、systemctl enable --now sync-inventory.timer でタイマーを有効化・即時起動します。
依存関係の解決(After= / Wants= / RequiresMountsFor=)
cron から移行するときに最も効果が大きいのが、依存関係の明示的な宣言です。cron では「DB が起動していなければジョブが失敗する」ような状況をスクリプト内のリトライで吸収していたケースが多く、暗黙の前提として積み重なっていることがあります。
主要なディレクティブの使い分けは次のとおりです。
- After=:起動順序を指定します。指定したユニットが active になってから本ユニットを起動します。依存の有無は問わず、順番だけを制御したい場合に使います。
- Wants=:指定ユニットの起動を試みますが、失敗しても本ユニットは継続します。ネットワークや NTP など「あれば望ましい」依存に使います。
- Requires=:Wants= の強版です。指定ユニットが起動に失敗すると本ユニットも起動しません。DB 接続が必須のバッチには Requires= の方が安全な場合があります。
- RequiresMountsFor=:指定パスがマウント済みであることを前提にします。NFS や外部ストレージ上のスクリプト・データを扱うバッチで有効です。
一般的なつまずきとして、network-online.target への依存を忘れたまま外部 API を叩くバッチを設定し、ネットワーク到達前に実行されて失敗するケースがあります。After=network-online.target と Wants=network-online.target をセットで書く習慣をつけると防ぎやすくなります。
また、タイマー自体に依存を書くより .service 側に書くことが推奨されます。タイマーはスケジューリングの関心だけを持ち、実行条件の宣言はサービスに寄せるとユニットの責任が明確になります。
journald によるログ統合と可観測性
cron ジョブのログ調査でよくある課題が、実行結果が散在することです。標準出力はメールに流れ、スクリプト内部の logger 呼び出しは syslog に混在し、エラーを見つけるのに複数の場所を確認しなければならない状況は珍しくありません。
systemd timer ではジョブの標準出力・標準エラーがそのまま journald に記録されます。ユニット名でフィルタリングできるため、次のコマンドで対象バッチだけの実行履歴をすぐに確認できます。
# 直近の実行ログを確認
journalctl -u sync-inventory.service -n 50 --no-pager
# タイムスタンプ付きで本日のログのみ表示
journalctl -u sync-inventory.service --since today
# タイマーの次回実行予定を一覧表示
systemctl list-timers --all
systemctl list-timers は全タイマーの「最終実行時刻」「次回実行時刻」「経過時間」を一覧で表示します。複数のバッチを管理している環境では、cron の crontab より格段に把握しやすくなります。
Prometheus や Grafana Loki と連携する場合は、systemd_journal エクスポーターや promtail の journald 入力を使うと、バッチの終了ステータスや実行時間をメトリクス化できます。ログの一元管理と監視の組み合わせは、cron 時代には自前で組み上げる必要があったものが、systemd 標準の仕組みで実現できる点が大きな利点です。
移行手順と段階的な検証
本番稼働中のバッチを cron から systemd timer へ切り替えるときは、一度に全件移行するより段階的に進める方がリスクを抑えられます。次のような手順が実践的です。
Step 1:ユニットファイルの作成とテスト実行
まずタイマーを有効化せず、systemctl start sync-inventory.service で手動実行します。systemctl status sync-inventory.service と journalctl -u sync-inventory.service -n 20 で正常終了・ログ出力を確認します。
Step 2:ドライランで時刻を確認
systemd-analyze calendar "*-*-* 02:00:00" で OnCalendar= の記述が意図した時刻に解釈されているかを検証します。複雑な時刻表現(毎月第1月曜など)は特に事前確認が重要です。
Step 3:タイマーを有効化し、cron エントリをコメントアウト
systemctl enable --now sync-inventory.timer でタイマーを起動し、crontab の対応エントリをコメントアウト(削除ではなく)します。数回のスケジュール実行を見届け、期待どおりに動作することを確認します。
Step 4:cron エントリの削除
安定動作を確認してから crontab のエントリを削除します。コメントアウト期間を最低でも1週間程度設けると、週次・月次で動くバッチも検証できます。
ユニットファイルに構文エラーがあっても systemctl daemon-reload 時点では警告のみで失敗しないケースがあるため、systemd-analyze verify sync-inventory.service を移行前に実行する習慣を持つと安心です。
切り戻し設計と 2026 年の現行環境差分
何らかの問題が発生したときに素早く cron へ戻せるよう、切り戻し手順をあらかじめ整理しておくことが重要です。操作は単純で、タイマーを停止・無効化し、crontab のコメントアウトを解除するだけです。
# タイマーの停止と無効化
systemctl stop sync-inventory.timer
systemctl disable sync-inventory.timer
# 状態確認
systemctl status sync-inventory.timer
# → inactive (dead) になっていることを確認
# crontab を元に戻す(コメントアウトを解除)
crontab -e
ユニットファイル自体は /etc/systemd/system/ に残していても害はなく、タイマーが無効であれば実行されません。設定の見直し後に再度 enable するだけで元に戻せるため、ファイルを削除する必要はありません。
2026 年時点の現行環境差分
RHEL 9 / Rocky Linux 9 / AlmaLinux 9 ではデフォルトで cronie がインストールされているものの、systemd の推奨はユニットファイル方式です。Ubuntu 24.04 LTS(Noble Numbat)以降は systemd 255 系が採用されており、OnCalendar= に weekly monthly などのエイリアスに加えて semiannually(半年ごと)が正式サポートされています。
Debian 12(Bookworm)以降では /etc/systemd/system/ への配置が標準で、/lib/systemd/system/ はパッケージ管理のユニットに限定されるガイドラインが明確化されています。自前のバッチは /etc/systemd/system/ への配置を徹底することで、パッケージアップデートによる上書きを防げます。
コンテナ環境(Kubernetes CronJob など)では systemd timer ではなくプラットフォーム固有のスケジューラーが主流ですが、コンテナホスト自体のメンテナンスバッチや、コンテナ外で動くデータ収集・ローテーション処理については引き続き systemd timer が実用的な選択肢です。ホスト側のバッチ管理を systemd に統一しておくことで、ログ調査と依存管理の窓口を一本化できるメリットは現行環境でも変わりません。
「コマンドは打てる。次は”現場で通用する型”を最短で身につけたい」——そんなあなたへ。
ネットの断片的なTipsをコピペするだけでは、体系的な運用スキルはなかなか積み上がりません。リナックスマスター.JPの無料メルマガ「リナマガ」では、現場で実際に使うLinuxサーバー運用の考え方を、順を追って実務目線でお届けしています。いま登録された方には、安全なサーバー構築の「型」をまとめた『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼント中です。
>> 図解60P『Linux入門マニュアル』を無料で受け取る
(メルマガ登録は10秒・いつでも解除OK)
※ 「独学の時間がもったいない」「プロから現場の技術を最短で学びたい」という本気の方には、2日で実務レベルが身につく初心者向けハンズオンセミナーもご用意しています。
PR・広告
[試して理解]Linuxのしくみ 増補改訂版(Amazon)
プロセス・権限・ファイルシステムなどLinux内部を図解で理解できる一冊。運用判断の勘所に。
※ Amazonのアソシエイトとして、当サイトは適格販売により収入を得ています。
