「 の自動バックアップが動いているから安心」と思っていましたが、設定画面を見て気づきました。週 1 回・1 世代のみ。3 日前の誤削除には対応できません。

本記事では、当方が Hostinger ( + + + Traefik + 各種 DB)に対して、 + で毎日を組んだ実装記録を、コマンド付きで公開します。月額 22 円、初回 16.7GB を 2 分 59 秒でアップロード、7 世代保管の構成です。

構成全体像

restic + Cloudflare R2 によるバックアップ構成図
restic + Cloudflare R2 によるバックアップ構成図
要素採用理由
バックアップ元Hostinger VPSNextcloud / n8n / Postiz / 各 DB が稼働
バックアップツールrestic 0.16.4単一バイナリ、暗号化、、対応
保存先 R2egress 無料、$0.015/GB/月、S3 互換
スケジュール 04:00 UTC(13:00 JST)サーバー負荷の低い時間帯
日次 7 + 週次 4 + 月次 6restic forget で自動運用
暗号化restic 標準パスフレーズで全リポジトリ暗号化

Step 1: Cloudflare R2 のセットアップ

1.1 R2 を有効化

Cloudflare ダッシュボード → R2 で「Purchase R2」をクリック。クレジットカードを登録(無料枠内なら課金 0 円)。

1.2 バケット作成

項目設定
Bucket name<任意>(例: vps-backup)
LocationAutomatic(Asia Pacific が自動選択)
Default Storage ClassStandard(restic は週 1 回 check で読みに行くため)

Infrequent Access クラスは月 1 回未満アクセス前提で、restic の整合性チェックで取り出し料金が発生するため不向きです。

1.3 Account API Token を発行

「Manage R2 API Tokens」→「Create Account API token」。

項目設定
Token namevps-backup-token
PermissionsObject Read & Write(Admin Read & Write は権限が広すぎるので NG)
Specify bucket(s)作成したバケットのみ()
TTLForever

発行後に表示される Access Key ID と Secret Access Key を必ずメモ。この画面を閉じると Secret は二度と表示されません。

💡 KEY TAKEAWAYS
Account API Token は必ずバケットスコープで発行。「All buckets」+「Admin Read & Write」は事故時の影響範囲が広すぎます。

バックアップの仕組みを、社内で組んで直せる人を育てませんか?
Claude Code 研修
研修の内容を見る

Step 2: VPS 側のセットアップ

2.1 restic のインストール

export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq restic
restic version
# → restic 0.16.4 compiled with go1.22.2 on linux/amd64

apt-get の対話プロンプトが表示されると SSH 越しにハングするので、DEBIAN_FRONTEND=noninteractive を必ず設定します。

2.2 認証情報を保存

/root/.config/restic/r2-credentials.env を chmod 600 で作成:

export AWS_ACCESS_KEY_ID="<R2 Access Key ID>"
export AWS_SECRET_ACCESS_KEY="<R2 Secret Access Key>"
export RESTIC_REPOSITORY="s3:https://<account-id>.r2.cloudflarestorage.com/<bucket-name>"
export RESTIC_PASSWORD="<openssl rand -base64 32 で生成した強パスフレーズ>"

RESTIC_PASSWORD は 3 重保管してください(VPS / 手元 PC / オフライン媒体)。これを失うとバックアップは復元できなくなります。

2.3 リポジトリ初期化

. /root/.config/restic/r2-credentials.env
restic init
# → created restic repository <id> at s3:https://...

これで R2 バケット内に restic のリポジトリ構造が作られます。

Step 3: バックアップスクリプト

/usr/local/bin/vps-backup.sh を作成(chmod 700)。流れは「DB ダンプ → restic backup → 保持ポリシー適用 → 軽い整合性チェック」。

#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="/var/log/vps-backup.log"
DUMP_DIR="/tmp/restic-dumps-$$"
log() { printf '[%s] %s\n' "$(date -u +%FT%TZ)" "$*" | tee -a "$LOG_FILE"; }
trap 'rm -rf "$DUMP_DIR"' EXIT

. /root/.config/restic/r2-credentials.env
. /root/.config/restic/db-credentials.env

mkdir -p "$DUMP_DIR" && chmod 700 "$DUMP_DIR"

log "=== Backup START ==="

# DB ダンプ(並列で MariaDB + Postgres x N)
docker exec -e MYSQL_PWD="$NEXTCLOUD_DB_PASSWORD" nextcloud-db \
    mariadb-dump --single-transaction --quick --skip-lock-tables \
    -uroot nextcloud | gzip > "$DUMP_DIR/nextcloud.sql.gz"

docker exec -e PGPASSWORD="$POSTIZ_DB_PASSWORD" postiz-postgres \
    pg_dumpall -U postiz | gzip > "$DUMP_DIR/postiz.sql.gz"

