ラズパイ 読む時間 約17分

ラズパイでコンテンツファイルのバックアップを完全自動化:WordPress の wp-content を毎週 NAS へ(個人情報のフォルダは外す)

WordPress の wp-content(画像・テーマ・プラグイン)を、ラズパイが毎週日曜に NAS へ完全自動でバックアップする手順です。個人情報が入るフォルダは tar の --exclude で外します。サーバーの自動のバックアップだけでよい人・この仕組みが向いている人の分け方も書きました(シリーズ第 7 回)。

ラズパイでコンテンツファイルのバックアップを完全自動化:WordPress の wp-content を毎週 NAS へ(個人情報のフォルダは外す)

第 6 回で、レンタルサーバーのデータベースを毎晩 NAS にバックアップするようにしました。ところが WordPress のサイトは、データベースだけでは元に戻りません。記事の画像・テーマ・プラグインは、wp-content というフォルダのファイルとして、サーバーに置かれているからです。

この回では、第 6 回と同じ形で、wp-content を毎週 1 回 NAS へ完全自動でバックアップします。あわせて、お問い合わせの添付など、個人情報が入るフォルダはバックアップから外す設定も書きます。

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

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

  • 毎週日曜の 3:10 に、サイトの wp-content が 1 つの tar.gz にまとまって NAS に保存される
  • 新しい 4 本(4 週分)を残し、古いものは自動で消える
  • 個人情報が入るフォルダや、作り直せるキャッシュは、バックアップに含めない

先に:この仕組みが要る人・要らない人

多くのレンタルサーバーには、自動のバックアップがあります。エックスサーバーなら、何もしなくてもファイルは 7 日分、データベースは 14 日分が保存されていて、サーバーパネルから戻せます(日数はサーバーパネルの「バックアップ」で見られます)。

この仕組みが向いている人

  • テーマやプラグインを git で管理していない(手動で直したファイルが、サーバーにしかない)
  • 7 日より前の状態に戻したいことがある
  • サーバーの会社の外(自宅)にも、バックアップを持っておきたい

サーバーの自動のバックアップだけでよい人

  • テーマやプラグインは git で管理していて、いつでも作り直せる
  • 戻したくなるのは、たいてい数日のうち
  • 個人情報が入るファイルを、自宅にはなるべく持ちたくない

わたしの家は「サーバーの自動のバックアップだけでよい人」のほうです。テーマとプラグインは git で管理していて、アップロードされたファイルには個人情報が入ることもあるので、ファイルはエックスサーバーの 7 日分に任せることにしました。この記事のために、家のラズパイで数サイトを実際に動かして数字を取ったあと、毎週の予定は外しています。

「この仕組みが向いている人」に当てはまる人は、このあとの手順で作ってください。

仕組みの流れ

第 6 回とほとんど同じです。違うのは、サーバーが出すものが「データベース」ではなく「wp-content の tar.gz」になることと、毎晩ではなく毎週にすることです。

レンタルサーバーが wp-content を tar.gz にまとめて出し(個人情報のフォルダ・cache・upgrade・ログは外す)、ラズパイが毎週日曜 3:10 にファイル専用の鍵で受け取ってそのまま NAS に書き、確認して保存する。NAS には新しい 4 本を残す。サーバーの自動のバックアップだけでよい人には不要
  1. ① ラズパイが、毎週日曜の 3:10 にサーバーへつなぐ(ファイル専用の鍵で)
  2. ② サーバーの側で、wp-content を tar.gz にまとめて出す(外すフォルダは除く)
  3. ③ ラズパイが受け取って、そのまま NAS に書く(ラズパイの SD カードには書かない)
  4. ④ 壊れていないかを確認してから、正式な名前を付け、新しい 4 本より古いものを消す

含めるもの・外すもの

含めるもの(wp-content の下)

  • uploads(記事の画像など)
  • themes(テーマ)
  • plugins(プラグイン)

外すもの(--exclude で指定)

  • 個人情報が入るフォルダ(例:Contact Form 7 の添付の一時置き場 uploads/wpcf7_uploads)
  • キャッシュ(cache)・更新の一時置き場(upgrade):作り直せる
  • ログ(*.log)

