誰も経験したくない午前2時の緊急呼び出し
同僚が昨年、3週間分のデータベースエクスポートを失った。ハードウェア障害が原因ではなく、マイグレーションが失敗する前に手動バックアップスクリプトを実行し忘れたためだ。ファイルはソースサーバーに存在していたが、いつの間にかなくなっていた。自動バックアップはどこにもなかった。
心当たりはないだろうか?手動バックアップの運用は、実際の業務プレッシャーの下では崩壊する。ある金曜日をスキップし、また次の金曜日もスキップ、そして火曜日の朝に何かが壊れて、記憶を頼りにデータを復元する羽目になる。
解決策は自己管理ではなく、自動化だ。具体的には、rsyncとcronの組み合わせが、追加ソフトウェアやクラウドサブスクリプションなしにこの問題をすっきり解決してくれる。
手動バックアップが必ず失敗する理由
根本的な原因は怠惰ではない。手動バックアップが失敗するのは、構造的な理由がある:
- コンテキストスイッチ — デプロイに集中しているとき、バックアップのステップが頭から抜け落ちてしまう。
- 不規則なタイミング — 「今夜やろう」が「明日やろう」になる。
- ツールの不適切な選択 —
cp -rでのコピーは、シンボリックリンク、パーミッション、途中で中断した転送を正しく扱えない。 - 検証の欠如 — バックアップを実行したが、完了したのか?宛先に実際に書き込まれたのか?
バックアップが誰かの記憶頼みになった時点で、データ損失のリスクを受け入れたも同然だ。Cronが記憶の必要性をなくし、rsyncがツールの問題を解決する。
rsync vs. その他のバックアップ手法
Linuxバックアップの話をすると、よく出てくるツールが3つある。それぞれが実際に何をするのか、そしてどこで限界を迎えるのかを見ていこう。
オプション1: cp -r
シンプルでどこでも使えるが、毎回すべてをコピーする。大きなディレクトリでは、1つのファイルが変更されただけでも完全な再転送が必要になる。拡張属性の保持や、中断した転送の再開も適切に処理できない。
オプション2: tarアーカイブ
圧縮アーカイブを作成するため、長期保存やオフサイトへのバックアップ移動に便利だ。欠点は、1つのファイルを復元するにもアーカイブ全体を展開しなければならないこと。そして毎回フルコピーになる。
オプション3: rsync(差分同期)
rsyncはファイルサイズと更新タイムスタンプ(または指定した場合はチェックサム)を比較し、変更された部分のみ転送する。本番のUbuntu 22.04サーバーでは、tarを使った夜間バックアップに約12分かかっていたが、rsyncに切り替えると90秒以内に収まった。変更されたファイルだけが転送されるため、差分が非常に小さかったのだ。
rsyncは中断した転送を途中から再開する機能もあり、適切なフラグを使えばパーミッション、シンボリックリンク、タイムスタンプ、所有権もデフォルトで保持される。
ローカルでもリモートでも、サーバーバックアップにはrsyncが圧倒的に優れている。
自動バックアップのためのrsync設定
rsyncのインストール
ほとんどのLinuxディストリビューションにはrsyncがデフォルトで含まれている。入っていない場合:
# Debian/Ubuntu
sudo apt install rsync
# RHEL/AlmaLinux/Rocky
sudo dnf install rsync
rsyncコマンドの基本構造
rsync -avz --delete /source/directory/ /destination/directory/
フラグの説明:
-a(アーカイブモード)— パーミッション、タイムスタンプ、シンボリックリンク、所有者、グループを保持する-v(詳細出力)— 転送中のファイルを表示する(ログに役立つ)-z(圧縮)— 転送中にデータを圧縮する(主にリモート転送に有効。ローカルバックアップでは省略可)--delete— ソースに存在しなくなったファイルを宛先から削除する(同期状態を保つ)
ソースの末尾スラッシュに注意 — /source/directory/は「このディレクトリの中身を同期する」という意味だ。スラッシュがないと、rsyncは宛先の中にdirectoryというサブディレクトリを作成してしまう。これはよくある落とし穴だ。
ローカルバックアップの例
# /var/wwwを/mnt/backupにマウントされた外付けドライブにバックアップ
rsync -av --delete /var/www/ /mnt/backup/www/
SSHを使ったリモートバックアップ
# リモートサーバーへバックアップ
rsync -avz --delete -e ssh /var/www/ user@backup-server:/backups/www/
# 特定のSSHキーと非標準ポートを使用する場合
rsync -avz --delete -e "ssh -i /root/.ssh/backup_key -p 2222" \
/var/www/ user@backup-server:/backups/www/
自動(無人)リモートバックアップには、パスワードなしのSSH認証が必要だ。専用のキーペアを生成しよう:
# ソースサーバーでキーを生成
ssh-keygen -t ed25519 -f /root/.ssh/backup_key -N ""
# 公開鍵を宛先にコピー
ssh-copy-id -i /root/.ssh/backup_key.pub user@backup-server
ファイルとディレクトリの除外
# キャッシュ、一時ファイル、ログを除外
rsync -av --delete \
--exclude='cache/' \
--exclude='*.log' \
--exclude='tmp/' \
/var/www/ /mnt/backup/www/
除外項目が多い場合は、ファイルにまとめる:
# /etc/rsync-excludes.txt
cache/
*.log
tmp/
*.swp
.git/
rsync -av --delete --exclude-from=/etc/rsync-excludes.txt \
/var/www/ /mnt/backup/www/
本番環境対応バックアップスクリプトの作成
cronから直接rsyncを実行することもできるが、ラッパースクリプトを使うとログ記録、エラー処理、そして将来的なロジック追加をcrontabを変更せずに行える。
#!/bin/bash
# /usr/local/bin/backup-www.sh
SOURCE="/var/www/"
DEST="/mnt/backup/www/"
LOG="/var/log/rsync-backup.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] バックアップを開始" >> "$LOG"
rsync -av --delete \
--exclude='cache/' \
--exclude='*.log' \
--log-file="$LOG" \
"$SOURCE" "$DEST"
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo "[$DATE] バックアップが正常に完了しました" >> "$LOG"
else
echo "[$DATE] バックアップが失敗しました(終了コード: $EXIT_CODE)" >> "$LOG"
fi
exit $EXIT_CODE
# 実行権限を付与
chmod +x /usr/local/bin/backup-www.sh
# まず手動でテスト
/usr/local/bin/backup-www.sh
cronでスケジュール設定
スクリプトが手動で正常に動作したら、cronに任せよう。
# rootのcrontabを編集
crontab -e
スケジュールを追加する。cronの構文は:分 時 日 月 曜日 コマンド
# 毎日午前2時30分に実行
30 2 * * * /usr/local/bin/backup-www.sh
# 6時間ごとに実行
0 */6 * * * /usr/local/bin/backup-www.sh
# 毎週日曜日の午前1時に実行
0 1 * * 0 /usr/local/bin/backup-www.sh
cronの構文がわからない場合は、crontab.guruに式を貼り付けると、わかりやすい説明が表示される。
cronがジョブを実行しているか確認する
# システムのcronログを確認(Debian/Ubuntu)
grep CRON /var/log/syslog | tail -20
# cronログを確認(RHEL/AlmaLinux)
grep CRON /var/log/cron | tail -20
複数のバックアップ世代を保持する
通常のrsyncでは1つのコピーしか作成されない。ソースでファイルを削除すると、次回の実行時にバックアップからも削除されてしまう。バージョン管理されたバックアップには、--backupと--backup-dirを使おう:
#!/bin/bash
# バージョン管理バックアップ:変更・削除されたファイルの日付付きコピーを保持
SOURCE="/var/www/"
BACKUP_ROOT="/mnt/backup"
CURRENT="$BACKUP_ROOT/current"
DATED="$BACKUP_ROOT/versions/$(date +%Y-%m-%d)"
rsync -av --delete \
--backup \
--backup-dir="$DATED" \
"$SOURCE" "$CURRENT/"
/mnt/backup/current/はライブミラーとして維持される。変更または削除されたファイルは/mnt/backup/versions/2026-03-08/のような日付付きディレクトリに保存される。毎回フルコピーを保存せずに、特定時点への復元が可能だ。
バージョンディレクトリが何年も蓄積しないよう、クリーンアップステップを追加しよう:
# 30日以上前のバージョンディレクトリを削除
find "$BACKUP_ROOT/versions/" -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;
復元テスト — 省略厳禁
テストされていないバックアップは、何かが壊れるまで安心感を与えるだけのディスクスペースに過ぎない。設定が完了したら、実際に何かを復元してみよう:
# 単一ファイルを復元
rsync -av /mnt/backup/www/html/index.php /tmp/restore-test/
# ディレクトリ全体をテスト場所に復元
rsync -av /mnt/backup/www/ /tmp/full-restore-test/
最低でも四半期に一度は実施しよう。バックアッププロセスは時間とともにずれていく。cronジョブが別のサーバーに移動され、マウントポイントが変わり、宛先ドライブが満杯になっても誰も気づかない。復元テストを行うことで、問題が深刻になる前にすべてを発見できる。
クイックリファレンス
- cronに追加する前に必ずrsyncコマンドを手動でテストする
- 出力をファイルにログ記録する — 無音のcronジョブは失敗を隠してしまう
- 実際に転送せずrsyncが何を転送するかプレビューするには
--dry-runを使う - リモートバックアップには、宛先で制限されたパーミッションを持つ専用SSHキーを設定する
- バックアップ宛先に十分な空き容量があるか確認する — rsyncは転送中にエラーで終了するが、ログを監視していないと気づけない
# ドライラン — 転送される内容を確認
rsync -av --dry-run --delete /var/www/ /mnt/backup/www/
# 宛先のディスク使用量を確認
df -h /mnt/backup
初期設定は約30分で完了する。それ以降は、あなたが意識しなくても毎晩自動で動き続ける。それがすべての目的だ。

