サーバーのバックアップと復元確認|pg_dump・tar・世代管理をClaude Codeと作る
pg_dumpとtarで毎日バックアップを取り、SHA-256と全体の読み通しで確かめ、cronで古い世代を消す手順を解説します。一時のDBに戻して全表の件数を比べる復元確認まで紹介します。
目次
この記事でわかること
- 自作アプリのサーバーで、何をバックアップすればよいか
- 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を実行し、最後まで読めるかを確かめるところから始めてみてください。
よくある質問
pg_dumpはアプリを止めずに取ってよいですか?
取れます。pg_dumpは始めた時点のスナップショットを取り、その間もほかの読み書きを止めません。ただし、表の構造を変える操作のように強いロックが必要な処理とは重なるので、そうした作業の時間とは分けます。
プレーンなSQL(-Fp)とカスタム形式(-Fc)のどちらがよいですか?
毎日のバックアップにはカスタム形式をおすすめします。既定で圧縮され、pg_restoreで中身の一覧を見たり、1つの表だけを戻したりできます。
ユーザー(ロール)やパスワードもバックアップに入りますか?
pg_dumpには入りません。ロールはDBの外にあるためで、取るならpg_dumpallの--globals-onlyを使います。検証では、アプリ用のロールで実行すると権限のエラーになり、--no-role-passwordsを付けるとパスワードを除いて取れました。
何日分残せばよいですか?
決まった正解はありません。気づくまでにかかる日数と、ディスクの空きで決めます。このサイトでは30日にしています。find -mtime +30は「31日以上前」を選ぶので、実際には31日目から消える点に注意してください。
復元の確認はどのくらいの頻度で行えばよいですか?
仕組みを作ったとき、スクリプトやDBの構成を変えたとき、そして定期的に行います。このサイトでは、戻して比べたのが仕組みを作ったときの1回だけだったので、月に1回などの頻度を決めて続けることをおすすめします。