個人情報が入るフォルダは、使っているプラグインで変わります。会員の添付・注文の書類・応募のファイルなどを受け付けているなら、そのフォルダの名前を調べて、外すものに足します。分からないときは、サーバーで ls ~/example.com/public_html/wp-content/uploads を実行して、年月(2026 など)でないフォルダを見てみてください。

手順 A:サーバーに wp-content を出すスクリプトを設置する

第 6 回の手順 A と同じように、PC から SSH でサーバーにつないで、次のスクリプトを ~/bin/backup-files.sh に設置します。ラズパイから「files 名前」と呼ばれたときだけ、決めたサイトの wp-content を出します。

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

  • dir_of の中の「mysite」と「$HOME/example.com/public_html/wp-content」:自分で決める名前と、そのサイトの wp-content の場所(エックスサーバーなら「ホーム/ドメイン/public_html/wp-content」)
  • EXCLUDES:外すフォルダ。個人情報が入るフォルダがあれば、同じ形で 1 行ずつ足す
#!/bin/bash
# ~/bin/backup-files.sh(レンタルサーバーに置く)
# ラズパイから「files 名前」と呼ばれたときだけ、wp-content を tar.gz にして出す
set -euo pipefail
umask 077
# ===== ここだけ自分の環境に変える =====
# 名前と、その名前で取る wp-content のフォルダ(何行でも足せる)
dir_of() {
  case "$1" in
    mysite)  echo "$HOME/example.com/public_html/wp-content" ;;
    # blog)  echo "$HOME/blog.example.com/public_html/wp-content" ;;
    *)       return 1 ;;
  esac
}
# 含めないもの:個人情報が入るもの・作り直せるもの(wp-content からの場所で書く)
EXCLUDES=(
  --exclude='wp-content/uploads/wpcf7_uploads'   # Contact Form 7 の添付(一時置き場)
  --exclude='wp-content/cache'                   # キャッシュ(作り直せる)
  --exclude='wp-content/upgrade'                 # 更新の一時置き場
  --exclude='*.log'
)
# =====================================
read -r cmd name extra <<< "${SSH_ORIGINAL_COMMAND:-}"
if [ "${cmd:-}" != "files" ] || [ -z "${name:-}" ] || [ -n "${extra:-}" ]; then
  echo "使えるのは files <名前> だけ" >&2; exit 2
fi
src=$(dir_of "$name") || { echo "知らない名前:$name" >&2; exit 2; }
[ -d "$src" ] || { echo "$src がない" >&2; exit 3; }
cd "$(dirname "$src")"
# 取っているあいだにファイルが変わると、tar は 1 を返す(バックアップは最後まで作られる)。ラズパイの側で 1 は受け入れる
nice -n 15 tar czf - "${EXCLUDES[@]}" wp-content
nano ~/bin/backup-files.sh      # 上のスクリプトを貼り付けて保存
chmod 700 ~/bin/backup-files.sh

サーバーには、バックアップのファイルを作りません。tar がまとめたデータを、そのままラズパイに流します。nice -n 15 で、サイトの表示より後回しにして動かすので、サイトが重くなりにくくなります。

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

1. ファイル専用の鍵を作る

第 6 回の鍵(データベース用)とは別に、ファイルのためだけの鍵を作ります。1 つの鍵には 1 つの処理だけをさせておくと、どちらかの鍵がもし漏れても、できることが限られます。

ssh-keygen -t ed25519 -f ~/.ssh/backup_files_ed25519 -N "" -C "raspi-backup-files"
cat ~/.ssh/backup_files_ed25519.pub

2. サーバーに鍵を登録する

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

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

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

ラズパイの ~/.ssh/config に、次の 5 行を足します。第 6 回の backup-server と、鍵(IdentityFile)と名前だけが違います。

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

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

ラズパイの ~/backup/files_backup.sh に、次のスクリプトを置きます。

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

  • SERVER:3. で書いた Host の名前(そのままなら backup-files)
  • NAS_DIR:第 6 回と同じ、NAS のバックアップを置くフォルダ
  • KEEP:残す数。毎週 1 本なので、4 なら 4 週分
