はじめに:バックアップは保険である

ホームサーバーを運用していると、いつかは問題が発生します。ハードディスクの故障、誤操作によるファイル削除、ランサムウェア感染など、データ損失のリスクは常に存在します。前編でセキュリティ設定を完了したなら、今度は最悪の事態に備える番です。

「バックアップはしていますか?」という質問に「もちろん」と答える方も多いですが、実際にリカバリテストをしたことがある方は少ないです。バックアップは単にファイルをコピーすることではなく、復元可能な状態を維持することです。この編では、体系的なバックアップ戦略とともに、問題が発生する前に事前に検知できるモニタリングシステムの構築方法を見ていきます。

1. 3-2-1バックアップルール

1.1 3-2-1ルールとは?

データバックアップのゴールデンルールと呼ばれる3-2-1ルールは以下の通りです:

  • 3つのコピー:元データを含め、最低3つのコピーを維持します。
  • 2種類の異なるメディア:最低2種類の異なるストレージメディアにバックアップします(例:内蔵HDD + 外付けHDD、SSD + NAS)。
  • 1つはオフサイト:最低1つのバックアップは物理的に異なる場所に保管します(クラウドまたは別の地域のサーバー)。

このルールに従えば、火災、盗難、ハードウェア故障など、ほとんどの災害状況でもデータを復元できます。

1.2 ホームサーバーへの適用

ホームサーバー環境で3-2-1ルールを適用する実際の例です:

コピー 保存場所 バックアップ方式 頻度
元データ サーバーメインディスク - -
バックアップ1 サーバー内別ディスクまたはNAS rsync 毎日
バックアップ2 クラウド(Google Drive、Backblazeなど) rclone 毎週

2. rsyncを使ったローカルバックアップ

2.1 rsync基本使用法

rsyncはLinuxで最も広く使われているファイル同期ツールです。変更されたファイルのみを転送するため、効率的で高速です。

# rsyncインストール確認(ほとんどデフォルトでインストール済み)
rsync --version

# 基本使用法
rsync -av /source/directory/ /backup/directory/

# 主要オプション説明
# -a : アーカイブモード(権限、所有者、タイムスタンプなどを保持)
# -v : 詳細出力
# -z : 圧縮転送(リモートバックアップ時に有用)
# --delete : ソースで削除されたファイルをバックアップでも削除
# --exclude : 特定ファイル/ディレクトリを除外
# --progress : 転送進行状況表示

2.2 実用的なrsync例

# ホームディレクトリバックアップ(特定ファイル除外)
rsync -av --progress \
  --exclude='.cache' \
  --exclude='*.tmp' \
  --exclude='node_modules' \
  /home/username/ /backup/home/

# Dockerボリュームバックアップ
rsync -av --progress \
  /var/lib/docker/volumes/ /backup/docker-volumes/

# リモートサーバーへバックアップ(SSH使用)
rsync -avz --progress \
  -e "ssh -p 2222" \
  /home/username/ user@remote-server:/backup/

# 双方向同期が必要な場合(注意して使用)
rsync -av --delete /source/ /destination/

2.3 増分バックアップの実装

日付別に増分バックアップを維持すると、特定時点への復元が可能です。

# ハードリンクを使った増分バックアップ
#!/bin/bash
BACKUP_DIR="/backup/incremental"
SOURCE_DIR="/home/username"
DATE=$(date +%Y-%m-%d)
LATEST="$BACKUP_DIR/latest"

# 以前のバックアップがあればハードリンクを使用
if [ -d "$LATEST" ]; then
    rsync -av --delete \
      --link-dest="$LATEST" \
      "$SOURCE_DIR/" "$BACKUP_DIR/$DATE/"
else
    rsync -av "$SOURCE_DIR/" "$BACKUP_DIR/$DATE/"
fi

# latestシンボリックリンク更新
rm -f "$LATEST"
ln -s "$BACKUP_DIR/$DATE" "$LATEST"

