2026年7月1日
2026年7月1日
rsyncのオプションと差分バックアップの方法:–link-destで世代を残すスナップショット運用まで
rsyncで差分バックアップをするには、-a(属性を保ったコピー)と --delete(消えたファイルの反映)を基本に、前回のバックアップを --link-dest で指定して、変わっていないファイルはハードリンクで共有させます。こうすると、毎日の各フォルダーはすべてのファイルがそろった状態に見えるのに、実際に容量を使うのは変更されたファイルの分だけになります。この記事では、よく使うオプションの意味、末尾のスラッシュなどのつまずきやすい点、--link-dest と --compare-dest の違い、世代を残すスクリプトとcronでの自動化、復元の確認までを説明します。
まず覚えたいオプション
rsyncのオプションは多いですが、バックアップで使うものは限られます。
| オプション | 意味 |
|---|---|
-a |
アーカイブモード。-rlptgoD と同じで、再帰的にコピーし、シンボリックリンク・権限・更新日時・所有者・グループ・デバイスファイルを保ちます |
-v / -h |
処理したファイルを表示する / 容量を読みやすい単位で表示する |
-n(--dry-run) |
実際には何も変更せず、何が起きるかだけを表示します |
--delete |
コピー元で消えたファイルを、コピー先でも削除します |
--exclude / --exclude-from |
対象から外すパターンを指定します |
-H |
ハードリンクをハードリンクのまま保ちます |
-A / -X |
ACL / 拡張属性を保ちます |
--numeric-ids |
所有者をユーザー名ではなく番号のまま保ちます。別のサーバーへ復元するときに所有者がずれにくくなります |
-z |
転送中に圧縮します。ネットワーク越しの転送で効果があり、同じマシン内のコピーでは不要です |
-P |
--partial --progress と同じで、途中で切れたファイルを残し、進捗を表示します |
-e "ssh -p 2222" |
リモートへの接続方法を指定します |
-a は所有者を保とうとするため、所有者が異なるファイルをそのまま保ちたい場合は root 権限で実行します。
末尾のスラッシュで結果が変わる
rsyncで最も多い失敗が、コピー元の末尾のスラッシュです。
rsync -a /var/www/html/ /backup/html/ # html の「中身」を /backup/html/ へ
rsync -a /var/www/html /backup/html/ # html というフォルダーごと → /backup/html/html/
コピー元の末尾に / を付けると「このフォルダーの中身」、付けないと「このフォルダー自体」という意味になります。コピー先の書き方は結果にほとんど影響しません。
--delete は dry-run で確かめてから
--delete は、コピー元とコピー先を同じ状態にするためのオプションです。コピー元やコピー先の指定を間違えると、コピー先のファイルが大量に消えます。初めて実行するコマンドは、-n を付けて削除される対象を確かめます。
rsync -avn --delete /var/www/html/ /backup/html/ | grep '^deleting'
なお、--delete を付けた単純なミラーは、コピー元で誤って消したファイルも次の実行でバックアップから消えます。世代を残さないミラーは「最新の状態の複製」であり、誤削除や改ざんからの復旧には向きません。世代を残す方法は次の節で説明します。
「差分」と「増分」の整理
バックアップの用語では、次のように区別されます。
- 差分バックアップ: 最後のフルバックアップから変わったファイルを保存します。復元はフルと最新の差分の2つで済みます。
- 増分バックアップ: 直前のバックアップから変わったファイルを保存します。1回ごとの容量は小さいものの、復元には途中のすべての回が必要です。
rsyncそのものも「変わったファイル(とファイルの変わった部分)だけを転送する」という意味で差分的に動きますが、それだけでは過去の状態は残りません。過去の状態を残すには、次のどちらかを使います。
--link-dest:毎回フルに見えるスナップショット
--link-dest=前回のフォルダー を付けると、前回と同じ内容のファイルは新しくコピーせず、前回のファイルへのハードリンクを作ります。変更されたファイルと新しいファイルだけが実際にコピーされます。
その結果、日付ごとのフォルダーにはすべてのファイルがそろっていて、そのフォルダーだけで復元できます。古いフォルダーを削除しても、ほかのフォルダーから参照されているファイルは消えません。容量の面では増分に近く、復元の手軽さではフルに近い方式で、rsyncでの世代バックアップの定番です。
--compare-dest:本来の意味の差分
--compare-dest=フルバックアップのフォルダー を付けると、そのフォルダーと同じファイルはコピーされず、変わったファイルだけが新しいフォルダーに入ります。フルバックアップを基準にした差分になりますが、復元にはフルと差分を重ねる手間があり、削除されたファイルの記録も残りません。特別な理由がなければ --link-dest を使うほうが扱いやすいです。
世代を残すバックアップスクリプト
--link-dest を使って、日付ごとのフォルダーを作り、14世代を残すスクリプトの例です。実行前に、バックアップ先に十分な空きがあるか、同じファイルシステム上かを確認します。ハードリンクは同じファイルシステムの中でしか作れません。
#!/bin/bash
# /usr/local/bin/rsync-snapshot.sh
set -euo pipefail
SRC="/var/www/html/"
DEST="/backup/www"
KEEP=14
TODAY="$(date +%F_%H%M)"
LATEST="$DEST/latest"
mkdir -p "$DEST"
OPTS=(-aH --delete --numeric-ids --exclude-from=/etc/rsync-backup-exclude.txt)
if [ -d "$LATEST" ]; then
OPTS+=(--link-dest="$LATEST")
fi
set +e
rsync "${OPTS[@]}" "$SRC" "$DEST/$TODAY/"
rc=$?
set -e
# 24 は「コピー中にコピー元のファイルが消えた」で、稼働中のサイトでは起こりうる
if [ "$rc" -ne 0 ] && [ "$rc" -ne 24 ]; then
echo "rsync failed: exit $rc" >&2
exit "$rc"
fi
ln -sfn "$DEST/$TODAY" "$LATEST"
# 名前の新しい順に並べ、KEEP 件より古いフォルダーを削除する
ls -1d "$DEST"/20??-??-??_???? | sort -r | tail -n +$((KEEP + 1)) | while read -r old; do
rm -rf -- "$old"
done
いくつかの点を補足します。
latestというシンボリックリンクで前回のフォルダーを指し、次回の--link-destに使います。初回はlatestが無いので、--link-destを付けずにフルコピーになります。- 終了コードの
23は一部のファイルを転送できなかったことを、24は転送中にコピー元のファイルが消えたことを表します。キャッシュファイルが頻繁に入れ替わるサイトでは24が出やすいため、この例では警告扱いにしています。23は権限などの問題なので失敗として扱います。 - 古い世代は、フォルダー名(日付)で判断して削除しています。
-aはフォルダーの更新日時もコピー元に合わせるため、find -mtimeでは作成日を判断できません。
除外リストの例です。キャッシュやアップグレード用の一時フォルダーは、バックアップから外しても問題になりにくい部分です。
# /etc/rsync-backup-exclude.txt
wp-content/cache/
wp-content/upgrade/
*.log
データベースは別にダンプする
WordPressのようにデータベースを使うサイトでは、MySQLのデータファイルを稼働中にrsyncでコピーしても、整合性のとれたバックアップにはなりません。先に mysqldump や wp db export でダンプを作り、そのファイルをrsyncの対象に含めます。
cronで自動化する
毎日3時30分に実行する例です。flock を使うと、前回の実行が長引いたときに二重に動くのを防げます。
# sudo crontab -e
30 3 * * * flock -n /run/rsync-snapshot.lock /usr/local/bin/rsync-snapshot.sh >> /var/log/rsync-snapshot.log 2>&1
スクリプトには chmod 700 を設定し、ログを定期的に確認します。失敗しても気づかないのが自動バックアップで一番多い問題なので、失敗時にメールや監視サービスへ通知する仕組みを足しておくと安心です。
リモートへ送る場合
同じマシン内のバックアップは、ディスクの故障やサーバーの乗っ取りに弱いため、別のサーバーへも送ります。リモートでも --link-dest は使え、パスはリモート側のパスとして解釈されます。
rsync -aH --delete --numeric-ids -e "ssh -i /root/.ssh/backup_key" \
--link-dest=/backup/www/latest \
/var/www/html/ backup@backup.example.com:/backup/www/$(date +%F_%H%M)/
この場合、latest の付け替えと古い世代の削除はバックアップ先のサーバーで行います。バックアップ専用の鍵を使い、バックアップ先のユーザーにはそのフォルダー以外への権限を与えないようにします。rsyncの版によって使えるオプションに違いがあるため、rsync --version で両側の版を確認しておきます。主要なディストリビューションでは3.2系が広く使われています(2026年10月現在)。
復元と確認
ハードリンクを使った世代は、各フォルダーがそのまま元の状態なので、復元したい日付のフォルダーからコピーし直すだけです。
# まず dry-run で何が変わるか確認する
rsync -avn --delete /backup/www/2026-10-17_0330/ /var/www/html/
# 問題なければ -n を外して実行
容量が本当に節約できているかは、du で確かめられます。同じファイルシステム上のハードリンクは一度だけ数えられるため、複数の世代をまとめて指定すると、2つ目以降の世代は変更分の容量だけが表示されます。
du -sh /backup/www/2026-10-16_0330 /backup/www/2026-10-17_0330
バックアップのファイルはハードリンクで共有されているため、バックアップ先でファイルを直接編集すると、ほかの世代の同じファイルも変わってしまいます。バックアップのフォルダーは読み取り専用として扱い、復元はコピーで行ってください。また、定期的に別の場所へ実際に復元して、サイトが動くかを確かめておきます。
まとめ
- 基本は
-aと--delete。コピー元の末尾の/の有無で結果が変わり、--deleteは-nで確かめてから使います。 - 単純なミラーは誤削除も複製するため、世代を残すには
--link-destを使います。変わっていないファイルはハードリンクになり、各世代が単独で復元できます。 --compare-destはフルを基準にした差分を作れますが、復元の手間を考えると--link-destのほうが扱いやすいです。- スクリプトでは終了コード(23・24)を区別し、古い世代はフォルダー名で削除し、cronでは
flockで二重実行を防ぎます。 - データベースは別にダンプし、別のサーバーにもバックアップを送り、定期的に復元を試します。