バックアップは“作る”だけでは終わりません。復元できて初めてバックアップです。
本講座では、小規模運用やフリーランスの保守現場で実際に回せる「最小構成の定期バックアップ」を、設計から実装、そして検証復元まで手を動かして仕上げます。キーワードは rsync × 世代管理(GFS) × 復元テスト。迷いがちな選択肢を切り分け、現実解だけに絞って進めます。
想定するリスクは、誤削除・更新ミス・障害・ランサムウェア・物理故障。これらに対して、RPO/RTOを数字で決め、3-2-1ルール(3つのコピー/2種類のメディア/1つはオフサイト)を満たす構成を組み立てます。ローカルではrsync+ハードリンクで差分バックアップを高速に回し、オフサイトはrclone+S3/B2で冗長化し、age/GPGで暗号化。さらに daily/weekly/monthly のローテーション、除外設計、通知とログ、そして検証復元の定例化までをテンプレートで整えます。
本講座のゴール
- 設計:RPO/RTOの基準表と3-2-1を満たす保存先の決定
- 実装:
--link-destを活用した差分バックアップと除外リスト運用 - オフサイト:rcloneでの転送・バージョニング・オブジェクトロックの活用
- セキュリティ:暗号化と鍵管理、最小権限での運用
- 運用:cron/systemdタイマー、ログ/アラート、容量監視
- 検証:サンドボックスへの復元リハーサルとハッシュ検証の定例化
前提条件は、LinuxサーバにSSH接続できること(sudo権限)、バックアップ保存用のディスク領域(またはオブジェクトストレージの利用可否)、テスト復元に使える別ディレクトリ(または別ホスト)があること。DBやコンテナをお使いの場合も、論理バックアップやスナップショットを組み合わせて一貫性を確保するところまで解説します。
「いざという時に戻せる自信」を、手順とテンプレートごと持ち帰ってください。ここから先は、実環境を想定した設計→実装→検証の順に、学習者目線で丁寧に進めていきます。
詳細な流れを横断的に確認したい場合は Linuxトラブルシューティング総合ハブ から全体像をご覧いただけます。
バックアップの目的と設計指針(RPO/RTO・3-2-1)
想定環境と前提条件の確認
まずは「どこから、どこへ」そして「どのくらいの頻度で」残すのかを決めます。難しく考えず、最初はローカルの別ディスクに毎日、週次、月次の三種類を作るところからで十分です。SSH で入れる Linux サーバーが1台、保存用のディスク(もしくはマウント済みの NAS)が1つ、オフサイト用にクラウドへ送るためのアカウントが1つ、という想定です。
動かす前に、現在地を軽く確認しておくと後が楽です。
# 実行ユーザーと権限
whoami && id
# 保存先の空き容量(マウント名も確認)
df -hT | grep -E 'Filesystem|/backup'
# バージョン(rsync / rclone / age)
rsync --version | head -1
rclone version | head -1 || true
age --version || true
保存用ディレクトリの骨組みも先に作っておきます。ホスト名と日付で迷わない形にしておくのがポイントです。
sudo mkdir -p /backup/{daily,weekly,monthly}/"$(hostname)"/
sudo chown -R "$USER":"$USER" /backup
RPO/RTOを数値化して基準表を作る
「どれくらい前まで戻れればOKか(RPO)」「復旧にどれくらい時間をかけられるか(RTO)」を、言葉ではなく数字で決めます。ここで背伸びは不要です。まずは RPO=24時間、RTO=2時間など、現実的なラインから始めて、運用しながら詰めていけば大丈夫です。手元のメモでも良いですが、後で共有しやすいように小さな YAML を残しておくと便利です。
# /backup/runbook/policy.yaml
service: "main-web"
rpo_hours: 24 # 24時間以内の時点へ戻せれば許容
rto_hours: 2 # 復旧作業は2時間以内に完了させたい
targets:
- /etc
- /var/www
- /var/lib/mysql
- /home
notes: "まずは最小構成。運用しながら短縮を検討する"
このファイルは“理想”ではなく“約束”です。次に用意するスクリプトやスケジュールは、この数字を守るために動きます。
3-2-1ルールと改ざん耐性の考え方
バックアップは「3つのコピー、2種類のメディア、1つはオフサイト」に分けると安心度がぐっと上がります。ローカルの世代管理に加えて、クラウドへもコピーしておくと、機器故障や盗難、ランサムウェアにも強くなります。クラウド側は「バージョニング」や「削除保護(オブジェクトロック)」を有効化できるサービスを選ぶと、うっかり消してしまった時の後悔が減ります。設定は後ほど rclone の章で具体的に触れます。
バックアップ対象と除外ポリシー
システム設定・シークレット・証明書の扱い
最初に守りたいのは、設定と秘密情報です。/etc の設定一式、アプリの .env、TLS 証明書は、万一の時に復旧の指針になります。ファイルの所有者や権限も重要なので、コピー時に「属性を保つ」オプションを使います。
# 設定・鍵・証明書をまとめて“設定用アーカイブ”に
TODAY=$(date +%F)
HOST=$(hostname)
mkdir -p /backup/daily/$HOST/$TODAY/meta
sudo tar --listed-incremental=/backup/.meta.snar \
--xattrs --acls -czf /backup/daily/$HOST/$TODAY/meta/etc.tgz \
/etc
# 例: Nginx/TLSなど、必要に応じて追加
sudo tar --xattrs --acls -czf /backup/daily/$HOST/$TODAY/meta/certs.tgz \
/etc/letsencrypt || true
.env のような秘密情報は暗号化して保管します。ここでは扱いやすい age を使います。
# 1回だけ鍵ペアを作成
age-keygen -o ~/.config/age/keys.txt
# 受取側(自分)で使う公開鍵を確認
awk '/public key:/{print $3}' ~/.config/age/keys.txt
# 例: .env を暗号化して保存
PUBKEY="age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
age -r "$PUBKEY" -o /backup/daily/$HOST/$TODAY/meta/app-env.age /var/www/app/.env
アプリ/DB/メディア/ログの優先度と境界
Web アプリ本体(/var/www など)は rsync の差分コピーが得意です。データベースは“動いている最中でも矛盾しない取り方”が大切なので、コマンドを使って論理バックアップを作ります。画像・動画などの大きなメディアは容量を食いやすいので、世代管理と除外のバランスを意識します。
# Webルートの差分バックアップ(rsync)
SRC=/var/www
DEST=/backup/daily/$HOST/$TODAY/www
LINKDEST=/backup/daily/$HOST/$(date -d 'yesterday' +%F)/www
mkdir -p "$DEST"
rsync -aAX --delete --numeric-ids \
--link-dest="$LINKDEST" \
--exclude-from=/backup/.exclude \
"$SRC"/ "$DEST"/
# MySQL: トランザクション一貫性を保ちつつダンプ
mysqldump --single-transaction --routines --triggers \
--default-character-set=utf8mb4 \
--all-databases \
| zstd -T0 -19 -o /backup/daily/$HOST/$TODAY/db/all.sql.zst
# PostgreSQL の例
PGPASSWORD="$PGPASSWORD" pg_dumpall -U postgres \
| zstd -T0 -19 -o /backup/daily/$HOST/$TODAY/db/all.psql.zst
メディアの扱いはサイトの性質で変わります。更新頻度が低い大容量ディレクトリは、月次だけ保持する、あるいはクラウド側で長期保存に寄せる、といった分け方が現実的です。
除外リストの作り方(キャッシュ・一時・巨大ファイル)
動作中の一時ファイルやキャッシュまで全部持っていく必要はありません。最初は代表的なパスから始めて、運用しながら足したり引いたりすればOKです。
# /backup/.exclude
/proc
/sys
/dev
/run
/tmp
/var/tmp
/var/cache
/var/lib/docker
/swapfile
**/node_modules
**/.cache
容量が心配な時は、まず“重いもの”を見つけてから方針を決めると無駄がありません。
# 重いディレクトリ上位をざっくり把握
sudo du -xh /var/www | sort -h | tail -n 20
保存先とセキュリティ(ローカル+オフサイト)
ローカル世代管理(GFS)の役割
ローカル側は「毎日とって、週に1つ、月に1つを長めに残す」という素直な回し方が扱いやすいです。rsync の --link-dest を使うと、同じファイルはハードリンクとして共有されるため、見た目は“毎回フル”なのに、使う容量は差分だけ、という動きになります。
# ディレクトリの基本形(毎日・週次・月次)
HOST=$(hostname); TODAY=$(date +%F)
mkdir -p /backup/{daily,weekly,monthly}/$HOST/$TODAY/{www,db,meta}
# 週次・月次への“昇格”は日次からコピーするだけでOK(高速・省容量)
WEEKLY=/backup/weekly/$HOST/$TODAY
MONTHLY=/backup/monthly/$HOST/$TODAY
mkdir -p "$WEEKLY" "$MONTHLY"
cp -al /backup/daily/$HOST/$TODAY/* "$WEEKLY"/
“昇格”という言葉の通り、週次や月次は日次のスナップショットをそのまま格上げします。コピーは一瞬で終わり、容量もほとんど増えません。
rclone×S3/B2とバージョニング/オブジェクトロック
オフサイトには rclone を使います。まずは設定ウィザードで接続を作り、バケット側でバージョニングをオンにします。可能であれば「一定期間は削除や上書きを禁止する」オブジェクトロック(WORM)的な設定も検討すると、消し間違いに強くなります。
# 初回のみ。対話でプロバイダを選択
rclone config
# 例: "myremote" という接続名ができたとする
# 日次バックアップをクラウドへ同期(変更分のみ)
rclone sync /backup/daily/$HOST/$TODAY myremote:my-bucket/$HOST/daily/$TODAY \
--transfers=8 --checkers=16 --fast-list --bwlimit=10M \
--log-file=/backup/logs/rclone-$TODAY.log --log-level=INFO
クラウド側の保持期間(何日分を残すか)は、最初は短めでも構いません。費用と安心感のバランスで、徐々に形を整えていきます。
暗号化(age/GPG)と鍵管理・復号手順
バックアップが外に出るなら、暗号化は“お守り”ではなく“必需品”です。age は鍵の管理がシンプルで、復号も簡単です。鍵ペアを作ったら、公開鍵はサーバーに、秘密鍵は手元の安全な場所へ。万一に備え、復号の手順を紙でも残しておくと安心です。
# ディレクトリを丸ごと暗号化(tarで固めてから age)
SRC_DIR=/backup/daily/$HOST/$TODAY
TAR=/backup/tmp/$HOST-$TODAY.tar.zst
ENC=/backup/offsite/$HOST-$TODAY.tar.zst.age
PUBKEY="age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
mkdir -p /backup/tmp /backup/offsite
tar --xattrs --acls -I 'zstd -19 -T0' -cf "$TAR" -C "$SRC_DIR" .
age -r "$PUBKEY" -o "$ENC" "$TAR"
rm -f "$TAR" # 平文の一時ファイルは残さない
復号はとても簡単です。鍵ファイルを用意して、解凍するだけです。
# 復号して中身を確認
age -d -i ~/.config/age/keys.txt -o /tmp/restore.tar.zst /backup/offsite/$HOST-$TODAY.tar.zst.age
unzstd -c /tmp/restore.tar.zst | tar -tvf - | head
暗号化の相手が複数いる場合は、公開鍵を複数渡して暗号化すれば、どの担当者でも復号できます。運用上の小さな工夫が、いざという時の速さに直結します。
実装① rsync+ハードリンクで差分バックアップ
はじめに「見た目は毎回フル」「中身は差分だけ」という形を作ります。rsync の --link-dest を使うと、前回と同じ内容のファイルは“ハードリンク”として共有され、容量をほとんど増やさずに世代を並べられます。まずは小さな実験で感触をつかんでみましょう。
# お試し:/tmp で差分バックアップの仕組みを確認
rm -rf /tmp/src /tmp/backup && mkdir -p /tmp/src /tmp/backup/{2025-09-09,2025-09-10}
echo hello > /tmp/src/a.txt
rsync -a /tmp/src/ /tmp/backup/2025-09-09/ # 1回目
# 2回目、a.txtは同じでb.txtだけ新規
echo world > /tmp/src/b.txt
rsync -a --link-dest=/tmp/backup/2025-09-09 /tmp/src/ /tmp/backup/2025-09-10/
# 同じ a.txt はハードリンク(リンク数2)になっている
stat -c '%n %h' /tmp/backup/2025-09-0{9,10}/a.txt
本番では、前回のスナップショットへの参照先を「ディレクトリ単位」で渡します。末尾のスラッシュは“そのディレクトリの中身だけをコピーする”という意味なので、つけ忘れないようにします。
#!/usr/bin/env bash
# /usr/local/sbin/backup-rsync.sh
set -Eeuo pipefail
umask 077
DATE=$(date +%F)
YESTERDAY=$(date -d 'yesterday' +%F || date -v -1d +%F) # mac/BSD互換
HOST=$(hostname)
BASE="/backup"
EXCLUDE="$BASE/.exclude"
SNAP="$BASE/daily/$HOST/$DATE"
PREV="$BASE/daily/$HOST/$YESTERDAY"
logdir="$BASE/logs"; mkdir -p "$logdir"
logfile="$logdir/rsync-$DATE.log"
log(){ echo "[$(date +'%F %T')] $*" | tee -a "$logfile"; }
# 同時実行の防止(ロック)
exec 9> /var/lock/backup.lock
flock -n 9 || { echo "another backup is running"; exit 1; }
trap 'rc=$?; log "ERROR (rc=$rc) line=$LINENO"; exit $rc' ERR
mkdir -p "$SNAP"/{www,home,etc,db,meta}
# 前回のスナップショットがあれば --link-dest を有効化
ld_www=(); [[ -d "$PREV/www" ]] && ld_www=(--link-dest="$PREV/www")
ld_home=(); [[ -d "$PREV/home" ]] && ld_home=(--link-dest="$PREV/home")
ld_etc=(); [[ -d "$PREV/etc" ]] && ld_etc=(--link-dest="$PREV/etc")
log "sync /var/www -> $SNAP/www"
rsync -aAX --delete --numeric-ids "${ld_www[@]}" \
--exclude-from="$EXCLUDE" /var/www/ "$SNAP/www/"
log "sync /home -> $SNAP/home"
rsync -aAX --delete --numeric-ids "${ld_home[@]}" \
--exclude-from="$EXCLUDE" /home/ "$SNAP/home/"
log "sync /etc -> $SNAP/etc"
sudo rsync -aAX --delete --numeric-ids "${ld_etc[@]}" /etc/ "$SNAP/etc/"
# ハッシュ台帳を作成(後で整合性チェックに使う)
( cd "$SNAP" && find . -type f ! -name '.sha256' -print0 | sort -z | xargs -0 sha256sum ) > "$SNAP/.sha256"
log "done: $SNAP"
-aAX --numeric-ids は「所有者・権限・ACL・拡張属性も含めて、見た目どおりに写す」ための基本セットです。初回は --dry-run --progress をつけて動きを眺めても良いでしょう。ログはあとで復旧テストのときに役立ちます。
データベースや設定ファイルの扱いは、同じスナップショットの下に“役割ごと”に分けて入れておくと迷いません。$SNAP/db や $SNAP/meta のように箱を分け、rsyncのコピーとダンプを同じ日にまとめます。こうしておくと「この日付の完全な状態」をひと目で確認できます。
実装② tar増分・スナップショット・DB一貫性
rsync だけで十分に回せる環境も多いのですが、「ひと固まりのアーカイブにしたい」「ネットワーク越しに一気に送りたい」という場面では tar の増分モードが便利です。仕組みはシンプルで、前回との差分情報を SNAR ファイル に記録しておき、次回はそこから変化分だけを拾います。
# /var/www を tar の増分で保存(初回フル → 2回目以降は差分)
SNAP_BASE="/backup/daily/$HOST/$DATE"
mkdir -p "$SNAP_BASE/www-inc"
SNAR="$SNAP_BASE/www-inc/track.snar"
# 初回(SNARがなければ自動で作成)
tar --listed-incremental="$SNAR" -I 'zstd -19 -T0' \
-cf "$SNAP_BASE/www-inc/full-$(date +%F).tar.zst" /var/www
# 2回目以降(差分)
tar --listed-incremental="$SNAR" -I 'zstd -19 -T0' \
-cf "$SNAP_BASE/www-inc/inc-$(date +%F-%H%M).tar.zst" /var/www
復元はフルのあとに差分を時間順で重ねるだけです。tar -tvf で中身をのぞいてから展開すると安心です。ひとかたまりにしたアーカイブは、age でそのまま暗号化しやすいのも利点です。
稼働中のデータベースは、コピーの仕方に少し気をつけます。MySQL/MariaDB なら --single-transaction を付けると、動いている最中でも“矛盾のない状態”でダンプできます。圧縮は CPU が許す範囲で強めにかけると、転送量が下がって嬉しい場面が多いです。
# MySQL/MariaDB(すべてのDBを論理バックアップ)
mkdir -p "$SNAP/db"
mysqldump --single-transaction --routines --triggers \
--default-character-set=utf8mb4 --events --flush-logs \
--all-databases \
| zstd -T0 -19 -o "$SNAP/db/all.sql.zst"
PostgreSQL は pg_dump(単体のDB)か pg_dumpall(まとめて)を使います。拡張やロールも含めるなら pg_dumpall が手軽です。
# PostgreSQL(全体バックアップ)
PGPASSWORD="$PGPASSWORD" pg_dumpall -U postgres \
| zstd -T0 -19 -o "$SNAP/db/all.psql.zst"
もしアプリがコンテナ上で動いているなら、ダンプはコンテナの中で行います。docker exec でコマンドを実行し、標準出力をそのまま圧縮して取り込みます。
# Docker上のMySQLの例(コンテナ名: mysql)
docker exec mysql sh -lc \
'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction --all-databases' \
| zstd -T0 -19 -o "$SNAP/db/all.sql.zst"
ファイルシステムに スナップショット機能(LVM/ZFS/Btrfs) があるなら、いったん“時点を止める”ようにコピーすると、より安心して取れます。ここでは考え方だけを共有します。LVM なら、ボリュームのスナップショットを切ってそこから rsync で吸い上げ、終わったらスナップショットを削除します。ZFS/Btrfs なら send/receive を使って増分転送も可能です。導入の手間は少し増えますが、更新の多いシステムでは効果が大きい方法です。
最後に、どの方法でも“戻せること”が目的です。アーカイブを作った直後に少しだけ時間を取り、テスト用ディレクトリへ展開してみましょう。tar -tvf で目当てのファイル名が見えるか、zstdcat | tar -x で実際に開けるか、mysql --execute="SHOW DATABASES" でリストア後に期待するDBが並ぶか。ほんの数分の確認で、バックアップ全体への信頼感が大きく変わります。ここまでできれば、次は「毎日・週次・月次の回し方」と「容量をあふれさせない整理」に進んでいけます。
ローテーション設計(daily/weekly/monthly)
毎日のスナップショットを“週次”や“月次”に昇格させ、古い世代を整理していくと、見た目はシンプルでも中身はしっかり守れる構成になります。考え方は難しくありません。日次はこまめに残し、週次は「その週で一番良い状態」を代表に、月次は「その月の代表選手」を残すイメージです。昇格はコピーではなくハードリンクで行うため、処理は一瞬、容量もほとんど増えません。
実際の回し方は小さなスクリプトにまとめると扱いやすいです。週の終わり(例:日曜)に日次→週次へ、月初に日次→月次へ昇格し、保持数を超えた分は古いものから安全に片付けます。
#!/usr/bin/env bash
# /usr/local/sbin/backup-rotate.sh
set -Eeuo pipefail
umask 077
HOST=$(hostname)
BASE="/backup"
TODAY=$(date +%F)
LOGDIR="$BASE/logs"; mkdir -p "$LOGDIR"
LOG="$LOGDIR/rotate-$TODAY.log"
log(){ echo "[$(date +'%F %T')] $*" | tee -a "$LOG"; }
# 保持数(まずは控えめに)
KEEP_D=7 # 日次7世代
KEEP_W=4 # 週次4世代
KEEP_M=6 # 月次6世代
# 同時実行の防止
exec 9> /var/lock/backup-rotate.lock
flock -n 9 || { echo "another rotation is running"; exit 1; }
# 週次・月次への昇格(最新の日次をベースに)
LATEST_DAILY=$(ls -1d $BASE/daily/$HOST/* 2>/dev/null | sort | tail -n1 || true)
if [[ -n "$LATEST_DAILY" ]]; then
# 週次(日曜=7)に昇格
if [[ $(date +%u) -eq 7 ]]; then
WDIR="$BASE/weekly/$HOST/$TODAY"
mkdir -p "$WDIR"
log "promote daily -> weekly: $LATEST_DAILY -> $WDIR"
cp -al "$LATEST_DAILY"/* "$WDIR"/
fi
# 月次(1日)に昇格
if [[ $(date +%d) -eq 01 ]]; then
MDIR="$BASE/monthly/$HOST/$TODAY"
mkdir -p "$MDIR"
log "promote daily -> monthly: $LATEST_DAILY -> $MDIR"
cp -al "$LATEST_DAILY"/* "$MDIR"/
fi
fi
prune_old () {
local dir="$1" keep="$2"
[[ -d "$dir" ]] || return 0
mapfile -t snaps < <(ls -1d "$dir"/* 2>/dev/null | sort)
local total="${#snaps[@]}"
(( total <= keep )) && return 0
local remove_count=$(( total - keep ))
for ((i=0; i<remove_count; i++)); do
log "prune: ${snaps[$i]}"
rm -rf --one-file-system -- "${snaps[$i]}"
done
}
# 古い世代を整理(安全に上から)
prune_old "$BASE/daily/$HOST" "$KEEP_D"
prune_old "$BASE/weekly/$HOST" "$KEEP_W"
prune_old "$BASE/monthly/$HOST" "$KEEP_M"
log "rotation done"
最初のうちは保持数を控えめにして、容量の増え方を見ながら微調整すると安心です。削除処理は“上から順に古いもの”だけを触り、パスが期待どおりかをログで残しておくと、万一のときも追跡できます。実行前に echo に置き換えて“動きだけ”確かめるのもおすすめです。
検証復元と整合性チェック・通知
バックアップは、作った直後に“軽いリハーサル”をする習慣があるだけで、信頼度が大きく変わります。やることはシンプルで、ハッシュで整合性を確認し、テスト用ディレクトリに少しだけ展開して、アプリやDBが読み込めるかを確かめます。終わったら、成功・失敗を通知します。
先ほどの rsync スクリプトで .sha256 台帳を生成しました。これを使って、その日のスナップショットが壊れていないかを一括で確認できます。
#!/usr/bin/env bash
# /usr/local/sbin/backup-verify.sh
set -Eeuo pipefail
umask 077
HOST=$(hostname)
BASE="/backup"
TODAY=$(date +%F)
LOGDIR="$BASE/logs"; mkdir -p "$LOGDIR"
LOG="$LOGDIR/verify-$TODAY.log"
PING_OK="${PING_OK:-}" # 例: https://hc-ping.com/xxxxxxxx-... (成功時)
PING_FAIL="${PING_FAIL:-}" # 例: https://hc-ping.com/xxxxxxxx-.../fail (失敗時)
exec 9> /var/lock/backup-verify.lock
flock -n 9 || { echo "another verify is running"; exit 1; }
latest=$(ls -1d $BASE/daily/$HOST/* 2>/dev/null | sort | tail -n1)
[[ -n "$latest" ]] || { echo "no snapshot"; exit 1; }
notify(){ [[ -n "$1" ]] && curl -fsS --max-time 10 "$1" >/dev/null || true; }
trap 'rc=$?; echo "FAIL rc=$rc" | tee -a "$LOG"; notify "$PING_FAIL"; exit $rc' ERR
echo "verify: $latest" | tee -a "$LOG"
( cd "$latest" && sha256sum -c .sha256 ) | tee -a "$LOG"
# 代表的なディレクトリだけ、サンドボックスに展開して目視確認
SANDBOX="/restore-sandbox/$(date +%F-%H%M)"
mkdir -p "$SANDBOX/www"
rsync -a "$latest/www/" "$SANDBOX/www/"
echo "ok" | tee -a "$LOG"
notify "$PING_OK"
通知先はメールでも Slack でも構いません。Slack なら Webhook を1つ用意して、成功・失敗のメッセージを分けるだけで十分です。
# 例: Slack へ通知
SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXX/YYY/ZZZ"
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"✅ Backup verify OK: $(hostname) $(date +%F)\"}" \
"$SLACK_WEBHOOK_URL"
検証の頻度は、最初は毎日でも構いません。慣れてきたら、日次はハッシュだけ、週次はサンドボックス復元まで、月次は実機で“丸ごと”の復元演習、と段階を分けると負担が抑えられます。
自動化テンプレと運用(cron/systemd・ログ)
毎日手で実行するのは続きません。サーバーが得意な「決まった時間に、決まった順番で」の出番です。cron はシンプル、systemd タイマーは“失敗したら再試行”や依存関係の管理が得意です。どちらでも構いませんが、どちらか一方に統一するとトラブル時の見通しが良くなります。
cron での例は次の通りです。深夜の空いている時間に、rsync → ローテーション → 検証 → クラウド同期の順でつなぎます。
# crontab -e
# 毎日 02:15 に差分バックアップ
15 2 * * * /usr/local/sbin/backup-rsync.sh >> /backup/logs/cron-rsync.log 2>&1
# 毎日 03:00 にローテーション
00 3 * * * /usr/local/sbin/backup-rotate.sh >> /backup/logs/cron-rotate.log 2>&1
# 毎日 03:15 に整合性チェック&検証
15 3 * * * PING_OK="https://hc-ping.com/xxx" PING_FAIL="https://hc-ping.com/xxx/fail" /usr/local/sbin/backup-verify.sh >> /backup/logs/cron-verify.log 2>&1
# 毎日 04:00 にオフサイトへ同期
00 4 * * * /usr/local/sbin/backup-offsite.sh >> /backup/logs/cron-offsite.log 2>&1
systemd 版は、サービスとタイマーをセットで置きます。ログは journalctl でまとまって見られるので、障害のときに追いやすいのが利点です。
# /etc/systemd/system/backup-rsync.service
[Unit]
Description=Daily rsync snapshot backup
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/backup-rsync.sh
Nice=10
# /etc/systemd/system/backup-rsync.timer
[Unit]
Description=Run backup-rsync daily
[Timer]
OnCalendar=*-*-* 02:15:00
Persistent=true
[Install]
WantedBy=timers.target
systemctl daemon-reload && systemctl enable --now backup-rsync.timer のように有効化すれば、サーバーの再起動をまたいでも、次のタイミングで自動実行されます。同様に backup-rotate.timer、backup-verify.timer、backup-offsite.timer を用意すれば、分業しつつ順番もコントロールできます。
ログは溜まりっぱなしにせず、軽く回しておくとディスクにも優しくなります。
# /etc/logrotate.d/backup-scripts
/backup/logs/*.log {
daily
rotate 14
compress
missingok
notifempty
create 0640 root root
}
運用の“取扱説明書(Runbook)”も、サーバー上に一緒に置いておくと、いざというときに迷いません。復元の順番、鍵の場所、誰が代わりに実行できるかを短くまとめるのがコツです。
つまずきやすいポイントと拡張(Q&A/ツール比較)
最初に出会いやすい悩みは、だいたい決まっています。権限で止まるときは、rsync -aAX --numeric-ids を使って“見たままの権限と所有者”を保つのが近道です。別のサーバーに戻すなら、ユーザーIDやグループIDが違っていてファイルの持ち主がズレることがあります。そんな時はユーザーを先に作るか、chown --reference を活用して揃えると落ち着きます。--delete は便利ですが、間違った宛先に向けると消してはいけないものまで消します。最初は --dry-run をつけて“何が起きるか”を眺める癖をつけましょう。
容量の悩みは、メディアの扱いで解決することが多いです。更新の少ない大容量ディレクトリは月次だけ残す、あるいはクラウド側の長期保存クラスに寄せると、ローカルは軽く、オフサイトは安心、という良いバランスになります。転送の失敗は、帯域制限や不安定な回線が原因のことが多いので、rclone で --bwlimit を少し絞ったり、--retries を増やして落ち着かせると安定します。時刻ずれも地味に効くので、NTP を入れておくとログの追跡が楽です。
ツールの選び方で迷ったら、まず rsync の“見た目フル・中身差分”を体感してください。もう少し自動で面倒を見てほしいと感じたら、rsnapshot は rsync のハードリンク運用をラップしてくれるので、設定ファイル一つで世代管理まで面倒を見てくれます。より強力な圧縮や重複排除、暗号化を“最初から込み”で使いたいなら、borg や restic が選択肢です。どちらも検証の仕組みが充実していて、リポジトリ自体の整合性チェックができます。コンテナや仮想マシンを丸ごと守りたい場合は、アプリのデータは“アプリのやり方(ダンプ)”、画像は“ファイルとして(rsync/tar)”、VMイメージは“別のレイヤ”で、という分担にすると、復元の時に迷いません。
ここまでの流れで、設計→実装→検証→自動化のひと回りを体験できました。残りは、あなたの現場に合わせて少しずつチューニングするだけです。RPO/RTO を時々見直し、検証復元をサボらず、通知が静かな日々が続いているかを見守る。その積み重ねが“戻せる自信”につながっていきます。
まとめ
バックアップは「作ったら安心」ではなく、「戻せる自信を育てる習慣」となります。
この記事で学習できたのは、rsync の差分や GFS の回し方という手順だけではありません。RPO/RTO を数字で決める設計の視点、3-2-1 による複線化、暗号化と鍵の扱い、そして検証復元を欠かさない姿勢そのものが、あなたの現場を静かに強くします。
デジタルのデータは、便利さと引き換えに消えるときは一瞬です。誤削除、壊れたディスク、アップデートのつまずき、ランサムウェア。どれも珍しい出来事ではありません。消えたその瞬間から、業務は止まり、説明責任が生まれ、信頼が揺れます。復旧に追われる時間は、売上や機会の損失だけでなく、チームの疲弊や判断の質まで奪っていきます。だからこそ、「失う前に準備する」が唯一の現実解です。
心構えはシンプルです。完璧を狙わず、最小構成で始めて、毎日回す。戻せることを確認して、少しずつ整える。日次を作り、週次・月次に昇格し、オフサイトに複製し、鍵を守り、ログと通知で見守る。難しい言葉に置き換えれば冗長化や耐改ざん性ですが、やっていることは「未来の自分を助けるための、今日の小さな積み木」です。
そして忘れたくないのは、バックアップの価値は復元の瞬間にこそ現れるということ。サンドボックスへの展開やハッシュ照合、DB のダンプからのリストアを、ほんの数分のリハーサルとして続けてみてください。はじめて「戻った」とき、あなたの中に静かな確信が生まれます。あの確信は、システムを守るだけでなく、クライアントやチームへの説明、提案の説得力、見積もりの精度、働く時間の質まで変えていきます。
この記事の手順は、特別な環境を前提にしていません。あなたのサーバーでそのまま動き、あなたの現場のルールに合わせて無理なく伸びていくように作りました。
今日できるところからで構いません。--link-dest の一歩でも、検証復元の5分でも、通知のピンでも、積み上がれば大きな差になります。データが消える「一瞬」に備えるために、今の「一手」を置く。その繰り返しが、不安を設計に、事故を学びに、作業を力に変えていきます。
明日、通知が静かに「成功」を知らせてくれるはずです。その静けさが、あなたの新しい標準になります。

