Linuxサーバーを管理しているみなさま、こんにちは!赤猫座の道具店の店主です🐱
サーバーで定期的に動かしたい処理って、数え切れないほどありますよね。深夜のデータベースバックアップ、溜まったDockerコンテナやログの自動お掃除、Let's EncryptのSSL証明書更新チェック、外部APIとの定期同期……。
そんな「定期実行」といえば、Linuxの世界で半世紀近く君臨してきた大ベテラン「cron(crontab)」が真っ先に思い浮かぶはずです。crontab -e と叩いて、おなじみの5つのアスタリスク(0 3 * * * /path/to/script.sh)を1行書き足すだけ。シンプルで手軽、手書き感覚で設定できる素晴らしい道具でした。
……しかし、サーバー運用を長く続けていると、誰もが一度は「cronの地雷」を踏んで深夜に冷や汗を流すことになります。
「手動でスクリプトを実行したら完璧に動くのに、cronから動かすとなぜか音もなく死んでいる……」
「エラーログがどこにも出力されておらず、なぜ落ちたのか原因が一切わからない……」
「処理が長引いて前回のプロセスが終わる前に次回が起動し、二重起動でサーバーのCPUとメモリが昇天した……」
そんな「cronあるある」に長年頭を抱えていた店主ですが、数年前からすべての定期バッチ処理を「systemd service + systemd timer」へ完全移行しました。その結果、トラブルは激減し、運用の安心感が劇的に跳ね上がりました。
今回は、「なぜ今さらcronを捨ててsystemd timerを使うべきなのか?」という理由から、具体的な設定方法、現場で命を救われる神機能まで、実践コード付きで徹底解説します!
- 脱cronの決定打:環境変数の罠、ログ消失、二重起動、次回予定の不透明さをsystemdが一挙に解決。
- 2ファイル1組の基本構造:「何を実行するか(.service)」と「いつ実行するか(.timer)」を分離する美しさ。
- 実践ステップバイステップ:自動バックアップタスクを例に、ユニットファイルの書き方から有効化まで完全網羅。
- 現場を救う神機能:サーバー停止中の実行漏れを救う
Persistent=true、負荷集中を防ぐRandomizedDelaySec。 - 運用・デバッグの極意:
journalctlによるログ追跡、systemd-analyze calendarによる構文検証、単体テストの手軽さ。
1. なぜ今、cronではなく「systemd timer」なのか? 〜cronの4大苦痛〜
長年愛されてきたcronですが、現代のLinux運用においては構造的な弱点が目立つようになってきました。特に痛感する4つの苦痛を振り返ってみましょう。
苦痛①:環境変数(PATH)の罠
cronで最も初心者が引っかかるのがこれです。ターミナル上で手動実行すると動くのに、cron経由だと command not found で沈黙します。
ターミナルでは .bashrc や .profile によって PATH=/usr/local/bin:/usr/bin:... や LANG=ja_JP.UTF-8 が読み込まれていますが、cronは極限まで削ぎ落とされた最小限の環境変数(PATH=/usr/bin:/bin程度)で実行されます。そのため、docker や node、aws-cli などのパスが通らずに即死するのです。
苦痛②:ログが「虚無」に消える問題
cronの標準出力・標準エラー出力は、デフォルトでは「ローカルのメール(/var/mail)」に送られます。しかし現代のサーバーでローカルメールサーバー(Postfix等)を動かしていない場合、ログは完全に虚空へと消滅します。
対策として >> /var/log/myjob.log 2>&1 のようにリダイレクトを書くのが定石ですが、ログローテーション(logrotate)の設定を忘れると今度はディスク容量を食いつぶす爆弾になります。
苦痛③:二重起動の防止がめんどくさい
バッチ処理のデータ量が増えて実行時間が予定のインターバルを超えてしまったとき、cronは無慈悲に「次回のプロセス」を多重起動します。これが連鎖するとサーバーが固まってロードアベレージが100を超えます。
これを防ぐには flock -n /var/lock/myjob.lock /path/to/script.sh のようにロックコマンドでラップする必要がありますが、毎回書くのは骨が折れます。
苦痛④:状態や次回予定の可視化ができない
「このジョブ、最後に成功したのは何時何分?」「実行に何秒かかった?」「次回は具体的に何時何分に走る予定?」……cronの管理画面(といってもcrontabファイルそのもの)からは、これらを即座に把握する手段がありません。
徹底比較表:cron vs systemd timer
systemd timerは、Ubuntu・Debian・CentOS・AlmaLinux・Rocky Linuxなど、現代のほぼすべての主要Linuxディストリビューションに標準搭載されている「systemd」の標準機能です。追加のインストールは一切不要です。
| 比較項目 | 従来の cron | モダンな systemd timer |
|---|---|---|
| 設定の記述方法 | crontab -e の1行に凝縮(暗号的) |
INI形式の分かりやすいユニットファイル(2ファイル構成) |
| ログの記録・管理 | リダイレクト必須、放置すると消滅 | journalctl で標準出力・エラー・終了ステータスが自動保存 |
| 環境変数の制御 | 極小のデフォルト環境で事故多発 | Environment= や実行ユーザー・作業ディレクトリを明示指定可能 |
| 二重起動防止 | flock などの追加ツールが必要 |
標準で前回の完了を待機(自動で多重起動を防止) |
| 単体テスト実行 | 時間まで待つか、コマンドを手動コピペ | systemctl start hoge.service で即座に単体テスト可能 |
| 停止中の取りこぼし | サーバー停止中に予定時刻を過ぎたら永久に不発 | Persistent=true で起動直後にキャッチアップ実行! |
| 実行状態・次回確認 | 不可(ログファイルを自力で漁る) | systemctl list-timers で次回・前回の時刻と経過が一目瞭然 |
2. systemd timerの基本仕組み:2つのファイルで1つのセット
systemd timerを導入するとき、最初に誰もが「えっ、ファイルが2つも必要なの?cronなら1行で済むのに……」と戸惑います。
しかし、この「実行内容」と「スケジュール」を綺麗に分離する設計こそが、運用の柔軟性とテストのしやすさを支える最大の強みなのです。
xxx.service(サービス定義):「何を、誰の権限で、どのディレクトリで実行するか」を定義する。これ単体でいつでも手動実行できる。xxx.timer(タイマー定義):「いつ、どの間隔で、どの.serviceを呼び出すか」を定義する。同名の.serviceが自動的に紐づく。
「スケジュールをまだ決めかねているけれど、バッチ処理本体の動作確認をしたい」というとき、systemctl start xxx.service を叩くだけで、本番とまったく同じ実行ユーザー・環境変数・作業ディレクトリの条件下で即時テストができます。この快適さを一度味わうと、もうcronには戻れません。
3. 実践!自作バックアップタスクを作ってみよう(ステップバイステップ)
それでは具体例として、「毎日深夜3:00に、データベースと指定ディレクトリのバックアップを取るタスク」をsystemd timerで作ってみましょう!
下準備:実行したいスクリプトの作成
まずは、定期実行したいシェルスクリプトを用意します。ここでは /opt/akanekoza/scripts/backup.sh に配置したと仮定します。
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/var/backups/akanekoza"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "${BACKUP_DIR}"
echo "[$(date)] バックアップ処理を開始します..."
# 例: データベースのダンプや重要ファイルのアーカイブ処理
tar -czf "${BACKUP_DIR}/backup_${DATE}.tar.gz" -C /var/www akanekoza.com
echo "[$(date)] バックアップが正常に完了しました: backup_${DATE}.tar.gz"
スクリプトに実行権限を付与しておきます。
$ sudo chmod +x /opt/akanekoza/scripts/backup.sh
ステップ①:サービスファイル(backup.service)を作成する
システム全体の定期タスク用のユニットファイルは、/etc/systemd/system/ ディレクトリに配置します。
sudo nano /etc/systemd/system/backup.service で以下のように記述します。
[Unit]
Description=AKANEKOZA Daily Backup Service
After=network.target
[Service]
Type=oneshot
User=root
WorkingDirectory=/opt/akanekoza
ExecStart=/opt/akanekoza/scripts/backup.sh
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
ここがポイント!
Type=oneshot:Webサーバーのように常駐し続けるプロセスではなく、「1回実行して終了するバッチ処理」であることをsystemdに伝えます。User=root:実行したいユーザーを指定します。rootで動かす必要がない処理なら、一般ユーザー名(例:www-dataやubuntu)を指定して最小権限に絞れます。StandardOutput=journal:標準出力とエラー出力を自動的にsystemdのシステムジャーナル(journald)に流します。ログファイルの管理やローテーションを自前でする必要がありません!
ステップ②:タイマーファイル(backup.timer)を作成する
次に、スケジュールを管理するタイマーファイルを作成します。ファイル名はサービスと同じ名前にして末尾を .timer にします。
sudo nano /etc/systemd/system/backup.timer を作成します。
[Unit]
Description=Run AKANEKOZA Daily Backup every day at 3:00 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=10m
[Install]
WantedBy=timers.target
ここがポイント!
OnCalendar=*-*-* 03:00:00:カレンダー形式で「毎日午前3時」を指定しています。Persistent=true:超重要!深夜3時にサーバーがメンテや再起動で落ちていた場合、サーバーが次に立ち上がった瞬間に「あ、昨夜の3時の分が未実行だ!」と検知して即座に実行してくれます。cronでは絶対にできない芸当です。RandomizedDelaySec=10m:指定時刻から最大10分の間でランダムに実行時刻を揺らします(ジッター)。複数台のサーバーが一斉に外部APIを叩いたりバックアップ先に負荷を集中させるのを防ぎます。
ステップ③:ユニットを反映してタイマーを有効化する
ファイルを作成したら、systemdに設定ファイルの再読み込み(須藤さんことsudoの出番です!)を命じ、タイマーを起動・常駐化します。
# 1. systemd に新しいファイルを認識させる
$ sudo systemctl daemon-reload
# 2. まずはサービス単体を手動実行してテストしてみる(超便利!)
$ sudo systemctl start backup.service
# 3. 実行ログを確認して、ちゃんと動いたかチェック
$ sudo journalctl -u backup.service -n 20 --no-pager
# 4. 問題なければ、タイマーを有効化&今すぐスタート!
$ sudo systemctl enable --now backup.timer
enable --now を使うと、「OS起動時の自動起動(enable)」と「今すぐタイマーの監視開始(start)」を1コマンドで同時に行えます。
4. 現場で使える!systemd timerの神機能たち
基本の作り方が分かったところで、日々の運用で「本当に助かった……!」と実感する便利技をご紹介します。
神技①:一覧確認コマンドで次回と前回が一目瞭然
登録されているタイマーが今どういう状況なのかは、systemctl list-timers コマンドを叩くだけで完璧に可視化されます。
$ systemctl list-timers --all
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2026-10-01 03:04:12 JST 5h 42min left Tue 2026-09-30 03:01:22 JST 18h ago backup.timer backup.service
Wed 2026-10-01 06:25:00 JST 9h left Tue 2026-09-30 06:25:00 JST 15h ago apt-daily.timer apt-daily.service
「次回は何時何分に走るのか(NEXT)」「あと何時間後か(LEFT)」「最後に走ったのはいつか(LAST)」。知りたい情報がすべて綺麗に整列して表示されます。この視認性の高さは感動ものです。
神技②:ログの調査が超簡単(journalctl)
「昨日の夜のバックアップ、失敗してないかな?」と思ったら、journalctl を呼ぶだけです。
# 直近のバックアップログをリアルタイムで追尾
$ sudo journalctl -u backup.service -f
# 今日のログだけを抽出
$ sudo journalctl -u backup.service --since today
# 直近のエラーログだけを赤文字で確認
$ sudo journalctl -u backup.service -p err..alert
cronのように「/var/log/myjob.log がどこにあるか探して……パーミッションが……」と悩む必要は一切ありません。OSの標準ジャーナルにすべての標準出力・標準エラーがタイムスタンプ付きで綺麗に収まっています。
神技③:相対時間指定(起動後〇分、前回完了後〇時間)が簡単
「毎日〇時」という絶対時刻だけでなく、「システム起動から5分後に1回だけ実行したい」「前回の処理が終わってから30分おきに回したい」という指定も直感的に書けます。
[Timer]
# サーバー起動後、15分経過したら実行
OnBootSec=15min
# 前回のバックアップ完了から、4時間ごとに繰り返し実行
OnUnitInactiveSec=4h
これを使えば、「前回の処理に1時間かかったから、次回までの間隔が詰まってサーバーが過負荷になる」といった事態を綺麗に防止できます。
5. ハマりやすい落とし穴と回避テクニック
systemd timerにも、慣れるまでに初心者がつまずきやすい「落とし穴」がいくつか存在します。
罠①:設定ファイルを書き換えたのに反映されない!
cronはファイルを編集して保存した瞬間に即座に反映されますが、systemdは「設定ファイルをメモリにキャッシュしている」ため、ファイルを書き換えただけでは古い設定のまま動き続けます。
.service や .timer を編集したら、必ず sudo systemctl daemon-reload を叩く!これを忘れて「あれ?変更した時間が効いてない…?」と悩むのは、全Linuxユーザーが通る通過儀礼です。
罠②:OnCalendarの書き方が分からない!構文チェッカーを使おう
OnCalendar の日時フォーマット(DayOfWeek Year-Month-Day Hour:Minute:Second)は、慣れないと「これ合ってるのかな?」と不安になります。
そんな時は、systemdに付属している最強の構文検証ツール systemd-analyze calendar を使いましょう!
$ systemd-analyze calendar "*-*-* 03:00:00"
Normalized form: *-*-* 03:00:00
Next elapse: Thu 2026-10-01 03:00:00 JST
(in UTC): Wed 2026-09-30 18:00:00 UTC
From now: 5h 32min left
# 「毎週月曜の深夜2時」を試してみる
$ systemd-analyze calendar "Mon *-*-* 02:00:00"
Normalized form: Mon *-*-* 02:00:00
Next elapse: Mon 2026-10-05 02:00:00 JST
From now: 4 days left
入力した文字列がどう解釈され、次回いつ発火するのかを秒単位で教えてくれます。間違った文字列を入れるとどこがエラーなのか親切に指摘してくれるので、タイマーを書くときはこのコマンドを叩きながら確認するのが一番の近道です。
罠③:root権限が不要なら「ユーザータイマー」を使おう
自分のホームディレクトリ内のファイル整理や個人スクリプトを動かすのに、いちいち sudo で /etc/systemd/system/ を触るのはセキュリティ的にも推奨されません。
一般ユーザー権限で動かしたい場合は、~/.config/systemd/user/ 配下にファイルを置き、以下のように --user フラグを付けて管理できます。
# ユーザー権限でタイマーを管理(sudo不要!)
$ systemctl --user daemon-reload
$ systemctl --user enable --now mytask.timer
$ systemctl --user list-timers
(※ログインしていない間も一般ユーザーのタイマーを常駐させたい場合は、sudo loginctl enable-linger $USER を実行してリンガーモードを有効にしておきます)
結び:一度慣れたら、もうcrontabには戻れない
「crontabの1行で済む手軽さ」に比べると、たしかにファイルが2つ必要で、daemon-reload を叩く儀式があるsystemd timerは、最初のハードルが少し高く感じるかもしれません。
しかし、
- 「なぜか動かない」ときに
journalctl一発で原因がわかる安心感 - サーバー再起動があっても
Persistent=trueで確実に仕事をしてくれる信頼感 systemctl startでいつでも安全にテスト実行できる快適さ
これらが生み出してくれる精神的平穏は、設定ファイルを書くわずか数分の手間を何十倍も上回る価値があります。
サーバーやPCの中で、人知れず黙々と定期タスクをこなしてくれる裏方の仕組み。赤猫座の道具店でも、バックアップやログ監視、データクリーンアップなどの地味で大切な仕事は、すべてこのsystemd timerが頼もしく支えてくれています。
「最近cronのトラブルで消耗したな……」という方は、ぜひ次の定期タスクから systemd timer を試してみてください。きっと、その堅牢さと手触りの良さに驚くはずです!🐱