# 30日以上経過したバックアップを削除
find "$BACKUP_DIR" -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;

3. 自動バックアップスクリプト(cron)

3.1 バックアップスクリプト作成

体系的なバックアップスクリプトを作成してみましょう。

#!/bin/bash
# /usr/local/bin/backup.sh

# 設定
BACKUP_ROOT="/backup"
LOG_FILE="/var/log/backup.log"
DATE=$(date +%Y-%m-%d_%H-%M-%S)
DISCORD_WEBHOOK="YOUR_DISCORD_WEBHOOK_URL"

# ログ関数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 通知関数
notify() {
    local message="$1"
    local status="$2"  # success or error

    if [ "$status" = "error" ]; then
        color="15158332"  # 赤色
    else
        color="3066993"   # 緑色
    fi

    curl -H "Content-Type: application/json" \
         -d "{\"embeds\":[{\"title\":\"バックアップ通知\",\"description\":\"$message\",\"color\":$color}]}" \
         "$DISCORD_WEBHOOK" 2>/dev/null
}

# バックアップ開始
log "=== バックアップ開始 ==="

# 1. Dockerコンテナ停止(データ一貫性保証)
log "Dockerコンテナ一時停止中..."
docker-compose -f /home/username/docker-compose.yml stop

# 2. 重要ディレクトリのバックアップ
DIRS_TO_BACKUP=(
    "/home/username/data"
    "/var/lib/docker/volumes"
    "/etc/nginx"
    "/etc/letsencrypt"
)

for dir in "${DIRS_TO_BACKUP[@]}"; do
    if [ -d "$dir" ]; then
        dest_dir="$BACKUP_ROOT/daily/$(basename $dir)"
        log "バックアップ中: $dir -> $dest_dir"
        rsync -av --delete "$dir/" "$dest_dir/" 2>&1 | tee -a "$LOG_FILE"
    fi
done

# 3. データベースバックアップ
log "データベースバックアップ中..."
mkdir -p "$BACKUP_ROOT/database"
docker exec mysql-container mysqldump -u root -pPASSWORD --all-databases > "$BACKUP_ROOT/database/mysql_$DATE.sql"

# 4. Dockerコンテナ再起動
log "Dockerコンテナ再起動中..."
docker-compose -f /home/username/docker-compose.yml start

# 5. 古いバックアップの整理
log "古いバックアップ整理中..."
find "$BACKUP_ROOT/database" -name "*.sql" -mtime +7 -delete

# バックアップ完了
BACKUP_SIZE=$(du -sh "$BACKUP_ROOT" | cut -f1)
log "=== バックアップ完了 (合計サイズ: $BACKUP_SIZE) ==="
notify "バックアップが正常に完了しました。\n合計サイズ: $BACKUP_SIZE" "success"

3.2 cron設定

# スクリプトに実行権限付与
sudo chmod +x /usr/local/bin/backup.sh

# crontab編集
sudo crontab -e

# 毎日深夜3時にバックアップ実行
0 3 * * * /usr/local/bin/backup.sh

# 毎週日曜日深夜4時にクラウドバックアップ実行
0 4 * * 0 /usr/local/bin/cloud-backup.sh

# cronログ確認
grep CRON /var/log/syslog
ヒント:cronジョブが正しく実行されるか確認するには、最初は短い間隔で設定し、正常動作を確認してから希望の周期に変更してください。

4. クラウドバックアップ(rclone)

4.1 rcloneインストールと設定

rcloneは様々なクラウドストレージをコマンドラインで管理できるツールです。Google Drive、Dropbox、OneDrive、AWS S3、Backblaze B2など数十種類のサービスをサポートします。

# rcloneインストール
curl https://rclone.org/install.sh | sudo bash

# またはaptでインストール
sudo apt install rclone

# rclone設定(対話型)
rclone config

4.2 Google Drive設定例