#!/bin/bash
# ~/backup/files_backup.sh(ラズパイに置く)
# 使い方:~/backup/files_backup.sh <名前> [now|weekly]
#   now    … 今すぐ 1 本取る(名前は files_now_…。古いものを消す処理と「新しい KEEP 本」の数の外)
#   weekly … 毎週の分。取ったあと、新しい KEEP 本より古いものを消す
set -uo pipefail
# ===== ここだけ自分の環境に変える =====
SERVER="backup-files"       # ~/.ssh/config の Host の名前
NAS_DIR="/mnt/nas/backup"   # 第 5 回でつないだ NAS の、バックアップを置くフォルダ
KEEP=4                      # 残す数(毎週 1 本なら 4 週分)
# =====================================
name="${1:-}"; kind="${2:-now}"
[ -n "$name" ] || { echo "使い方:$0 <名前> [now|weekly]"; exit 2; }
LOG="$HOME/backup/backup.log"
log()  { echo "$(date '+%F %T')	$name	files	$*" >> "$LOG"; }
# 失敗の記録は、第 6 回のデータベースの FAILED とは別の名前にする(片方の成功で、もう片方の失敗が消えないように)
FAILED="$HOME/backup/FAILED_FILES"
fail() { log "失敗:$1"; touch "$FAILED"; exit 1; }
[ -d "$NAS_DIR" ] && [ -w "$NAS_DIR" ] || fail "NAS のフォルダに書けない($NAS_DIR)"
dir="$NAS_DIR/$name"
mkdir -p "$dir" || fail "フォルダを作れない($dir)"
if [ "$kind" = weekly ]; then prefix="files_"; else prefix="files_now_"; fi
file="$dir/${prefix}$(date +%Y%m%d_%H%M).tar.gz"
start=$(date +%s)
# 1. サーバーで作った tar.gz を、NAS に .part の名前で書く
ssh -o BatchMode=yes -o ServerAliveInterval=30 "$SERVER" "files $name" > "$file.part" 2>>"$LOG"
rc=$?
# tar の 1(取っているあいだにファイルが変わった)は受け入れて、下の確認で決める
if [ $rc -ne 0 ] && [ $rc -ne 1 ]; then rm -f "$file.part"; fail "送る途中で止まった(rc=$rc)"; fi
# 2. 壊れていないかを確認する(gzip として読める・中にファイルが並んでいる)
count=$(tar tzf "$file.part" 2>/dev/null | wc -l)
if ! gzip -t "$file.part" 2>/dev/null || [ "$count" -lt 10 ]; then
  mv "$file.part" "$file.broken"; fail "確認で NG(ファイル数 $count・$file.broken)"
fi
# 3. 正式な名前にする
mv "$file.part" "$file"
log "OK $file size=$(du -h "$file" | cut -f1) files=$count $(( $(date +%s) - start ))s"
rm -f "$FAILED"
# 4. 毎週の分のときだけ、新しい KEEP 本より古いものを消す
if [ "$kind" = weekly ]; then
  ls -1 "$dir"/files_[0-9]*.tar.gz 2>/dev/null | sort | head -n -"$KEEP" | while read -r f; do
    rm -f "$f" && log "消した(残す数の外):$f"
  done
fi

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

chmod 700 ~/backup/files_backup.sh
ssh backup-files                       # 初回だけ。yes を聞かれたら yes と打つ。「使えるのは files <名前> だけ」と出て切れれば OK
~/backup/files_backup.sh mysite now    # 手動で 1 回取る
tail -n 1 ~/backup/backup.log          # 最後の行が OK なら成功

うまくいくと、ログの最後の行がこうなります。下の表の参考サイト B で取ったときの数字です。

2026-10-28 11:00:07	mysite	files	OK /mnt/nas/backup/mysite/files_20261028_1055.tar.gz size=526M files=17752 280s

files= は、tar.gz の中に並んでいるファイルとフォルダの数です。10 より少ないときは、場所のまちがいなどで空に近いバックアップになっているので、失敗として扱います。

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

crontab -e を開いて、最後に次の 1 行を足します。毎週日曜の 3:10 に、サイトを 1 つずつ順に取ります。第 6 回のデータベース(毎晩 2:40)が終わったあとの時刻にしています。

