この記事でわかること

  • 自作アプリのサーバーで、何をバックアップすればよいか
  • pg_dumpとtarで取り、取った直後に「読めること」を確かめる方法
  • cronで毎日取り、古い世代を安全に消す方法
  • 一時のDBに戻して、全表の件数を元と比べる「復元の確認」

バックアップは、取れていても戻せなければ意味がありません。この記事では、取るだけで終わらせず、戻して確かめるところまでを1つの型にします。DBとアプリ用のロールの作り方はAlmaLinuxでPostgreSQLを構築しアプリ用のユーザーとDBを作る手順で扱いました。

確かめた環境と、確かめていないこと

コマンドの動きは、2026年10月に作業PCの一時の環境でPostgreSQL 16.15を127.0.0.1だけで起動し、架空のDB(3つの表に合計1,350行)で確かめました。sha256sum・tar・findは、GNUのものを使っています。

一方で、cronへの登録と、本番のサーバーでの復元は、このサイトの運用の記録に基づいていて、今回の検証では実行していません。

何を取るかを決める

最初に、失うと戻せないものを決めます。コードはGitから戻せるので、毎日のバックアップには入れません。

対象 取り方 入れない理由・注意
DBの全体 pg_dump(カスタム形式) ロールは入らない
Gitにないファイル(投稿の記録・設定など) tar 一時のファイルは除く
crontabの内容 crontab -l の出力 変更したときに戻すため
.envなどの認証情報 取らない 複製を増やさない

このサイトでも、DBの全体・Gitにないファイル・crontabの控えを毎日取り、SHA-256の一覧を付けています。.envなどの認証情報は、バックアップに入れていません。

pg_dumpをカスタム形式で取る

pg_dumpは、始めた時点のスナップショットを取り、その間もほかの読み書きを止めません。カスタム形式(-Fc)は既定で圧縮され、pg_restoreで中身の一覧を見たり、表を選んで戻したりできます。

バックアップは、まず一時のフォルダに作ります。確かめ終わってから正式な名前に変えれば、途中で失敗した作りかけが、正式なバックアップに見えることはありません。

【用途】一時のフォルダにDBのダンプを取る /【実行場所】AlmaLinuxのサーバー /【状態変更】あり(ファイルを作る)

mkdir -p /home/your-user/backups/myapp/.tmp_20261012_034000
pg_dump -Fc -d appdb -U appuser -h 127.0.0.1 -f /home/your-user/backups/myapp/.tmp_20261012_034000/db.dump

パスワードは、コマンドやスクリプトに直接書きません。このサイトでは、アプリの.envから読んだ値を、実行中だけ環境変数で渡しています。検証では、CREATEDBやCREATEROLEを持たないアプリ用のロールでも、自分が所有するDBのダンプを取れました。

Gitにないファイルをtarでまとめる

Gitにないファイルは、tarで1つにまとめます。一時のファイルと、認証情報のファイルは除きます。

【用途】ファイルをまとめる /【実行場所】アプリのディレクトリ /【状態変更】あり(アーカイブを作る)

cd /home/your-user/apps/myapp
tar -czf /home/your-user/backups/myapp/.tmp_20261012_034000/files.tar.gz --exclude='.env*' --exclude='content/_work' content config

--excludeのパターンは、取りたいものまで除いていないかを、次の節の一覧で確かめます。

取った直後に確かめる

取ったら、すぐに次の3つを確かめます。どれも読み取りだけで、DBやファイルは変えません。

【用途】SHA-256の一覧を作る /【実行場所】AlmaLinuxのサーバー /【状態変更】あり(一覧のファイルを作る)

cd /home/your-user/backups/myapp/.tmp_20261012_034000
sha256sum db.dump files.tar.gz > SHA256SUMS
sha256sum -c SHA256SUMS

sha256sum -cは、一致するとdb.dump: OKと表示します。検証で1バイトだけ変えたファイルではdb.dump: FAILEDと警告が出て、終了コードは1になりました。後で戻す前にも、同じ照合をします。

【用途】ダンプを最後まで読めるかの確認 /【実行場所】AlmaLinuxのサーバー /【状態変更】なし(読んで表示するだけ)

pg_restore --list db.dump | grep -c "TABLE DATA"
pg_restore -f /dev/null db.dump

1行目は表のデータの数を数え、2行目はダンプを最後まで読み通します。どちらもDBには何も書き込みません。

ここで大事なのが2行目です。検証では、途中で切れたダンプでもpg_restore --listは成功し、全体を読み通したときに初めて次の表示で失敗しました。一覧が出ることは、ダンプが最後までそろっていることの確認になりません。

