2026年8月10日

2026年8月10日

サーバーのバックアップを世代管理する方法(tar・rsyncスナップショット・cron)

バックアップを1つだけ上書きしていると、壊れたファイルやマルウェアに感染した状態をそのまま上書きしてしまい、正常な時点に戻せなくなります。そこで、日付ごとに複数の世代を残し、決めた数や期間を過ぎたものだけを自動で削除する「世代管理」を行います。

この記事では、Linux サーバー上のウェブサイト(WordPress のファイルとデータベース)を例に、シェルスクリプトと cron で世代管理する手順を説明します。コマンドは 2026年10月時点の Ubuntu / Debian / RHEL 系で標準的に使える GNU tar・rsync・findutils を前提にしています。

先に決めること

項目 例 考え方
取得の間隔 毎日 2:00 更新頻度と、失ってよいデータの量で決める
残す世代 日次7・週次4・月次6 「何日前まで戻れればよいか」から逆算する
保存先 別ディスク・別サーバー 同じディスクだけでは、ディスク故障やサーバー侵害で全世代を失う
確認方法 週1回の展開テスト 取れているだけでなく、戻せることを確かめる

世代数を増やすほどディスクを使います。1回分のサイズ × 世代数が保存先に収まるかを先に確認してください(差分スナップショットなら、変更のあった分だけ増えます)。

方法1: tarの日付つきアーカイブを世代管理する

バックアップスクリプト

ファイルを tar で固め、データベースを mysqldump で書き出し、作成と検査が成功したあとで古い世代を削除します。先に削除してからバックアップを作る順番にすると、バックアップが失敗し続けたときに正常な世代まで消えていくので避けてください。

#!/bin/bash
# /usr/local/bin/backup-www.sh
set -euo pipefail

SRC_DIR="/var/www/html"        # バックアップするディレクトリ
DB_NAME="wordpress"            # データベース名
BACKUP_DIR="/backup/www"       # 保存先
KEEP=7                         # 残す世代数

STAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"

# 1. ファイル(一時名で作り、検査してから正式名にする)
tar -czf "$BACKUP_DIR/files-$STAMP.tar.gz.part" \
  -C "$(dirname "$SRC_DIR")" "$(basename "$SRC_DIR")"
tar -tzf "$BACKUP_DIR/files-$STAMP.tar.gz.part" > /dev/null
mv "$BACKUP_DIR/files-$STAMP.tar.gz.part" "$BACKUP_DIR/files-$STAMP.tar.gz"

# 2. データベース(接続情報は実行ユーザーの ~/.my.cnf に書く)
mysqldump --single-transaction --default-character-set=utf8mb4 "$DB_NAME" \
  | gzip > "$BACKUP_DIR/db-$STAMP.sql.gz.part"
gzip -t "$BACKUP_DIR/db-$STAMP.sql.gz.part"
mv "$BACKUP_DIR/db-$STAMP.sql.gz.part" "$BACKUP_DIR/db-$STAMP.sql.gz"

# 3. 新しい順に並べ、KEEP 個より古いものを削除
prune() {
  find "$BACKUP_DIR" -maxdepth 1 -type f -name "$1" \
    | sort -r | tail -n +$((KEEP + 1)) | xargs -r rm -f --
}
prune 'files-*.tar.gz'
prune 'db-*.sql.gz'

ポイント:

  • set -euo pipefail により、途中のコマンドが失敗した時点でスクリプトが止まり、削除の手順まで進みません。pipefail は mysqldump | gzip のように、パイプの前段(mysqldump)が失敗したことも検出するために必要です。
  • ファイル名を files-年月日-時分秒 にしているので、名前の並び順がそのまま日時順になります。sort -r で新しい順に並べ、tail -n +8(KEEP=7 のとき)で8番目以降を削除対象にしています。
  • 期間で消したい場合は、最後の削除を find "$BACKUP_DIR" -maxdepth 1 -name 'files-*.tar.gz' -mtime +7 -delete のようにします。ただし期間指定は、バックアップが止まっていても古いものを消し続けます。世代数で数える方が、失敗が続いたときに残る世代が多くなります。
  • GNU tar は、固めている最中にファイルが変わると警告を出して終了コード 1 を返すことがあります。この場合もスクリプトは止まり、古い世代は残ります。更新の多いディレクトリ(キャッシュなど)は --exclude で除外してください。

データベースの接続情報は、スクリプトにパスワードを書かず、実行ユーザーのホームディレクトリの ~/.my.cnf に書いて権限を絞ります。

[client]
user=backup_user
password=ここにパスワード
chmod 600 ~/.my.cnf

日次・週次・月次で残す

「直近1週間は毎日、それ以前は週1つ、さらに前は月1つ」のように残すと、少ない容量で長い期間をさかのぼれます。日次のアーカイブを週次・月次のディレクトリにハードリンクし、それぞれ別の世代数で削除する例です(ハードリンクは同じファイルシステム上でのみ作れます。容量はリンクの数だけ増えることはありません)。

# backup-www.sh の末尾に追加する例
WEEKLY_DIR="$BACKUP_DIR/weekly"
MONTHLY_DIR="$BACKUP_DIR/monthly"
mkdir -p "$WEEKLY_DIR" "$MONTHLY_DIR"

# 日曜日(%u が 7)は週次に、1日は月次にも残す
if [ "$(date +%u)" = "7" ]; then
  ln "$BACKUP_DIR/files-$STAMP.tar.gz" "$BACKUP_DIR/db-$STAMP.sql.gz" "$WEEKLY_DIR/"
