2026年7月15日
2026年7月15日
Amazon S3へのバックアップ自動化方法(aws s3 syncとcronで世代管理まで)
サーバーのファイルを Amazon S3 へ定期的に送るなら、AWS CLI の aws s3 sync を cron で回す方法が手軽です。ただし sync は「今の状態を鏡のように写す」コマンドなので、そのままでは誤削除や改ざんも S3 側に反映されてしまいます。この記事では、権限を絞った IAM の準備、sync の正しい使い方、バケットのバージョニングとライフサイクルルールによる世代管理、復元の確認までを順に説明します。
全体の構成を決める
WordPress サーバーを例にすると、バックアップ対象は大きく「データベース」と「ファイル(wp-content など)」の2つです。性質が違うので、送り方を分けると管理しやすくなります。
- データベース:mysqldump などで日付つきのダンプファイルを作り、
aws s3 cpで1ファイルずつ送る - ファイル:
aws s3 syncで差分だけを送り、過去の状態はバケットのバージョニングで残す
S3 は Unix のパーミッションや所有者、シンボリックリンクの情報を保持しません。サーバーを丸ごと元の状態に戻す必要があるなら、tar でまとめてから送るほうが確実です。一方、画像などのメディアを素早く戻せればよいという用途なら、sync のほうが転送量を抑えられます。
手順1:バケットを作り、バージョニングを有効にする
バックアップ用のバケットは、公開サイト用のバケットとは分けて作ります。S3 では新規バケットのパブリックアクセスのブロックが既定で有効で、保存時の暗号化(SSE-S3)も既定で適用されます(2026年10月現在)。バックアップ用途ではこの状態を変えないでください。
次にバージョニングを有効にします。バージョニングを有効にしたバケットでは、オブジェクトを上書き・削除しても以前のバージョンが残ります。sync の --delete でファイルが消えても、削除マーカーが付くだけで、過去のバージョンから取り戻せます。
aws s3api put-bucket-versioning \
--bucket your-backup-bucket \
--versioning-configuration Status=Enabled
ランサムウェアや乗っ取り対策をさらに強めたい場合は、一定期間削除できなくする S3 Object Lock もあります。設定を誤ると自分でも消せなくなる機能なので、保持期間とモードの意味を公式ドキュメントで確認してから使ってください。
手順2:バックアップ専用の権限を用意する
EC2 上のサーバーなら、アクセスキーを置かずにインスタンスに IAM ロールを割り当てるのが基本です。AWS 外の VPS などから送る場合は、バックアップ専用の IAM ユーザーを作り、そのアクセスキーだけを使います。管理者権限のキーをサーバーに置くのは避けてください。
権限は、対象バケットの一覧取得とアップロード、sync の --delete を使うなら削除までに絞ります。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::your-backup-bucket"
},
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::your-backup-bucket/*"
}
]
}
ここで s3:DeleteObjectVersion を与えていない点が重要です。バージョニングが有効なバケットでは、DeleteObject だけでは過去のバージョンを消せないため、サーバーが乗っ取られてキーを使われても、履歴まで消される危険を下げられます。
AWS 外のサーバーでは、名前つきプロファイルとして認証情報を保存します。
aws configure --profile backup
# Access Key ID / Secret Access Key / リージョン を入力
chmod 600 ~/.aws/credentials
AWS CLI は現行の v2 を使います。aws --version で aws-cli/2. から始まることを確認してください。
手順3:aws s3 sync の動きを理解して試す
sync は、送り元と送り先のファイルをサイズと更新日時で比べ、違いがあるものだけを転送します。まず --dryrun で、何が送られ何が消されるかを確認します。
aws s3 sync /var/www/html/wp-content/ \
s3://your-backup-bucket/files/wp-content/ \
--profile backup \
--exclude "cache/*" --exclude "*.log" \
--delete --dryrun
つまずきやすい点を挙げます。
- 末尾のスラッシュ:送り元と送り先の階層がずれると、意図しない場所に全ファイルが二重に送られます。dryrun の出力でパスを確認します。
- --delete の意味:送り元にないファイルを S3 から消します。バージョニングなしで使うと、誤って消したファイルが S3 からも消えます。
- 除外の書き方:
--excludeのパターンは送り元からの相対パスに対して評価されます。キャッシュやログ、別のバックアップファイルを除外して転送量を減らします。 - シンボリックリンク:既定ではリンク先をたどって送ります。不要なら
--no-follow-symlinksを付けます。
問題がなければ --dryrun を外して実行し、aws s3 ls s3://your-backup-bucket/files/ --recursive --summarize --profile backup で件数と合計サイズを確かめます。
手順4:スクリプトにまとめて cron で回す
データベースのダンプとファイルの sync を1本のスクリプトにまとめます。
#!/usr/bin/env bash
# /usr/local/bin/s3-backup.sh
set -euo pipefail
PROFILE="backup"
BUCKET="s3://your-backup-bucket"
STAMP="$(date +%Y%m%d-%H%M%S)"
TMP="/var/backups/s3-tmp"
mkdir -p "$TMP"
chmod 700 "$TMP"
# 1) データベース(認証情報は ~/.my.cnf に置き、権限 600)
mysqldump --single-transaction --quick --default-character-set=utf8mb4 wordpress \
| gzip > "${TMP}/db-${STAMP}.sql.gz"
aws s3 cp "${TMP}/db-${STAMP}.sql.gz" "${BUCKET}/db/" --profile "$PROFILE"
rm -f "${TMP}/db-${STAMP}.sql.gz"
# 2) ファイル
aws s3 sync /var/www/html/wp-content/ "${BUCKET}/files/wp-content/" \
--profile "$PROFILE" \
--exclude "cache/*" --exclude "*.log" \
--delete --only-show-errors
echo "s3 backup ok: ${STAMP}"
30 3 * * * flock -n /run/s3-backup.lock /usr/local/bin/s3-backup.sh >> /var/log/s3-backup.log 2>&1
--single-transaction は InnoDB のテーブルをロックせずに一貫したダンプを取るための指定です。pipefail を付けているので、mysqldump が途中で失敗した場合はスクリプト全体がエラーで止まります。cron は失敗しても知らせてくれないため、ログを監視するか、失敗時に通知する仕組みを足してください。
転送が回線を占有して困る場合は、aws configure set default.s3.max_bandwidth 10MB/s のように帯域を制限できます。プロファイルを分けているなら、そのプロファイルに対して設定します。
手順5:ライフサイクルルールで世代を整理する
バージョニングを有効にしたままだと古いバージョンが増え続け、保存量が膨らみます。ライフサイクルルールで、一定日数を過ぎた古いバージョンや古いダンプを自動で削除します。
{
"Rules": [
{
"ID": "expire-old-versions",
"Status": "Enabled",
"Filter": { "Prefix": "files/" },
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 }
},
{
"ID": "expire-db-dumps",
"Status": "Enabled",
"Filter": { "Prefix": "db/" },
"Expiration": { "Days": 60 },
"NoncurrentVersionExpiration": { "NoncurrentDays": 7 }
}
]
}
aws s3api put-bucket-lifecycle-configuration \
--bucket your-backup-bucket \
--lifecycle-configuration file://lifecycle.json
このルール設定は管理者権限で行い、バックアップ用のキーには与えません。古いデータを Glacier 系のストレージクラスへ移すルールも作れますが、取り出しに時間がかかることや、最低保存期間・取り出し手数料があることを理解したうえで選んでください。料金体系は変わることがあるため、最新の情報は AWS の料金ページで確認します。
手順6:復元を試す
本番とは別の場所へ戻して、中身を確かめます。
# ファイルを検証用ディレクトリへ
aws s3 sync s3://your-backup-bucket/files/wp-content/ /tmp/restore-test/wp-content/ --profile backup
# データベースのダンプを取得して検証用 DB へ
aws s3 cp s3://your-backup-bucket/db/db-20261018-033000.sql.gz /tmp/ --profile backup
gunzip -c /tmp/db-20261018-033000.sql.gz | mysql wordpress_test
過去のバージョンを取り出したいときは、aws s3api list-object-versions --bucket your-backup-bucket --prefix files/wp-content/uploads/2026/ で VersionId を確認し、aws s3api get-object に --version-id を付けて取得します。復元にかかった時間も記録しておくと、障害時に役立ちます。
まとめ
- DB はダンプを
aws s3 cp、ファイルはaws s3 syncと、性質に合わせて送り方を分ける - sync は鏡写しなので、バケットのバージョニングと組み合わせて過去の状態を残す
- 専用の IAM ロールまたはユーザーに必要最小限の権限だけを与え、
s3:DeleteObjectVersionは渡さない --dryrunでパスと削除対象を確認してから cron で自動化し、失敗の通知も用意する- ライフサイクルルールで古いバージョンを整理し、定期的に復元を試す