pg_restore: error: could not read from input file: end of file

【用途】アーカイブの中身と、秘密のファイルが無いことの確認 /【実行場所】AlmaLinuxのサーバー /【状態変更】なし(一覧を表示するだけ)

tar -tzf files.tar.gz
tar -tzf files.tar.gz | grep -E '(^|/)([.]env[^/]*|secrets)(/|$)'

2行目が何も表示しなければ、.envやsecretsは入っていません。検証では、除外を付けずに作ったアーカイブで試すと、このgrepが.envとsecrets/を見つけました。

3つとも問題がなければ、一時のフォルダを正式な名前に変えます。問題があれば一時のフォルダを残さず消し、前の日のバックアップをそのまま使います。

【用途】確かめたバックアップを正式な名前にする /【実行場所】AlmaLinuxのサーバー /【状態変更】あり(フォルダの名前を変える)

mv /home/your-user/backups/myapp/.tmp_20261012_034000 /home/your-user/backups/myapp/myapp_20261012_034000

cronで毎日取り、古い世代を消す

ここまでの手順を1つのスクリプトにまとめ、cronで毎日実行します。このサイトでは、ほかの定期処理と重ならない03:40に動かしています。crontabを変える前には、今の内容の控えを取ります。

【用途】今のcrontabを書き出しておく /【実行場所】AlmaLinuxのサーバー /【状態変更】あり(テキストのファイルを1つ作る)

crontab -l > /home/your-user/crontab_before.txt

【用途】毎日のバックアップを登録する(crontab -eで1行足す) /【実行場所】AlmaLinuxのサーバー /【状態変更】あり(crontabを変更する)

40 3 * * * /home/your-user/apps/myapp/backup.sh >> /home/your-user/apps/myapp/logs/backup_cron.log 2>&1

登録した後はcrontab -lで1行だけ増えたことを確かめ、翌朝にログと新しいバックアップを確認します。間違えたときは、控えをcrontab /home/your-user/crontab_before.txtで読み込めば元に戻ります。

古い世代は、findで選んでから消します。いきなり消さず、まず何が選ばれるかを表示します。

【用途】消す対象の確認 /【実行場所】AlmaLinuxのサーバー /【状態変更】なし(選ばれるものを表示するだけ)

find /home/your-user/backups/myapp -mindepth 1 -maxdepth 1 -type d -name 'myapp_[0-9]*_[0-9]*' -mtime +30 -print

-mtime +30は端数を切り捨てて数えるので、31日以上前のものだけが選ばれます。検証では、29.5・30.5・31.5・32.5日前のフォルダのうち、31.5日前と32.5日前だけが選ばれました。名前の形を指定しているので、ほかのフォルダは対象になりません。表示を確かめてから、スクリプトの中で同じ条件のものだけを消します。

注意したいのは、作業の前に手で取ったバックアップも同じ場所に置くと、30日を過ぎたときに一緒に消えることです。長く残したい控えは、別のフォルダに置きます。

一時のDBに戻して件数を比べる

ここからが復元の確認です。本番のDBには戻さず、確認用の一時のDBを作って戻します。一時のDBを作るにはCREATEDBの権限が要るので、作成と削除は管理者(postgres)で行い、戻す作業はアプリ用のロールで行います。

まず、戻すバックアップのフォルダでsha256sum -c SHA256SUMSを実行し、壊れていないことを確かめてから始めます。

【用途】確認用の一時のDBを作る /【実行場所】サーバー(sudoの使える管理者がpostgresに切り替える) /【状態変更】あり(空のDBを作る)

sudo su - postgres
createdb -T template0 -O appuser restore_check
exit

【用途】一時のDBに戻す /【実行場所】AlmaLinuxのサーバー /【状態変更】あり(一時のDBに書き込む)

pg_restore -h 127.0.0.1 -U appuser --no-owner -d restore_check db.dump
echo $?

echo $?が0なら、エラーなく戻せています。検証では、所有者をアプリ用のロールにした一時のDBへ、そのロールで--no-ownerを付けて戻すと、終了コード0で戻りました。

次に、元のDBと一時のDBで、全部の表の件数を同じSQLで出して比べます。

【用途】全部の表の件数を出す(元のDBと一時のDBの両方で) /【実行場所】psqlにappuserで接続 /【状態変更】なし(数えるだけ)

SELECT table_name,
       (xpath('/row/c/text()', query_to_xml(format('SELECT count(*) AS c FROM %I.%I', table_schema, table_name), false, true, '')))[1]::text::bigint AS rows