# restic backup
restic backup \
    --tag scheduled \
    --exclude-caches \
    --exclude '/var/log' --exclude '/tmp' \
    --exclude '/root/.npm' --exclude '/root/.cache' \
    /var/lib/docker/volumes/nextcloud_nc_data \
    /var/lib/docker/volumes/n8n_data \
    /var/lib/docker/volumes/postiz_postiz-uploads \
    /etc /root/.ssh /root/.config \
    "$DUMP_DIR"

# 保持ポリシー(日次 7 + 週次 4 + 月次 6)+ 自動 prune
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

# 軽い整合性チェック(5% サブセット)
restic check --read-data-subset=5%

log "=== Backup END ==="

mariadb-dump は --single-transaction でロックなしダンプ。本番稼働中の DB に影響しません。

Step 3.1: cron に登録

( crontab -l 2>/dev/null; echo '0 4 * * * /usr/local/bin/vps-backup.sh >> /var/log/vps-backup.log 2>&1' ) | crontab -

毎日 04:00 UTC(13:00 JST)に自動実行。サーバー負荷の低い時間帯です。

Step 3.2: ログローテート設定

cat > /etc/logrotate.d/vps-backup <<EOF
/var/log/vps-backup.log {
    weekly
    rotate 8
    compress
    missingok
    notifempty
    create 600 root root
}
EOF

これでログが肥大化することを防げます。

Step 4: 初回フルバックアップ

nohup /usr/local/bin/vps-backup.sh >> /var/log/vps-backup.log 2>&1 &
tail -f /var/log/vps-backup.log

当方の実測値:

項目値
ファイル数332,446
元サイズ16.730 GiB
R2 上の実サイズ5.048 GiB(圧縮率 2.56x、60.93% 削減)
所要時間2 分 59 秒
アップロード速度約 95 MB/s

R2 月額試算: 5GB × $0.015 = $0.075(約 11 円)。7 世代分の差分も含めて月 $0.15(約 22 円)程度に収まります。

Step 5: — 必ずやる

「バックアップは取れている」と思い込んで実は復元できないケースが多すぎます。月 1 回必ず復元テストを行います。

. /root/.config/restic/r2-credentials.env

# 任意のファイルを R2 から取り出す
restic restore latest \
    --target /tmp/restore-test \
    --include /etc/hostname

# オリジナルと比較
diff /etc/hostname /tmp/restore-test/etc/hostname && echo "✅ 一致"

# 後片付け
rm -rf /tmp/restore-test

当方の実測:単一ファイル復元は1 秒以内、フル復元は 5〜15 分。

💡 KEY TAKEAWAYS
復元テストをしていないバックアップは、バックアップではありません。月 1 回必ず実行する習慣をつけてください。

ハマりポイント 3 つ

restic + R2 構築で踏んだ 3 つの罠
restic + R2 構築で踏んだ 3 つの罠

落とし穴 1: SSH 鍵パスのタイプミス

~/.ssh/_ed25519 を ~/.ssh/_ed25739 と打ち間違えて、SSH コマンドが「Permission denied (publickey)」で延々と弾かれました。Tab 補完を使わずに手打ちで ed25519 を入れる場合、数字部分は要注意です。

突破: 鍵の場所は SSH の設定ファイルに接続先の別名(エイリアス)として登録して、ssh で接続するスタイルが安全です。

落とし穴 2: apt-get install がプロンプトでハング

apt-get install restic が SSH 越しに実行中、Configuration file modified の対話プロンプトが出てハングしました。SSH は「待機中」と見えるだけで、何も進みません。

突破: export DEBIAN_FRONTEND=noninteractive をスクリプト先頭で必ず宣言。これだけで対話プロンプトを全て自動回答に切り替えられます。

落とし穴 3: restic の保持ポリシー試算ミス

「日次 7 + 週次 4 + 月次 6 = 17 世代分の容量が必要」と思いがちですが、restic は重複排除しているため、実容量は初回 + 変更分のみ。当方の場合 16.7GB の元データに対し、7 世代維持しても R2 上は 7〜10GB 程度で済みます。

突破: 試算は「初回 × 1.5」程度を上限の目安に。月額計算は実測でみて初月で確定させます。

次のアクション

「バックアップは大事」と頭で分かっていても、実際に組まないと災害復旧はできません。本記事の構成は VPS 1 台あたり月 22 円から始められ、構築 30〜60 分で運用に乗ります。

本シリーズ第 1 回「Nextcloud のロック詰まりは Redis 化で解決する」、第 3 回「Dropbox 代替を自社で組む判断軸」と合わせて読むと、自社のクラウドストレージを / サブスクリプションから卒業する全体像が掴めます。

「自社で組むのは技術的に厳しい」「組んだが運用が続かない」という方は、社員が自分で業務の仕組みを作れるようになる Claude Code 研修 も行っています。EXBANK ではバックアップ構築 + 6 ヶ月伴走運用パッケージも提供しています。