# rclone config実行後
# n) New remote選択
# name> gdrive
# Storage> drive (Google Drive)
# client_id> (Enterキーでデフォルト値使用または独自ID入力)
# client_secret> (Enterキーでデフォルト値使用)
# scope> 1 (Full access)
# root_folder_id> (Enterキー)
# service_account_file> (Enterキー)
# Edit advanced config? n
# Use auto config? y (Webブラウザで認証)
# Configure as team drive? n

# 設定確認
rclone listremotes

# 接続テスト
rclone lsd gdrive:

4.3 rcloneバックアップスクリプト

#!/bin/bash
# /usr/local/bin/cloud-backup.sh

BACKUP_SOURCE="/backup/daily"
REMOTE_NAME="gdrive"
REMOTE_PATH="homeserver-backup"
LOG_FILE="/var/log/cloud-backup.log"
DATE=$(date +%Y-%m-%d)

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "=== クラウドバックアップ開始 ==="

# バックアップディレクトリ同期
rclone sync "$BACKUP_SOURCE" "$REMOTE_NAME:$REMOTE_PATH/$DATE" \
    --progress \
    --transfers 4 \
    --checkers 8 \
    --log-file="$LOG_FILE" \
    --log-level INFO

# 古いクラウドバックアップ削除(30日以上)
log "古いクラウドバックアップ整理中..."
rclone delete "$REMOTE_NAME:$REMOTE_PATH" \
    --min-age 30d \
    --log-file="$LOG_FILE"

# 使用量確認
USAGE=$(rclone about "$REMOTE_NAME:" --json | jq -r '.used // 0')
log "=== クラウドバックアップ完了 (使用量: $USAGE bytes) ==="

4.4 暗号化バックアップ

重要なデータはクラウドにアップロードする前に暗号化することをお勧めします。

# rclone crypt設定
rclone config
# n) New remote
# name> gdrive-crypt
# Storage> crypt
# remote> gdrive:encrypted-backup
# filename_encryption> standard
# directory_name_encryption> true
# Password> (強力なパスワード入力)
# Salt> (Enterで自動生成)

# 暗号化されたバックアップ使用
rclone sync /backup/sensitive gdrive-crypt:

5. システムモニタリングツール

5.1 htop - プロセスモニター

htopはターミナルで使用する対話型プロセスビューアです。

# htopインストール
sudo apt install htop

# 実行
htop

# 主要ショートカットキー
# F2: 設定
# F3: 検索
# F4: フィルター
# F5: ツリービュー
# F6: ソート
# F9: シグナル送信(kill)
# F10: 終了

5.2 glances - 総合システムモニター

glancesはhtopよりも多くの情報を一目で表示するツールです。

# glancesインストール
sudo apt install glances

# またはpipでインストール(最新バージョン)
pip3 install glances

# 基本実行
glances

# Webサーバーモードで実行(リモートモニタリング)
glances -w -p 61208

# Dockerコンテナモニタリング含む
glances --enable-plugin docker

# 特定時間ごとにレポート保存
glances --export csv --export-csv-file /var/log/glances.csv

5.3 netdata - リアルタイムWebダッシュボード

netdataは美しいWebインターフェースとともにリアルタイムモニタリングを提供します。

# netdataインストール(公式スクリプト)
wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh
bash /tmp/netdata-kickstart.sh

# サービス状態確認
sudo systemctl status netdata

# Webインターフェースアクセス(デフォルトポート: 19999)
# http://サーバーIP:19999

# 設定ファイル場所
# /etc/netdata/netdata.conf

# 通知設定
# /etc/netdata/health_alarm_notify.conf
# netdata Discord通知設定
sudo nano /etc/netdata/health_alarm_notify.conf

# 以下の内容を追加/修正
SEND_DISCORD="YES"
DISCORD_WEBHOOK_URL="YOUR_DISCORD_WEBHOOK_URL"
DEFAULT_RECIPIENT_DISCORD="alerts"