fi
if [ "$(date +%d)" = "01" ]; then
  ln "$BACKUP_DIR/files-$STAMP.tar.gz" "$BACKUP_DIR/db-$STAMP.sql.gz" "$MONTHLY_DIR/"
fi

prune_dir() {   # prune_dir ディレクトリ 世代数 パターン
  find "$1" -maxdepth 1 -type f -name "$3" \
    | sort -r | tail -n +$(($2 + 1)) | xargs -r rm -f --
}
prune_dir "$WEEKLY_DIR" 4 'files-*.tar.gz'
prune_dir "$WEEKLY_DIR" 4 'db-*.sql.gz'
prune_dir "$MONTHLY_DIR" 6 'files-*.tar.gz'
prune_dir "$MONTHLY_DIR" 6 'db-*.sql.gz'

日次のディレクトリで古いファイルを削除しても、週次・月次にハードリンクしたものは残ります。

方法2: rsyncの差分スナップショットで世代管理する

ファイル数が多いサイトでは、毎回すべてを tar で固めると時間も容量もかかります。rsync の --link-dest を使うと、前回から変わっていないファイルは前回のスナップショットへのハードリンクにし、変わったファイルだけをコピーできます。各世代はすべてのファイルがそろったディレクトリに見えるので、復元は必要な世代からコピーするだけです。

#!/bin/bash
# /usr/local/bin/snapshot-www.sh
set -euo pipefail

SRC="/var/www/html/"
SNAP_DIR="/backup/snapshots"
KEEP=14

NEW="$SNAP_DIR/$(date +%Y%m%d-%H%M%S)"
LATEST="$SNAP_DIR/latest"
mkdir -p "$SNAP_DIR"
rm -rf "$SNAP_DIR"/*.part      # 前回失敗した作りかけを片付ける

if [ -d "$LATEST" ]; then
  rsync -a --delete --link-dest="$LATEST" "$SRC" "$NEW.part/"
else
  rsync -a --delete "$SRC" "$NEW.part/"
fi
mv "$NEW.part" "$NEW"
ln -sfn "$NEW" "$LATEST"

# 新しい順に並べ、KEEP 個より古いスナップショットを削除
find "$SNAP_DIR" -mindepth 1 -maxdepth 1 -type d -name '20??????-??????' \
  | sort -r | tail -n +$((KEEP + 1)) | xargs -r rm -rf --
  • latest は最新のスナップショットを指すシンボリックリンクです。次回の --link-dest の比較対象になります。
  • 古いスナップショットを削除しても、ほかの世代から参照されているファイルの実体は消えません(ハードリンクの数が0になったときに消えます)。
  • 使用量は du -sh /backup/snapshots/* で確認できます。同じ du の中ではハードリンクは1回しか数えないため、最初の世代が全体のサイズ、以降の世代が変更分のサイズとして表示されます。
  • rsync は転送中に送り元のファイルが消えると終了コード 24 を返し、この例ではスクリプトが止まります(作りかけは次回に片付けます)。

データベースは方法1と同じく mysqldump で書き出し、スナップショットの対象ディレクトリに含めるか、別に世代管理してください。

cronで毎日実行する

手動で一度実行して、ファイルができることと削除が正しく動くことを確かめてから登録します。/etc/cron.d/ に置くファイルは実行ユーザーの欄が必要です(/etc/crontab の末尾に echo で追記するより、専用のファイルにする方が管理しやすくなります)。

sudo install -m 750 backup-www.sh /usr/local/bin/backup-www.sh
sudo /usr/local/bin/backup-www.sh; echo "exit=$?"
ls -l /backup/www/
# /etc/cron.d/backup-www (毎日 2:00 に root で実行し、出力をログに残す)
0 2 * * * root /usr/local/bin/backup-www.sh >> /var/log/backup-www.log 2>&1

世代がそろっているか・戻せるかを確認する

# 世代の一覧(新しい順)
ls -lt /backup/www/

# アーカイブが壊れていないか
tar -tzf /backup/www/files-20261015-020001.tar.gz > /dev/null && echo OK
gzip -t /backup/www/db-20261015-020001.sql.gz && echo OK

# 一時ディレクトリに展開して中身を確かめる
mkdir -p /tmp/restore-test
tar -xzf /backup/www/files-20261015-020001.tar.gz -C /tmp/restore-test
ls /tmp/restore-test/html

ファイル名の日時は、実際にできたファイルに合わせて読み替えてください。データベースは、本番とは別のテスト用データベースに読み込んで確認します。定期的に展開テストをしておくと、いざというときの手順の確認にもなります。

専用ツールを使う場合

自作スクリプトの代わりに、重複排除と世代管理の機能を持つバックアップツールを使う方法もあります。たとえば restic は restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune、BorgBackup は borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=6 のように、残す世代を指定して古いものを整理できます。導入手順は各ツールの公式ドキュメントを参照してください。

注意点

  • 世代管理は「過去の状態に戻せる」ための仕組みで、保存先そのものが壊れたときには役に立ちません。少なくとも1つは別のサーバーやストレージにコピーしてください。
  • バックアップにはデータベースのパスワードを含む wp-config.php も入ります。保存先のディレクトリは chmod 700 などで root 以外が読めないようにします。
  • ディスクの空きが足りなくなると、tar や rsync が途中で失敗します。df -h で保存先の空きを定期的に確認してください。

関連記事:

まとめ

  • 残す世代数・保存先・確認方法を先に決め、バックアップの作成と検査が成功してから古い世代を削除します。
  • tar のアーカイブは日付つきの名前にして世代数で削除し、週次・月次はハードリンクで残すと容量を抑えられます。
  • ファイル数が多いなら rsync の --link-dest で差分スナップショットにし、定期的に展開テストをして戻せることを確かめます。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