10 3 * * 0 for n in mysite blog; do /home/pi/backup/files_backup.sh $n weekly; done
  • 最後の weekly のときだけ、取ったあとに新しい 4 本より古いものを消します。サイトの更新の前などに手動で取るときは now にします。名前が files_now_ で始まり、古いものを消す処理と「新しい 4 本」の数の外になります(いらなくなったら手動で消します)。
  • 失敗したときは、~/backup/FAILED_FILES のファイルができます。第 6 回のデータベースの FAILED とは別の名前にして、片方の成功で、もう片方の失敗の記録が消えないようにしています。古いものを消す処理は成功したときしか動かないので、失敗が続いても、手元のよいバックアップは減りません。

バックアップから復元する

1 つのファイルだけ取り出す

消してしまった画像を 1 枚だけ戻したい、というときは、そのファイルだけをラズパイの /tmp に取り出します。

ls /mnt/nas/backup/mysite/
tar tzf /mnt/nas/backup/mysite/files_20261115_0310.tar.gz | grep photo.jpg
mkdir -p /tmp/restore && cd /tmp/restore
tar xzf /mnt/nas/backup/mysite/files_20261115_0310.tar.gz wp-content/uploads/2026/11/photo.jpg

取り出したファイルは、PC にコピーして、サーバーパネルのファイル管理や FTP でサーバーの同じ場所に戻します(ファイル専用の鍵は「出す」ことしかできないので、ラズパイから直接サーバーに書き戻すことはできません)。

サイトをまるごと戻す

テーマやプラグインまで全部を戻すときも、同じ tar.gz を PC で展開して、wp-content をサーバーに上げ直します。データベースは、第 6 回の同じ日付のバックアップを使います。ファイルとデータベースの日付がずれると、記事にはあるのに画像がない、ということが起きるので、なるべく近い日付のものをそろえます。

つまずきやすい所

  • ログに「tar: .: file changed as we read it」と出る:取っているあいだに、サイトのファイルが変わったという知らせです。家でも、下の表の参考サイト B(10/28)と参考サイト C(11/10)で出ました。tar はこのとき 1 を返しますが、バックアップは最後まで作られているので、スクリプトは 1 を受け入れて、その後の確認(gzip として読めるか・ファイルの数)で決めます。
  • 「知らない名前」:手順 A の dir_of に、その名前を書いていない
  • .broken や .part のファイルが残っている:確認で NG になったものや、途中で止まったものです。古いものを消す処理の対象の外なので、原因を見たら手動で消します(大きいサイトは 1 本で数 GB あります)。
  • NAS がいっぱいになる:1 サイト × 4 本の大きさが要ります。df -h /mnt/nas で空きを見て、足りなければ KEEP を減らします。
  • とても時間がかかる:画像の多いサイトは数 GB になります。下の表の参考サイト C は 約 2.1GB ありました。大きいサイトは、NAS の上で壊れていないかを確認する時間も長くなります(NAS の性能で変わります)。日曜の夜中に動かすのはこのためです。

実際の大きさと時間

サイト 大きさ・ファイル数・時間
参考サイト A(記事が少ない) 66MB・9,233 ファイル・43 秒
参考サイト B(記事と画像が中くらい) 526MB・17,752 ファイル・280 秒
参考サイト C(画像が多い) 約 2.1GB・14,621 ファイル・約 18 分(受け取り約 7 分+確認約 11 分)

データベース(第 6 回)が数 MB〜数百 MB なのに対して、ファイルはサイトによって 1 けたから 2 けた大きくなります。毎晩ではなく毎週にしているのは、このためです。

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

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

  • 「(このページの URL)の EXCLUDES に、WooCommerce の注文の書類のフォルダも足したい」
  • 「(このページの URL)の手順 C で、確認で NG と出た。backup.log はこうだった:(ログを貼る)」

まとめ

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

  • 毎週日曜の 3:10 に、wp-content が NAS に保存され、4 週分が残る
  • 個人情報が入るフォルダとキャッシュは、--exclude でバックアップから外せる
  • 第 6 回のデータベースと合わせて、サイトを丸ごと戻せる

git で管理していない・7 日より前に戻したいことがある、という人は、この仕組みを使ってください。そうでなければ、サーバーの自動のバックアップだけでよいことも多いです。わたしの家は、ファイルはエックスサーバーの 7 日分に任せ、データベースは第 6 回の仕組みでラズパイでも取っています。

次に読む

この記事を書いた人

フリースタイル

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

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

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