FROM information_schema.tables
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
ORDER BY table_name;

検証では、3つの表の件数が元と一致しました。さらに、行の中身から作ったハッシュも一致しました。件数が違えば、そのバックアップには頼らず、取り方を見直します。

確かめ終わったら、一時のDBを消します。名前がrestore_checkであることを確かめてから実行し、本番のDBの名前を指定しないようにします。

【用途】確認用の一時のDBを消す /【実行場所】サーバー(sudoの使える管理者がpostgresに切り替える) /【状態変更】あり(一時のDBを削除する)

sudo su - postgres
dropdb restore_check
exit

一時のDBを消しても、バックアップのファイルと本番のDBは変わりません。

復元のときのつまずき

検証で確かめた、戻すときの注意です。

  • 所有者のロールが無い環境に--no-ownerなしで戻すと、既定ではエラーを1件出したまま続け、データは戻っても終了コードは1でした。最後のerrors ignored on restoreの行と終了コードを必ず見ます
  • -eを付けると、最初のエラーで止まります。検証では、表を作った直後の所有者の変更で止まり、表は空のままでした
  • pg_restore -lで一覧を出し、1つの表の行だけを残して-Lで渡すと、その表だけを戻せました
  • ロールはpg_dumpに入りません。サーバーを作り直すときは、先にロールを作ってから戻します
  • 戻した後はANALYZEを実行して、問い合わせに使う統計を作り直します
  • pg_dumpの出力は、新しい版のPostgreSQLへ読み込めます。古い版への読み込みは保証されません

このサイトで実際にしていること

このサイトでは、毎日03:40にDBのダンプ・ファイルのtar・crontabの控えを取り、SHA-256の一覧を付けています。一時のフォルダで作り、確かめてから正式な名前にするので、作りかけは残りません。2026年10月3日から12日までの10日間、毎日のバックアップはすべて成功し、作業の前の手動のバックアップと合わせて43世代、合計約19MBになりました。

振り返ると、実際に一時のDBへ戻して全表の件数を比べたのは、仕組みを作ったときの1回だけでした。そのときは全18表の件数が本番と一致しました。毎日の確認は「一覧が読めること」までで、上で書いたとおり、それだけでは途中で切れたダンプを見分けられません。

また、このサイトのバックアップは同じサーバーのディスクにだけあり、別の機械へのコピーはありません。ディスクやサーバーそのものの故障には備えられないので、ダンプを定期的に別の機械へ移すことも検討してください。

Claude Codeに任せる範囲の判断

スクリプトの作成や、取ったバックアップの確認は、Claude Codeに任せやすい作業です。一方で、本番のDBへの復元・古いバックアップの削除・crontabの変更は、間違えると戻せません。まず読み取りだけを頼み、結果を見てから自分で判断します。読み取りの考え方はサーバーの状態確認をClaude Codeに読み取り専用で任せる方法にまとめています。

【用途】最新のバックアップを読み取りだけで点検してもらう /【実行場所】Claude Codeへの依頼 /【状態変更】なし(点検の結果を報告させ、対応は自分で決める)

/home/your-user/backups/myapp にある最新のバックアップを、読み取りだけで点検してください。
- そのフォルダで sha256sum -c SHA256SUMS の結果
- pg_restore --list の TABLE DATA の数と、pg_restore -f /dev/null が成功するか
- tar -tzf files.tar.gz に .env や secrets が入っていないか
- フォルダの数と、一番古いものの日付
DBへの復元・ファイルの削除・crontabの変更はしないでください。
結果と、気になる点があれば対応の案を報告して、私の確認を待ってください。

棚卸しから毎日の運用・一時のDBへ戻して比べるまでの依頼文とチェックリストを、有料の記事「Claude Code バックアップと復元確認の実践セット」にまとめています。

.envの中身やパスワードは、Claude Codeへの依頼文やチャットに書きません。サーバー全体の作業の流れはClaude CodeでAlmaLinuxサーバーを構築・運用する方法を参考にしてください。

まとめ

  • DBの全体・Gitにないファイル・crontabを取り、認証情報は入れない
  • 一時のフォルダで作り、SHA-256・全体の読み通し・tarの一覧で確かめてから正式な名前にする
  • pg_restore --listだけでは、途中で切れたダンプを見分けられない
  • 古い世代はfindで選んでから消す。-mtime +30は31日以上前
  • 一時のDBに戻して全表の件数を比べ、終わったら一時のDBを消す

まずは今あるバックアップでpg_restore -f /dev/nullを実行し、最後まで読めるかを確かめるところから始めてみてください。