ラズパイ 読む時間 約22分

ラズパイでデータベースのバックアップを完全自動化:レンタルサーバーから自宅の NAS へ毎晩保存

ラズパイに毎晩、レンタルサーバーのデータベースのバックアップを取らせて、自宅の NAS に完全自動で保存する仕組みの作り方です。サーバーとラズパイに設置するファイルと命令を、自分の環境に変える所を示して順に紹介します。

ラズパイで DB のバックアップを完全自動化:レンタルサーバーから自宅の NAS へ毎晩保存

※ この記事には Amazon のアソシエイトリンクが含まれています。

ラズパイに、毎晩、レンタルサーバー(エックスサーバー)のデータベースのバックアップを取らせて、自宅の NAS に完全自動で保存する環境を構築しました。2026 年 10 月から毎日動いています。

この記事では、同じ仕組みを自分の環境でも作れるように、設置するファイルと実行するコマンドを順番に書きます。「ここだけ自分の環境に変える」の所を書き換えれば、あとはそのまま実行できます。

この回の前にできていること

なぜ作ったのか

これまでは、システムを更新する前などに、サーバーの管理画面からデータベースのバックアップを取って、手元にダウンロードしていました。多い日は 1 日に 3 回です。

困っていたこと

  • 管理画面を開いて、ダウンロードが終わるまで待つ手間がかかる
  • ダウンロードしたファイルがどんどん増えて、どれを残せばよいか分からなくなる
  • 取り忘れると、もしものときに戻せない

用意するもの

現在わたしが家で使っている機種と、これから用意するときの選び方です。

ラズパイ本体

  • ラズパイの役目は、毎晩バックアップを取りに行くことです。夜に 1 回動かすだけなので、古い型でも大丈夫です。
  • 現在わたしが家で使っている機種:Raspberry Pi 3 Model B
  • これから買うなら、第 1 回と同じ Raspberry Pi 5 4GB(技適あり)

NAS

NAS に取り付ける HDD(HDD が別売りの NAS のとき)

  • 24 時間動かすので、NAS 向けの HDD を選びます。
  • 現在わたしが家で使っている物:NAS に付いていた HDD
  • 例:WD Red Plus 2TB

スイッチングハブと LAN ケーブル

  • ラズパイと NAS を同じハブに有線でつなぐと、やり取りが速く、途中で切れにくくなります。なくても動きます(Wi-Fi でも可)。有線にする手順は第 8 回にまとめました。
  • 現在わたしが家で使っている機種:TP-Link TL-SG608(8 ポート)と、カテゴリー 6 の LAN ケーブル
  • TP-Link TL-SG608(有線にしてバックアップの時間が半分になった話はこちら)

仕組みの流れ

  1. ① ラズパイが、毎晩 2 時 40 分にサーバーへつなぐ(ラズパイの cron)
  2. ② サーバーの側で、データベースのバックアップを作って圧縮する(mysqldump と gzip)
  3. ③ 圧縮したデータを、ラズパイが受け取って、そのまま NAS に書く
  4. ④ 書き終わったら壊れていないかを確認してから、正式な名前を付ける
毎晩のバックアップの流れ:ラズパイが 2 時 40 分にサーバーへつなぎ、サーバーが DB のバックアップを圧縮して出し、ラズパイがそのまま NAS に書き、壊れていないかを確認してから名前を付ける

④ の「壊れていないか」は、何を確認しているか

取ったファイルを展開して置き直すことはせず、次の 2 つを確認しています。

  • gzip として最後まで読めるか(gzip -t):圧縮したファイルを最後まで読み、中に書いてあるチェック用の値と合うかを見ます。転送の途中で切れたり、データが化けたりしていれば、ここで分かります。
  • 最後の行に「Dump completed」があるか:mysqldump は、最後まで書き終えたときにだけ、この 1 行を書きます。途中で止まったバックアップを見分けられます。

MySQL に読み込んで、本当に戻せるかまでは試していません。それは、ときどき別の場所に戻してみる「戻す練習」で確認します。

正常でないときは、どうなるか

  • 取ったファイルは正式な名前にせず、「○○.broken」の名前で NAS に残します。あとから中を見て原因を調べられます。
  • backup.log に「確認で NG」と書き、失敗を知らせるファイル(~/backup/FAILED)を作って止まります。手順 D の「翌朝に確認する」で見ているのは、このファイルです。
  • 前の日までのよいバックアップは消しません。古いものを消す処理は、成功したときにしか動かないので、失敗が続いても、手元のよいバックアップが減っていくことはありません。
  • 次の日に成功すれば、FAILED のファイルは自動で消えます。

