2026年7月7日
2026年7月7日
VPSのバックアップを自動化する方法:WordPressのDBとファイルを毎日別の場所へ送る仕組み
VPSのバックアップを自動化するには、「データベースのダンプを取る」「必要なファイルと一緒に別の場所へ送る」「古い世代を消す」という一連の処理をスクリプトにまとめ、systemd タイマー(または cron)で毎日実行します。さらに、失敗に気づける仕組みと、定期的な復元テストまで用意して初めて、いざというときに使えるバックアップになります。この記事では、WordPressが動くVPSを例に、restic を使った暗号化バックアップを自動で回すまでの手順を説明します。
何をどこにバックアップするか
最初に対象を決めます。WordPressのVPSで最低限残したいのは次のものです。
- データベース:投稿、固定ページ、設定、ユーザーなど。ファイルではなく、mysqldump でダンプしたものを保存します
- WordPressのファイル:特に
wp-content(テーマ、プラグイン、アップロードした画像)とwp-config.php - サーバーの設定:
/etc/nginxや/etc/apache2、PHPの設定、/etc/letsencrypt、crontab など。なくてもサイトは作り直せますが、復旧にかかる時間が大きく変わります
保存先は、VPSとは別の事業者やクラウドストレージにします。同じVPSの中や、同じ事業者のスナップショットだけでは、契約やアカウントのトラブル、基盤の障害でまとめて失う可能性が残ります。S3互換のオブジェクトストレージや、別の事業者のVPSが候補です。
なぜ restic を使うのか
tar と rsync の組み合わせでも自動化はできますが、ここでは restic を使います。理由は次の通りです。
- 送信前に暗号化されるので、保存先の事業者からは中身が見えない
- 重複排除により、毎日フルで取っても変更があった部分だけが送られる
forgetコマンドで「日次7世代、週次4世代」のような世代管理ができる- S3互換ストレージ、SFTP、rest-server など保存先の選択肢が多い
Debian/Ubuntu系なら sudo apt install restic で入ります。ディストリビューションのパッケージは版が古いことがあるため、restic version で確認し、必要なら公式の配布物を使ってください(2026年10月現在)。
手順1:認証情報を安全に置く
バックアップ用の設定ファイルは root だけが読めるディレクトリにまとめます。パスワードをスクリプトやコマンドラインに直接書くと、ps の表示やシェルの履歴に残るためです。
sudo install -d -m 700 /etc/wp-backup
sudo touch /etc/wp-backup/my.cnf /etc/wp-backup/restic.env /etc/wp-backup/restic-password
sudo chmod 600 /etc/wp-backup/*
/etc/wp-backup/my.cnf には、データベースに接続するための情報を書きます。バックアップ専用のユーザーを作り、読み取りに必要な権限(SELECT、SHOW VIEW、TRIGGER、LOCK TABLES など)だけを与えておくと安全です。
[client]
user=backup_user
password=your-db-password
/etc/wp-backup/restic.env には保存先の情報を書きます。restic-password にはリポジトリの暗号化パスワードを1行で書きます。
RESTIC_REPOSITORY=s3:https://s3.example.com/your-bucket/wp-vps
RESTIC_PASSWORD_FILE=/etc/wp-backup/restic-password
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
暗号化パスワードを失うと、バックアップは誰にも復元できなくなります。パスワード管理ツールなど、VPSの外にも控えを残してください。設定ができたら、一度だけリポジトリを初期化します。
sudo bash -c 'set -a; . /etc/wp-backup/restic.env; set +a; restic init'
手順2:バックアップスクリプトを作る
/usr/local/sbin/wp-backup.sh として次の内容を保存し、sudo chmod 700 で実行権限を付けます。パスやデータベース名は環境に合わせて変えてください。
#!/usr/bin/env bash
set -euo pipefail
umask 077
DUMP_DIR=/var/backups/mysql
SITE_DIR=/var/www/example.com
DB_NAME=wordpress_db
mkdir -p "$DUMP_DIR"
# 1. データベースをダンプする(一時ファイルに書いてから置き換える)
mysqldump --defaults-extra-file=/etc/wp-backup/my.cnf \
--single-transaction --routines --triggers --no-tablespaces \
"$DB_NAME" > "$DUMP_DIR/$DB_NAME.sql.tmp"
mv "$DUMP_DIR/$DB_NAME.sql.tmp" "$DUMP_DIR/$DB_NAME.sql"
# 2. ダンプとファイルを restic で送る
restic backup --tag wp \
"$DUMP_DIR" "$SITE_DIR" /etc/nginx /etc/letsencrypt \
--exclude "$SITE_DIR/wp-content/cache"
# 3. 古い世代を整理する
restic forget --tag wp --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
いくつかの点に理由があります。
set -euo pipefailにより、途中のコマンドが失敗した時点でスクリプトが止まり、異常終了として記録されます。ダンプに失敗したのに古いダンプを送って「成功」と表示される事態を防げます--single-transactionは、InnoDBのテーブルをロックせずに一貫した時点の内容を取り出すためのものです。サイトを止めずに実行できます--no-tablespacesは、MySQL 8.0 系でバックアップ用ユーザーにPROCESS権限がないときに出るエラーを避けるために付けています- ダンプのファイル名に日付を入れていないのは、世代管理を restic に任せているからです。同じ名前で上書きしても、過去の版は restic のスナップショットに残ります
- ダンプを gzip で圧縮していないのは、重複排除が効きにくくなるためです。新しい形式のリポジトリでは restic 側で圧縮されます
まず手動で実行し、エラーが出ないことと、restic snapshots に記録が増えることを確認します。スクリプトは保存先の情報を環境変数から読むので、手動で動かすときは手順1の restic init と同じように設定ファイルを読み込んでから実行します。
sudo bash -c 'set -a; . /etc/wp-backup/restic.env; set +a; /usr/local/sbin/wp-backup.sh && restic snapshots'
手順3:systemd タイマーで毎日実行する
cron でも構いませんが、systemd タイマーにはログが journal にまとまる、実行時刻をずらせる、停止中に逃した実行を起動後に補える、といった利点があります。
/etc/systemd/system/wp-backup.service:
[Unit]
Description=WordPress backup with restic
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/wp-backup/restic.env
ExecStart=/usr/local/sbin/wp-backup.sh
Nice=10
IOSchedulingClass=idle
/etc/systemd/system/wp-backup.timer:
[Unit]
Description=Run WordPress backup daily
[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=15m
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now wp-backup.timer
systemctl list-timers wp-backup.timer
# 手動で一度動かして結果を見る
sudo systemctl start wp-backup.service
journalctl -u wp-backup.service -n 50
Nice と IOSchedulingClass は、バックアップ中にサイトの応答が遅くなるのを和らげるための設定です。アクセスの少ない時間帯を選ぶことも効果があります。
手順4:失敗に気づける仕組みを作る
自動化でいちばん多い失敗は、「止まっていたのに何か月も気づかなかった」というものです。ディスクの空き不足、認証情報の期限切れ、保存先の容量超過などで、ある日から静かに失敗し続けることがあります。
対策としては、成功したときだけ外部の監視サービスに通知を送り、通知が途絶えたら知らせてもらう方式(いわゆるデッドマンスイッチ)が扱いやすい方法です。スクリプトの最後に次のような行を足します。URLは使う監視サービスが発行するものに置き換えます。
curl -fsS -m 10 --retry 3 https://monitor.example.com/ping/your-check-id > /dev/null
set -e があるので、途中で失敗すればこの行まで到達せず、通知が来ないことで異常が分かります。systemd の OnFailure= を使って失敗時に別のユニットを動かす方法もあります。
手順5:復元テストを定期的に行う
バックアップは、戻せることを確かめるまでは仮のものです。月に一度などと決めて、別のディレクトリへの復元を試します。
restic snapshots --tag wp
restic restore latest --tag wp --target /tmp/restore-test
ls -la /tmp/restore-test/var/backups/mysql/
ダンプは、テスト用のデータベースや検証環境に読み込んで、記事が表示されるところまで確認できると安心です。あわせて、ときどき restic check でリポジトリの整合性も確認します。確認が終わった復元データは、個人情報を含むので削除しておきます。
保存先の消去対策も考える
VPSが乗っ取られた場合、VPSに置いてある認証情報を使ってバックアップ自体を消される可能性があります。保存先側でバージョニングやオブジェクトロックを有効にする、追記のみを許可するモード(rest-server の append-only モードなど)を使う、削除権限を持たないキーを使う、といった対策を検討してください。使える機能はストレージの種類や事業者によって異なります。
まとめ
- 対象はデータベースのダンプ、
wp-contentとwp-config.php、サーバーの設定。保存先はVPSとは別の場所にする - 認証情報は root だけが読めるファイルに置き、コマンドラインやスクリプト本文に書かない
- スクリプトは
set -euo pipefailで失敗した時点で止まる形にし、systemd タイマーで毎日実行する - 成功通知が途絶えたら分かる監視と、月1回程度の復元テストまでを自動化の一部として扱う
- 乗っ取り時にバックアップを消されないよう、保存先側の保護機能も検討する