cronとsystemd timerで定期実行 — バックアップ自動化の基本
本記事はアフィリエイト広告(A8.net)を含みます。価格・仕様は2026年6月時点の目安です。最新は各公式サイトでご確認ください。
TL;DR
サーバーで「毎日決まった時刻にバックアップを取る」「ログを定期的に整理する」といった処理は、人が手で叩く前提にすると必ず抜けます。Linuxには定期実行の仕組みが2つあります。古くから使われる cron と、systemd標準の systemd timer です。
使い分けの目安はシンプルです。ワンライナーの軽い処理ならcronで十分、ログ・依存関係・「サーバー停止中に逃した実行の追いかけ」が欲しいならsystemd timer を選びます。timerは .service と .timer の2ファイルが必要で書く量は増えますが、journalctl で実行ログが残り、Persistent=true で電源を入れ直したときに逃したジョブを実行できる強みがあります。
本記事はUbuntu/Debian系を前提に、cronの基本と落とし穴、systemd timerの構成、両者の比較、そして「毎日バックアップを取って失敗時に気づく」までの実例を、コマンドと設定ファイル付きで示します。
本記事は自分が契約・管理するサーバー上での定期処理の解説です。バックアップ先や保存期間は扱うデータの性質に合わせて設計してください。バックアップ全体の考え方は中小企業のための3-2-1バックアップ戦略を、コンテナを定期処理の対象にする場合はDocker Composeではじめるセルフホスティングを参照してください。
cronとsystemd timer、どちらを使うか
| 観点 | cron | systemd timer |
|---|---|---|
| 設定の手軽さ | 1行で書ける | .service + .timer の2ファイル |
| 実行ログ | 標準では残りにくい(出力先を自分で設計) | journalctl に自動で残る |
| 逃した実行の追いかけ | できない | Persistent=true で可能 |
| 依存関係の指定 | 不可 | After= 等で指定可 |
| 実行間隔の表現 | 分単位の5フィールド | OnCalendar で柔軟・OnBootSec 等も可 |
| ランダム遅延 | 不可(標準では) | RandomizedDelaySec で可 |
「とりあえず毎晩スクリプトを1本回す」だけならcronが最短です。サーバーが夜間に止まることがある、実行結果のログを確実に残したい、別ユニットの後に動かしたい といった要件が出てきたらsystemd timerに移行する、という順序が現実的です。
cronの基本
crontabの編集
ユーザーごとのcron設定は crontab -e で編集します。
crontab -e
初回はエディタの選択を聞かれることがあります。保存すると即座に有効になります。登録済みの内容は crontab -l で確認できます。
crontab -l
cron式(5フィールド)の読み方
cronの1行は、5つの時刻フィールドと実行コマンドで構成されます。
┌── 分 (0-59)
│ ┌── 時 (0-23)
│ │ ┌── 日 (1-31)
│ │ │ ┌── 月 (1-12)
│ │ │ │ ┌── 曜日 (0-7、0と7が日曜)
│ │ │ │ │
* * * * * 実行するコマンド
記号の意味は次の通りです。
| 記号 | 意味 | 例 |
|---|---|---|
* | すべての値 | 毎分・毎時など |
, | 値の列挙 | 0,30 = 0分と30分 |
- | 範囲 | 1-5 = 月〜金 |
/ | 間隔 | */15 = 15分ごと |
具体例を挙げます。
| cron式 | 実行タイミング |
|---|---|
0 3 * * * | 毎日午前3時0分 |
30 2 * * 1 | 毎週月曜の午前2時30分 |
*/15 * * * * | 15分ごと |
0 0 1 * * | 毎月1日の午前0時 |
0 9-18 * * 1-5 | 平日の9時〜18時に毎時0分 |
毎日午前3時にバックアップスクリプトを実行する例です。
0 3 * * * /usr/local/bin/backup.sh
特殊な記述として @daily、@hourly、@reboot といった省略形も使えます(@daily は 0 0 * * * 相当)。
cronの落とし穴
cronは手軽な反面、対話シェルとは実行環境が違うため、つまずきやすいポイントがいくつかあります。
環境変数とPATHが対話シェルと異なる
cronは最小限の環境で実行されます。PATH も短く、ログイン時に読まれる .bashrc や .profile は読まれません。手元で動くコマンドがcronだと「command not found」になるのは、ほぼこれが原因です。
対策は絶対パスで書くことです。mysqldump ではなく /usr/bin/mysqldump と書きます。場所は which mysqldump で確認できます。crontabの先頭で PATH を明示する方法もあります。
PATH=/usr/local/bin:/usr/bin:/bin
0 3 * * * /usr/local/bin/backup.sh
出力先を決めないとエラーに気づけない
cronはコマンドの標準出力・標準エラーをそのまま捨てます(ローカルメールが設定されていればroot宛に送られますが、多くのVPSでは届きません)。ログファイルへリダイレクトして、後から確認できるようにします。
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
2>&1 は標準エラーを標準出力と同じ先へまとめる指定です。エラーだけを取りこぼさないために忘れないでください。
% はそのまま使えない
cron式の中で % は改行扱いの特殊文字になります。date +%F のようにパーセントを含むコマンドを直接書くとそこで切れてしまいます。バックスラッシュでエスケープするか、スクリプトに切り出して呼ぶのが安全です。
0 3 * * * /usr/bin/mysqldump app > /backup/db_$(date +\%F).sql
多重起動を防ぐ
前回の実行が終わらないうちに次の起動時刻が来ると、処理が重なって走ります。flock でロックを取り、重複実行を防げます。
0 3 * * * /usr/bin/flock -n /tmp/backup.lock /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
-n はロックを取れなかったら即座に終了する指定です。前回が走っている間は今回の起動をスキップします。
systemd timerの基本
systemd timerは「何をするか」を .service ユニットに、「いつ動かすか」を .timer ユニットに分けて書きます。2ファイル必要ですが、ログと逃した実行の追いかけが標準で手に入ります。
.service ユニット
実行する処理を定義します。Type=oneshot は「起動して、終わったら終了する」一回限りの処理に使います。/etc/systemd/system/backup.service を作成します。
[Unit]
Description=Daily backup job
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=deploy
After= で別のターゲットやユニットの後に動かす、といった依存関係を指定できます。これはcronにはできない点です。
.timer ユニット
起動タイミングを定義します。同じ名前(拡張子だけ違う)にすると、timerは自動で同名のserviceを起動します。/etc/systemd/system/backup.timer を作成します。
[Unit]
Description=Run backup.service daily at 03:00
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
OnCalendar の書き方
OnCalendar は 曜日 年-月-日 時:分:秒 の形式で指定します。省略した部分はワイルドカード扱いになります。
| OnCalendar | 実行タイミング |
|---|---|
*-*-* 03:00:00 | 毎日午前3時 |
Mon *-*-* 02:30:00 | 毎週月曜の午前2時30分 |
*-*-01 00:00:00 | 毎月1日の午前0時 |
Mon..Fri 09:00:00 | 平日の午前9時 |
daily | 毎日午前0時(*-*-* 00:00:00 相当) |
hourly | 毎時0分 |
書いた式が意図通りか、systemd-analyze で次回の発火時刻を検算できます。これはtimer固有の便利な点です。
systemd-analyze calendar "Mon..Fri 09:00:00"
Persistent=true の意味
Persistent=true を付けると、サーバーが停止していて発火時刻を逃した場合、起動直後にその回を実行します。夜間に電源が落ちることがある環境や、常時稼働でないマシンで効きます。cronにはこの仕組みがありません(同じ用途には別途 anacron が必要です)。
有効化と確認
ユニットを書いたら、systemdに読み込ませて、timerを有効化・起動します。
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
登録されたtimerの一覧と次回発火時刻を確認します。
systemctl list-timers
手動でserviceだけを単発実行して、スクリプト自体の動作を試すこともできます。
sudo systemctl start backup.service
実行ログは journalctl で確認できます。これがcronとの大きな違いです。
journalctl -u backup.service --since today
実例:毎日バックアップを取って失敗に気づく
バックアップスクリプト本体の例です。set -euo pipefail で、途中のコマンドが失敗したらそこで止まり、ゼロ以外の終了コードを返すようにします。/usr/local/bin/backup.sh として保存し、実行権限を付けます。
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR=/backup
STAMP=$(date +%F_%H%M)
DEST="$BACKUP_DIR/db_${STAMP}.sql.gz"
mkdir -p "$BACKUP_DIR"
# DBダンプを圧縮して保存
/usr/bin/mysqldump --single-transaction app | gzip > "$DEST"
# 7日より古いバックアップを削除
find "$BACKUP_DIR" -name 'db_*.sql.gz' -mtime +7 -delete
echo "backup done: $DEST"
sudo chmod +x /usr/local/bin/backup.sh
cronで回す場合
ログへ追記し、flock で多重起動を防ぐ形にします。
0 3 * * * /usr/bin/flock -n /tmp/backup.lock /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
systemd timerで回す場合
前掲の backup.service と backup.timer をそのまま使えます。ログは journalctl -u backup.service で残ります。
失敗通知
定期処理は「動いていると思い込んでいたが、実は数週間前から失敗していた」が最悪のパターンです。失敗したときに気づける仕組みを足します。
終了コードで分岐する(cron向け)
スクリプトの終了コードを見て、失敗時だけメールを送る例です(ローカルにメール送信手段がある前提)。
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1 || echo "backup FAILED on $(hostname)" | mail -s "backup failed" [email protected]
|| は直前のコマンドが失敗(ゼロ以外で終了)したときだけ右側を実行する指定です。スクリプト側で set -e を入れておくと、途中で失敗したときに確実にゼロ以外で抜けます。
OnFailure で通知する(systemd向け)
systemdなら、serviceが失敗したときに別のユニットを起動する OnFailure= を使えます。backup.service の [Unit] セクションに追記します。
[Unit]
Description=Daily backup job
OnFailure=notify-failure@%n.service
[email protected] をテンプレートユニットとして用意し、通知コマンド(メール送信やWebhook呼び出し)を ExecStart に書きます。%n には失敗したユニット名が渡るため、複数ジョブで通知ユニットを共有できます。
死活監視サービスを併用する
「実行されなかった(cronごと止まっていた)」ケースは、サーバー内部のログだけでは気づけません。外部の死活監視(dead man’s switch) を併用すると、スクリプト末尾でping用URLを叩き、一定時間 pingが来なければ外部から通知が飛ぶ、という形で「実行されなかったこと」を検知できます。スクリプトの最後に1行足すだけで導入できます。
タイムゾーンの注意
定期実行の時刻は、サーバーのタイムゾーン設定で解釈されます。海外データセンターのVPSはデフォルトがUTCのことがあり、「午前3時」と書いたつもりが日本時間では昼に走る、というずれが起きます。
まず現在のタイムゾーンを確認します。
timedatectl
日本時間に揃えたい場合は次のように設定します。
sudo timedatectl set-timezone Asia/Tokyo
systemd timerでは、ユニット単位でタイムゾーンを固定することもできます。OnCalendar の前に指定します。
[Timer]
OnCalendar=Asia/Tokyo *-*-* 03:00:00
Persistent=true
サマータイムのある地域では、切り替え時刻に重なるジョブが「実行されない」「2回実行される」といった挙動になり得ます。重要なジョブは切り替え時刻を避けて設定すると安全です。
まとめ
| 項目 | cron | systemd timer |
|---|---|---|
| 軽いワンライナー | ○ | △(やや過剰) |
| 実行ログを残す | △(自分で設計) | ○(journalctl) |
| 逃した実行の追いかけ | ×(要anacron) | ○(Persistent) |
| 依存関係・順序指定 | × | ○(After=) |
| 失敗通知 | ○(` | |
| タイムゾーン固定 | サーバー全体 | ユニット単位も可 |
最初の一歩は cronで 0 3 * * * の1行から で十分です。慣れてきて、実行ログを確実に残したい・夜間に止まる環境で逃した回を拾いたい・別ユニットの後に動かしたい、といった要件が出てきたらsystemd timerへ移します。どちらを使うにせよ、絶対パスで書く・出力をログへ残す・失敗に気づける通知を足す の3点を外さなければ、定期処理は「気づいたら止まっていた」を避けられます。
各サービス公式
ここで紹介したcron・systemd timerは、Ubuntu/Debianが動くVPSならそのまま使えます。定期バックアップの実行先としては、料金と国内サポートのバランスから次の2つが定番です。どちらもスナップショット機能を備え、バックアップの保存先として併用できます。
- ConoHa VPS: 時間課金とテンプレートが豊富で、検証用の使い捨てサーバーを立てやすい構成です。cronやtimerの動作確認に向きます。
- Xserver VPS: 高クロックなCPUとシンプルな料金体系。国内データセンターで安定して常時稼働させやすく、定期処理を回す常駐用途に向きます。
バックアップ全体の設計は中小企業のための3-2-1バックアップ戦略に、コンテナの運用はDocker Composeではじめるセルフホスティングにまとめています。