2026年5月28日
2026年5月28日
tarコマンドで定期バックアップするシェルスクリプト:失敗を見逃さない書き方(終了コード・排他制御・検証・通知)
tar で定期バックアップを取るスクリプトは数行でも動きますが、そのままだと「途中で失敗していたのに誰も気づかない」「壊れたアーカイブが残っている」「前回の処理と重なってディスクがあふれる」といった事故が起きがちです。この記事では、tar の終了コードの扱い、二重起動の防止、一時ファイルを使った書き込み、アーカイブの検証、失敗時の通知までを組み込んだスクリプトを、1行ずつ理由を説明しながら組み立てます。cron への登録と、復元テストのやり方も扱います。
tar のオプションの基本や増分バックアップは別記事で扱っているため、ここでは「無人で毎日動かしても安心できる作り」に絞ります。
最初に決めておくこと
スクリプトを書く前に、次の3点を紙に書き出しておくと、後で迷いません。
- 対象:たとえば WordPress のディレクトリ一式(
/var/www/example.com)。キャッシュなど再生成できるものは除外する - 保存先:同じサーバー内の別ディレクトリに作り、その後に別サーバーやクラウドへ転送する。同じディスクだけに置いたものは、ディスク障害やサーバー乗っ取りに耐えられません
- 保持期間:何日分残すか。容量と「いつまで遡りたいか」で決める
データベースは tar でファイルとしてコピーしても整合性のある状態になりません。WordPress なら mysqldump や WP-CLI の wp db export で先にダンプを作り、そのファイルを tar の対象に含めてください。
スクリプト全体
まず全体を示し、その後で部分ごとに説明します。パスや保持日数は自分の環境に合わせて書き換えてください。
#!/usr/bin/env bash
# /usr/local/bin/site-backup.sh
set -euo pipefail
umask 077
SRC_PARENT="/var/www"
SRC_NAME="example.com"
DEST_DIR="/var/backups/site"
KEEP_DAYS=14
LOCK_FILE="/run/lock/site-backup.lock"
LOG_TAG="site-backup"
WEBHOOK_URL="${WEBHOOK_URL:-}" # 通知先(未設定なら通知しない)
TMP=""
notify() {
logger -t "$LOG_TAG" "$1"
if [ -n "$WEBHOOK_URL" ]; then
curl -fsS -m 10 -H 'Content-Type: application/json' \
-d "{\"text\":\"$1\"}" "$WEBHOOK_URL" >/dev/null || true
fi
}
on_exit() {
local rc=$?
[ -n "$TMP" ] && rm -f "$TMP"
if [ "$rc" -ne 0 ]; then
notify "バックアップ失敗: $(hostname) 終了コード $rc"
fi
}
trap on_exit EXIT
# 二重起動の防止
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
logger -t "$LOG_TAG" "前回の処理が実行中のためスキップ"
exit 0
fi
mkdir -p "$DEST_DIR"
STAMP="$(date +%Y%m%d-%H%M%S)"
OUT="$DEST_DIR/site-$STAMP.tar.gz"
TMP="$OUT.part"
# アーカイブ作成(終了コード1は警告として扱う)
rc=0
tar --create --gzip --file="$TMP" \
--exclude="$SRC_NAME/wp-content/cache" \
-C "$SRC_PARENT" "$SRC_NAME" || rc=$?
if [ "$rc" -eq 1 ]; then
logger -t "$LOG_TAG" "警告: 作成中に変更されたファイルがありました"
elif [ "$rc" -ne 0 ]; then
exit "$rc"
fi
# 検証
gzip -t "$TMP"
tar --list --gzip --file="$TMP" >/dev/null
# 完成したら名前を変える
mv "$TMP" "$OUT"
TMP=""
sha256sum "$OUT" > "$OUT.sha256"
# 古い世代の削除(成功したときだけ実行される)
find "$DEST_DIR" -maxdepth 1 -name 'site-*.tar.gz*' -mtime +"$KEEP_DAYS" -delete
logger -t "$LOG_TAG" "完了: $OUT"
set -euo pipefail と umask
set -e はコマンドが失敗した時点でスクリプトを止め、set -u は未定義の変数を使ったときにエラーにします。-u があると、変数名の打ち間違いで rm の対象が空になる、といった事故を防げます。pipefail は、パイプの途中のコマンドが失敗したときにパイプ全体を失敗扱いにする設定です。tar ... | gzip のように書いた場合、これがないと tar の失敗が見えなくなります。
ただし set -e には「if の条件や || の左側では止まらない」などの例外があり、万能ではありません。重要な箇所は、この記事のように終了コードを明示的に確認するほうが読みやすく、誤解も少なくなります。
umask 077 は、作るファイルを所有者だけが読めるようにする設定です。バックアップには wp-config.php のデータベースのパスワードなどが含まれるため、ほかのユーザーから読めない状態で作ります。
tar の終了コードをどう扱うか
GNU tar の終了コードは、大きく次の3つに分かれます。
0:正常終了1:一部のファイルが異なる。作成時は「読み込み中にファイルが変更された」場合が代表例2:致命的なエラー(読めないファイルがある、書き込めない、など)
稼働中のサイトでは、ログやキャッシュがバックアップ中に書き換わることがよくあり、そのたびに終了コード1が返ります。これを set -e のまま失敗扱いにすると、毎晩のように失敗通知が届き、やがて誰も通知を見なくなります。そこでスクリプトでは || rc=$? で終了コードを受け取り、1は警告としてログに残し、2以上だけを失敗として止めています。
警告が毎回出るファイルが分かったら、それが本当に必要なものかを考えます。キャッシュや一時ファイルなら --exclude で外すと、ログが静かになり、本当の異常に気づきやすくなります。
flock で二重起動を防ぐ
サイトが大きくなると、バックアップが予定より長くかかり、次の実行時刻になっても前の処理が終わっていないことがあります。そのまま2本目が走ると、ディスクの読み書きが重なってさらに遅くなり、容量も圧迫します。
flock -n は、ロックが取れなければすぐに諦めるオプションです。ファイル記述子9でロックファイルを開いておけば、スクリプトが終了したとき(異常終了を含む)にロックは自動で解放されるため、古いロックファイルが残って以後ずっと動かなくなる、という心配がありません。/run/lock はディストリビューションによっては存在しないので、その場合は /var/lock など書き込めるディレクトリに変えてください。
一時ファイルに書いてから名前を変える
アーカイブは .part という名前で作り、検証が終わってから本来の名前に変えています。途中でディスクがいっぱいになったりプロセスが止められたりしても、「完成していないファイル」が正規のバックアップに見えることがありません。mv は同じファイルシステム内なら一瞬で終わるため、転送スクリプトなどがファイルを拾うときにも中途半端な状態を見ずに済みます。
trap on_exit EXIT により、どこで失敗しても .part は削除され、失敗の通知が送られます。
アーカイブを検証する
gzip -t は圧縮データが壊れていないかを、tar --list はアーカイブとして最後まで読めるかを確かめます。どちらかが失敗すれば set -e によりスクリプトが止まり、失敗通知が送られます。
検証後に作る .sha256 は、別サーバーへ転送した後に sha256sum -c で照合するためのものです。転送の途中で壊れていないかを確かめられます。
世代の削除は最後に
古いバックアップの削除は、新しいバックアップが検証を通った後にだけ実行されます。順番を逆にすると、作成に失敗した日に古いものだけが消え、手元に何も残らない状態になりかねません。find -delete は取り消せないので、最初は -delete を外して -print にし、消える対象を確認してから切り替えると安全です。
cron に登録する
root の crontab(sudo crontab -e)に、毎日3時に実行する例を書きます。
WEBHOOK_URL=https://example.com/your-webhook-url
0 3 * * * /usr/local/bin/site-backup.sh >> /var/log/site-backup.log 2>&1
cron でつまずきやすい点は次のとおりです。
- cron の環境は
PATHが最小限です。コマンドが見つからない場合はスクリプト内でPATHを設定します - crontab の行に直接
date +%Fのように書くと、%が改行として扱われます。日付の処理はスクリプト内に書くのが無難です - 登録前に、同じユーザーで手動実行して動くことを確かめます
通知の仕組みは「失敗したら知らせる」だけなので、cron 自体が止まっている場合には何も届きません。成功時に外部の監視サービスへ定期的に信号を送り、途絶えたら知らせる仕組み(いわゆるハートビート監視)を併用すると、この穴を埋められます。
復元できることを確かめる
バックアップは戻せて初めて意味があります。月に1回など頻度を決めて、別のディレクトリに展開してみてください。
mkdir -p /tmp/restore-test
tar --extract --gzip --file=/var/backups/site/site-20261018-030000.tar.gz -C /tmp/restore-test
ls -la /tmp/restore-test/example.com
本番のディレクトリに直接展開すると、現在のファイルを上書きしてしまいます。復元の練習はステージング環境や一時ディレクトリで行い、本番に戻すときは、その時点の状態を別に退避してから作業してください。
まとめ
set -euo pipefailとumask 077で、失敗を見逃さず、他人に読まれない状態で作る- tar の終了コード1(作成中のファイル変更)は警告、2以上は失敗として扱い分ける
flock -nで二重起動を防ぎ、.partに書いてからmvで完成させるgzip -tとtar --listで検証し、.sha256で転送後の破損も確かめられるようにする- 古い世代の削除は成功後だけ。失敗通知に加えて、実行が途絶えたことを知る仕組みも用意する
- GNU tarのバージョンによる違い(
--zstdの対応など)は環境ごとに差があるため(2026年10月現在)、tar --versionで確認してから使う