6. Grafana + Prometheusモニタリングスタック

6.1 Docker Composeでインストール

専門的なモニタリング環境を構築するには、GrafanaとPrometheusの組み合わせが標準です。

# docker-compose-monitoring.yml
version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/usr/share/prometheus/console_libraries'
      - '--web.console.templates=/usr/share/prometheus/consoles'
      - '--storage.tsdb.retention.time=15d'
    ports:
      - "9090:9090"
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=your_secure_password
      - GF_USERS_ALLOW_SIGN_UP=false
    ports:
      - "3000:3000"
    restart: unless-stopped
    depends_on:
      - prometheus

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
    ports:
      - "9100:9100"
    restart: unless-stopped

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8080:8080"
    restart: unless-stopped

volumes:
  prometheus_data:
  grafana_data:

6.2 Prometheus設定

# prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
    - static_configs:
        - targets: []

rule_files: []

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']
# スタック実行
docker-compose -f docker-compose-monitoring.yml up -d

# Grafanaアクセス: http://サーバーIP:3000
# デフォルトアカウント: admin / your_secure_password

# GrafanaでPrometheusデータソース追加
# Configuration > Data Sources > Add data source > Prometheus
# URL: http://prometheus:9090

6.3 便利なGrafanaダッシュボード

Grafanaダッシュボードは自分で作成することもできますが、コミュニティで共有されているダッシュボードをインポートして使用できます。

  • Node Exporter Full (ID: 1860):システム全体モニタリング
  • Docker Container Monitoring (ID: 193):Dockerコンテナモニタリング
  • Nginx (ID: 9614):Nginx Webサーバーモニタリング
# ダッシュボードインポート
# Grafana > Dashboards > Import
# Dashboard ID入力後Load
# Prometheusデータソース選択後Import

7. 通知設定

7.1 Discord Webhook通知

#!/bin/bash
# /usr/local/bin/discord-notify.sh

WEBHOOK_URL="YOUR_DISCORD_WEBHOOK_URL"

send_notification() {
    local title="$1"
    local message="$2"
    local color="${3:-3447003}"  # デフォルト青色

    curl -H "Content-Type: application/json" \
         -d "{
             \"embeds\": [{
                 \"title\": \"$title\",
                 \"description\": \"$message\",
                 \"color\": $color,
                 \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"
             }]
         }" \
         "$WEBHOOK_URL"
}

# 使用例
# send_notification "サーバー通知" "バックアップが完了しました。" "3066993"  # 緑色
# send_notification "警告" "ディスク使用量90%超過" "15158332"  # 赤色

7.2 Telegramボット通知

#!/bin/bash
# /usr/local/bin/telegram-notify.sh

BOT_TOKEN="YOUR_BOT_TOKEN"
CHAT_ID="YOUR_CHAT_ID"

send_telegram() {
    local message="$1"

    curl -s -X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \
         -d chat_id="$CHAT_ID" \
         -d text="$message" \
         -d parse_mode="HTML"
}

# 使用例
# send_telegram "サーバー通知
# バックアップが完了しました。
# 時間: $(date)"

7.3 システム状態通知スクリプト

#!/bin/bash
# /usr/local/bin/system-check.sh

source /usr/local/bin/discord-notify.sh

# ディスク使用量チェック
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 85 ]; then
    send_notification "ディスク警告" "ルートパーティション使用量: ${DISK_USAGE}%" "15158332"
fi

# メモリ使用量チェック
MEM_USAGE=$(free | awk '/Mem:/ {printf "%.0f", $3/$2 * 100}')
if [ "$MEM_USAGE" -gt 90 ]; then
    send_notification "メモリ警告" "メモリ使用量: ${MEM_USAGE}%" "15158332"
fi

# サービス状態チェック
SERVICES=("docker" "nginx" "ssh")
for service in "${SERVICES[@]}"; do
    if ! systemctl is-active --quiet "$service"; then
        send_notification "サービスダウン" "$service サービスが停止しました!" "15158332"
    fi