ラズパイの SD カードには書き込まないので、SD カードの劣化(寿命が縮むこと)を防げます。容量も使いません。ここからは、サーバー側(手順 A)とラズパイ側(手順 B)にファイルを設置して、動かして(手順 C)、毎晩動くようにします(手順 D)。

手順 A:サーバーにバックアップを出すスクリプトを設置する

手順 A は、サーバー側の準備を最初に 1 回だけする所です。いつも使っている PC から SSH でサーバーにつないで、次のスクリプトを ~/bin/backup-dump.sh に設置します(エックスサーバーなら、サーバーパネルの「ファイル管理」から設置してもかまいません)。ラズパイがサーバーにつなぐのは、手順 B で専用の鍵を作ってからです。

このスクリプトは、ラズパイから「dump 名前」と呼ばれたときだけ、決めた DB のバックアップを出します。

ここだけ自分の環境に変える(手順 A)

  • db_of の中の「mysite」と「example_db」:自分で決める名前と、その名前で取る DB の名前(サーバーパネルの MySQL 設定で見られる)
  • CNF_DIR:接続の設定を置く場所(そのままでよい)
#!/bin/bash
# ~/bin/backup-dump.sh(レンタルサーバーに置く)
# ラズパイから「dump 名前」と呼ばれたときだけ、DB のバックアップを gzip にして出す
set -euo pipefail
umask 077
# ===== ここだけ自分の環境に変える =====
# 名前と、その名前で取る DB の名前(何行でも足せる)
db_of() {
  case "$1" in
    mysite)  echo "example_db" ;;
    # blog)  echo "example_blog_db" ;;
    *)       return 1 ;;
  esac
}
CNF_DIR="$HOME/.backup"   # 接続の設定(名前.cnf)を置く場所
# =====================================
read -r cmd name extra <<< "${SSH_ORIGINAL_COMMAND:-}"
if [ "${cmd:-}" != "dump" ] || [ -z "${name:-}" ] || [ -n "${extra:-}" ]; then
  echo "使えるのは dump <名前> だけ" >&2; exit 2
fi
db=$(db_of "$name") || { echo "知らない名前:$name" >&2; exit 2; }
cnf="$CNF_DIR/$name.cnf"
[ -f "$cnf" ] || { echo "$cnf がない" >&2; exit 3; }
# いったんサーバーの中のファイルに書いてから送る(回線が遅いと、流しながらでは MySQL が待ちきれずに切れるため)
out=$(mktemp -p "$CNF_DIR" dump.XXXXXX)
trap 'rm -f "$out"' EXIT
nice -n 15 mysqldump --defaults-extra-file="$cnf" --single-transaction --quick \
  --no-tablespaces --default-character-set=utf8mb4 "$db" | gzip -6 > "$out"
cat "$out"

サーバーにはバックアップのファイルが残りません

  • mysqldump が出すデータを、そのまま gzip に流して圧縮するので、圧縮していない大きなファイルは作りません。
  • 圧縮したファイルは、ラズパイに送り終わると trap の行が自動で消します。途中でエラーになって止まったときも消えます。
  • 作っているあいだだけ、圧縮したファイルの大きさ分の空き容量がサーバーに要ります。

サーバーの再起動などで処理が強制的に止められたときだけ、~/.backup/dump. で始まるファイルが残ることがあります。ときどき ls ~/.backup で見て、残っていたら消してください。

次に、DB につなぐための接続の情報(ユーザー名・パスワード・ホスト名)を、DB ごとに 1 つずつ「名前.cnf」のファイルにして設置します(例:mysite.cnf・shop.cnf)。

  • パスワードをスクリプトやコマンドの中に書かずに済みます。コマンドに書くと、サーバーの中で動いているコマンドの一覧から見えてしまうことがあります。
  • ファイルの権限を 600(自分だけが読める)にして、ほかの人に読まれないようにできます。
[client]
user="example_user"
password="DB のパスワード"
host="mysql の接続先(サーバーパネルの MySQL 設定に書いてある名前)"
mkdir -p ~/bin ~/.backup
chmod 700 ~/.backup
nano ~/bin/backup-dump.sh      # 上のスクリプトを貼り付けて保存
chmod 700 ~/bin/backup-dump.sh
nano ~/.backup/mysite.cnf      # 上の接続の設定を貼り付けて保存
chmod 600 ~/.backup/mysite.cnf

手順 B:ラズパイに鍵とスクリプトを設置する

1. バックアップ専用の鍵を作る

ラズパイで、バックアップのためだけの鍵を作ります。表示された 1 行(公開鍵)をコピーしておきます。

mkdir -p ~/backup ~/.ssh
ssh-keygen -t ed25519 -f ~/.ssh/backup_ed25519 -N "" -C "raspi-backup"
cat ~/.ssh/backup_ed25519.pub

2. サーバーに鍵を登録する(この鍵でできることを絞る)

サーバーの ~/.ssh/authorized_keys の最後に、次の 1 行を足します。行の先頭に command= を付けると、この鍵でつないだときは手順 A のスクリプトしか動きません。

command="/home/サーバーのユーザー名/bin/backup-dump.sh",from="203.0.113.10",no-pty,no-port-forwarding,no-agent-forwarding,no-X11-forwarding ssh-ed25519 AAAA…(ラズパイで表示した公開鍵)… raspi-backup

ここだけ自分の環境に変える(鍵の 1 行)

  • /home/サーバーのユーザー名/:自分のサーバーのホームのフォルダ(サーバーのユーザー名は、エックスサーバーならサーバー ID。ラズパイの pi とは別)
  • from="203.0.113.10":自分の家のインターネットの IP アドレス(固定 IP でなければ from= の部分ごと外す)
  • ssh-ed25519 AAAA… の所:1. で表示された公開鍵をそのまま貼る

from= を付けると、その IP アドレスからしかつなげなくなります。家のインターネットが固定 IP でない場合、IP アドレスが変わった日から急にバックアップが取れなくなるので、付けないでください。あとから from= を外すときは、サーバーで次のように実行します。

cp ~/.ssh/authorized_keys ~/.ssh/authorized_keys.bak   # 先にバックアップ
sed -i 's/,from="[^"]*"//' ~/.ssh/authorized_keys      # from=… の部分だけを消す
grep raspi-backup ~/.ssh/authorized_keys               # from= が消えたかを見る

最後の行で表示された 1 行に from= がなければ完了です。ラズパイで手順 C の db_backup.sh mysite now をもう一度動かして、OK と出ることも確認します。

3. ラズパイからサーバーへのつなぎ方を書く

ラズパイの ~/.ssh/config に、次の 5 行を足します。スクリプトの中では backup-server という名前でつなぎます。

Host backup-server
    HostName example.xsrv.jp
    User サーバーのユーザー名
    Port 10022
    IdentityFile ~/.ssh/backup_ed25519

ここだけ自分の環境に変える(つなぎ方)

  • HostName:サーバーの SSH の接続先(エックスサーバーなら「サーバー ID.xsrv.jp」)
  • User:サーバーのユーザー名
  • Port:エックスサーバーは 10022。ほかのサーバーは 22 のことが多い

4. ラズパイにバックアップのスクリプトを設置する

ラズパイの ~/backup/db_backup.sh に、次のスクリプトを置きます。サーバーから受け取ったデータを NAS に書き、確認して、古いものを消します。

ここだけ自分の環境に変える(手順 B)

  • SERVER:3. で書いた Host の名前(そのままなら backup-server)
  • NAS_DIR:第 5 回でつないだ NAS の中の、バックアップを置くフォルダ
  • KEEP_DAYS・KEEP_MONTHS:毎日の分を残す日数と、各月の最初の 1 本を残す月数(家は 14 日と 12 か月)
#!/bin/bash
# ~/backup/db_backup.sh(ラズパイに置く)
# 使い方:~/backup/db_backup.sh <名前> [now|daily]
#   now   … 今すぐ 1 本取る(古いものは消さない)
#   daily … 毎晩の分。取ったあと、残す数の外のものを消す
set -uo pipefail
# ===== ここだけ自分の環境に変える =====
SERVER="backup-server"      # ~/.ssh/config の Host の名前
NAS_DIR="/mnt/nas/backup"   # 第 5 回でつないだ NAS の、バックアップを置くフォルダ
KEEP_DAYS=14                # 毎日の分を残す日数
KEEP_MONTHS=12              # 各月の最初の 1 本を残す月数
# =====================================
name="${1:-}"; kind="${2:-now}"
[ -n "$name" ] || { echo "使い方:$0 <名前> [now|daily]"; exit 2; }
LOG="$HOME/backup/backup.log"
log()  { echo "$(date '+%F %T')	$name	$kind	$*" >> "$LOG"; }
fail() { log "失敗:$1"; touch "$HOME/backup/FAILED"; exit 1; }
[ -d "$NAS_DIR" ] && [ -w "$NAS_DIR" ] || fail "NAS のフォルダに書けない($NAS_DIR)"
dir="$NAS_DIR/$name"
mkdir -p "$dir" || fail "フォルダを作れない($dir)"
file="$dir/${name}_$(date +%Y%m%d_%H%M).sql.gz"
# 1. サーバーで作ったバックアップを、NAS に .part の名前で書く
ssh -o BatchMode=yes "$SERVER" "dump $name" > "$file.part" 2>>"$LOG"
rc=$?
if [ $rc -ne 0 ]; then rm -f "$file.part"; fail "ダンプか転送が止まった(rc=$rc)"; fi
# 2. 壊れていないかを確認する(gzip として読める・最後の行に Dump completed がある)
if ! gzip -t "$file.part" 2>/dev/null || ! zcat "$file.part" | tail -n 1 | grep -q 'Dump completed'; then
  mv "$file.part" "$file.broken"; fail "確認で NG($file.broken)"