done

# 負荷チェック
LOAD=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' ')
CPU_CORES=$(nproc)
LOAD_INT=${LOAD%.*}
if [ "$LOAD_INT" -gt "$CPU_CORES" ]; then
    send_notification "負荷警告" "システム負荷: $LOAD (CPUコア: $CPU_CORES)" "15105570"
fi
# cronに登録(5分ごとにチェック)
sudo crontab -e
*/5 * * * * /usr/local/bin/system-check.sh

8. 障害対応と復旧手順

8.1 障害対応チェックリスト

サーバー障害発生時に慌てないよう、あらかじめチェックリストを準備しておきましょう。

  1. 問題確認:どのサービスが影響を受けているか?
  2. ログ確認:journalctl、/var/log/ 確認
  3. リソース確認:CPU、メモリ、ディスク容量
  4. ネットワーク確認:接続状態、ファイアウォールルール
  5. 最近の変更事項:アップデート、設定変更の有無
  6. サービス再起動:該当サービスまたはサーバー再起動
  7. ロールバック:必要時、以前の状態に復旧

8.2 バックアップからの復旧

# rsyncバックアップから復旧
sudo rsync -av --progress /backup/daily/data/ /home/username/data/

# データベース復旧
docker exec -i mysql-container mysql -u root -pPASSWORD < /backup/database/mysql_2026-01-22.sql

# クラウドバックアップから復旧
rclone copy gdrive:homeserver-backup/2026-01-22 /restore/ --progress

# Dockerボリューム復旧
sudo systemctl stop docker
sudo rsync -av /backup/docker-volumes/ /var/lib/docker/volumes/
sudo systemctl start docker

8.3 復旧テストの重要性

バックアップだけして復旧テストをしないと、実際の状況で慌てることがあります。定期的に復旧テストを行いましょう。

# 復旧テストスクリプト例
#!/bin/bash
# /usr/local/bin/recovery-test.sh

TEST_DIR="/tmp/recovery-test-$(date +%Y%m%d)"
mkdir -p "$TEST_DIR"

echo "=== バックアップ復旧テスト開始 ==="

# 1. ローカルバックアップテスト
echo "ローカルバックアップからサンプルファイル復旧中..."
rsync -av /backup/daily/data/sample-file.txt "$TEST_DIR/"

if [ -f "$TEST_DIR/sample-file.txt" ]; then
    echo "ローカルバックアップ復旧成功"
else
    echo "ローカルバックアップ復旧失敗!"
fi

# 2. クラウドバックアップテスト
echo "クラウドバックアップからサンプルファイル復旧中..."
rclone copy gdrive:homeserver-backup/latest/sample-file.txt "$TEST_DIR/cloud/"

if [ -f "$TEST_DIR/cloud/sample-file.txt" ]; then
    echo "クラウドバックアップ復旧成功"
else
    echo "クラウドバックアップ復旧失敗!"
fi

# クリーンアップ
rm -rf "$TEST_DIR"
echo "=== 復旧テスト完了 ==="

まとめ

バックアップとモニタリングは普段は目立ちませんが、問題が発生したときにその価値を発揮します。面倒でも3-2-1ルールに従ってバックアップ体系を構築し、モニタリングシステムでサーバー状態を常に把握できるようにしておきましょう。

特に重要なのは「復元可能な」バックアップを維持することです。バックアップファイルがあっても復元できなければ意味がありません。定期的に復旧テストを行い、復旧手順を文書化しておくことをお勧めします。

これでホームサーバー構築完全ガイドシリーズを終了します。ハードウェア選択からOSインストール、ネットワーク設定、Docker、セキュリティ、そしてバックアップとモニタリングまで、ホームサーバー運用に必要な核心的な内容を扱いました。このガイドが皆さんのホームサーバー構築の旅に役立つことを願っています。

追加の質問や気になる点があれば、コメントで残してください!