fi
# 3. 正式な名前にする
mv "$file.part" "$file"
log "OK $file size=$(du -h "$file" | cut -f1)"
rm -f "$HOME/backup/FAILED"
# 4. 毎晩の分のときだけ、残す数の外のものを消す
if [ "$kind" = daily ]; then
  keep_after=$(date -d "$KEEP_DAYS days ago" +%Y%m%d)
  oldest_month=$(date -d "$KEEP_MONTHS months ago" +%Y%m)
  seen=""
  for f in $(ls "$dir"/${name}_*.sql.gz 2>/dev/null | sort); do
    d=$(basename "$f" | sed -n "s/^${name}_\([0-9]\{8\}\)_.*/\1/p"); [ -n "$d" ] || continue
    m=${d:0:6}
    [ "$d" -ge "$keep_after" ] && continue            # 直近の日数の分は残す
    if [ "$m" != "$seen" ]; then                       # その月の最初の 1 本
      seen=$m
      [ "$m" -ge "$oldest_month" ] && continue         # 決めた月数のうちは残す
    fi
    rm -f "$f" && log "消した(残す数の外):$f"
  done
fi
exit 0

手順 C:手動で 1 回動かしてみる

ラズパイで次のコマンドを順に実行します。1 行目で、NAS の中にバックアップを置くフォルダを作ります。最初の ssh は、サーバーを覚えさせるための 1 回だけのものです。

mkdir -p /mnt/nas/backup         # 保存先のフォルダを作る(最初の 1 回だけ)
chmod 700 ~/backup/db_backup.sh
ssh backup-server                     # 初回だけ。「yes」と打つと「使えるのは dump <名前> だけ」と出て切れる
~/backup/db_backup.sh mysite now      # 手動で 1 回取る
tail -n 1 ~/backup/backup.log         # 最後の行が OK なら成功
ls -lh /mnt/nas/backup/mysite/

うまくいくと、ログの最後の行がこうなり、NAS に日付と時刻の名前のファイルができます。

2026-11-09 21:30:05	mysite	now	OK /mnt/nas/backup/mysite/mysite_20261109_2130.sql.gz size=12M

つまずきやすい所

  • 「Host key verification failed」:上の ssh backup-server を 1 回実行して yes と答えていない
  • 「NAS のフォルダに書けない」:NAS がつながっていない(第 5 回)か、/mnt/nas/backup を作っていない(手順 C の 1 行目)か、フォルダの名前が違う。ls /mnt/nas で見える?
  • 「知らない名前」:手順 A の db_of に、その名前を書いていない
  • ファイルの大きさが 0 か .broken になる:サーバーの .cnf のユーザー名・パスワード・接続先を見直す。NAS の空きも見る(df -h /mnt/nas)

手順 D:毎晩動くようにする

ラズパイで crontab -e を開いて、最後に次の 1 行を足します。毎晩 2 時 40 分に動きます。古いバックアップは、手順 B の 4 で決めた KEEP_DAYS(14 日)と KEEP_MONTHS(12 か月)に合わせて、スクリプトが自動で消します。

40 2 * * * /home/pi/backup/db_backup.sh mysite daily

複数のサイトをバックアップするとき

サイトが増えたら、まず手順 A の db_of にそのサイトの名前と DB の名前を足し、DB ごとに「名前.cnf」を設置します。そのうえで、crontab の書き方は次の 2 つから選びます。

書き方 1:1 行で順番に取る

1 行の中で for を使い、1 つ終わってから次のサイトへ進みます。同時に動かないので、サーバーに負担をかけず、時刻をずらして何行も書く必要もありません。迷ったら、こちらがおすすめです。

40 2 * * * for n in mysite shop blog; do /home/pi/backup/db_backup.sh $n daily; done

書き方 2:サイトごとに時刻をずらして何行も書く

サイトごとに取る時刻や回数を変えたいときの書き方です。前のサイトが終わる前に次が始まらないよう、時刻を 10 分ほどずつ空けます。

40 2 * * * /home/pi/backup/db_backup.sh mysite daily
50 2 * * * /home/pi/backup/db_backup.sh shop daily
0 3 * * * /home/pi/backup/db_backup.sh blog daily

自宅では、いちばん大事なサイトの DB を毎晩、ほかの WordPress のサイト 9 個を毎月 10 日に、書き方 1 の 1 行でまとめて取っています。古いバックアップは、サイトごとのフォルダの中で、それぞれ KEEP_DAYS と KEEP_MONTHS に合わせて消えます。

手動で取りたいとき(サイトの更新の前など)

cron の行のコマンドを、そのままラズパイで実行すれば取れます。ただし、最後を daily ではなく now にします。

~/backup/db_backup.sh mysite now                                  # 1 つのサイト
for n in mysite shop blog; do ~/backup/db_backup.sh $n now; done  # 複数のサイトをまとめて
  • now のときは、取るだけで、古いバックアップを消す処理は動きません。daily は毎晩の分なので、1 日に何回も動かすと、消す処理もそのたびに動きます。
  • 手動で取ったファイルも、毎晩の分と同じフォルダに、取った時刻の名前で入ります。あとの毎晩の分が動いたときに、KEEP_DAYS を過ぎたものから、ほかと同じ決まりで消えます。
  • PC からなら、ラズパイにつないで同じ 1 行を送るだけです(例:ssh mypi '~/backup/db_backup.sh mysite now')。第 4 回のように AI に「バックアップを取って」と頼むと、この 1 行を動かしてくれます。

こんな使い方もできます

自宅ではしていませんが、バックアップが終わったあとに、結果をメールで知らせるようにもできます。たとえば、毎朝「全部 OK」や「失敗したサイトがある」と届けば、ログを見に行かなくても様子が分かります。スクリプトの最後に、メールを送る 1 行を足すだけで作れるので、必要になったら AI に頼んでみてください。

次の日の朝に、ログを見て OK の行が増えていれば完了です。

tail -n 3 ~/backup/backup.log
ls ~/backup/FAILED 2>/dev/null && echo "失敗しています。backup.log を見てください"

セキュリティの考え方

1 つの鍵には 1 つの処理だけ

  • 手順 B の 2. の command= で、この鍵でつないだときは「決まった名前のバックアップを出す」ことしかできません。ほかのコマンドを送っても断られます。
  • 家のインターネットが固定 IP なら、from= で、つないでよい場所を自分の家だけに絞れます(現在わたしの家は固定 IP ではないので、外しています)。
  • DB のパスワードは、サーバーの .cnf(自分しか読めない 600)にだけ書き、スクリプトにもラズパイにも置きません。

実際にかかっている時間

業務のシステムのデータベースは、圧縮して約 0.40GB です。前は約 1.45GB あり約 27 分かかっていましたが、データベースを軽くして約 7 分になりました。

項目 時間
データベースを軽くする前(約 1.45GB) 約 27 分
軽くしたあと・ラズパイは Wi-Fi 約 8 分 30 秒〜9 分 40 秒
軽くしたあと・ラズパイを有線に 約 5 分 20 秒

ラズパイを有線にしたら、半分ほどの時間になりました(ラズパイを有線にしたら、バックアップの時間が半分に)。

分からないときは、このページを AI に渡してください

このページの URL を AI(Claude など)に渡して「この手順を手伝って」と頼めば、一緒に進めてくれます。頼み方の例です。

  • 「(このページの URL)の手順 C で、確認で NG と出た。backup.log はこうだった:(ログを貼る)」
  • 「(このページの URL)の手順 A の db_of に、shop という名前で example_shop_db を足した形に書き換えて」

まとめ

この回でできるようになったこと

  • ラズパイが毎晩、サーバーのデータベースのバックアップを NAS に保存する
  • 更新の前は ~/backup/db_backup.sh 名前 now の 1 つで、その場で取れる
  • 壊れていないかを確認してから名前を付け、残す数も自動で整える

管理画面からダウンロードする手間がなくなり、取り忘れもなくなりました。このラズパイには、ほかの処理もさせています。

次の回(第 7 回)では、画像やテーマなどのファイル(wp-content)のバックアップも自動にします。その次の第 8 回では、このバックアップを速く安定させるために、ラズパイを有線の LAN につなぎ替えます。

この記事を書いた人

フリースタイル

Web の仕事 25 年以上。WordPress の保守・引っ越しと、PHP の業務の仕組みづくり。 プロフィール →

まずは、お気軽にご相談ください。

相談と見積りは無料です。急ぎのときは、フォームで「急ぎ」を選んでください。