TL;DR
- 稼働中の n8n の
.n8nディレクトリをtarで固めたバックアップから復旧したら、SQLITE_CORRUPT: malformed database schema (sqlite_autoindex_binary_data_1) - orphan indexで起動ループになった -
.recoverも.dumpも通らず、SQLiteの修復コマンドは全滅 - 最終的に効いたのは修復ではなく、「壊れていない世代を探して入れ直す」ことだった
- 破損DBの修復に時間を使う前に、
findでDBファイルを全部洗い出すほうが速い
1. 背景:なぜ環境を作り直したのか
社内の自動化基盤として、余っていた Windows 11 Pro のミニPCで、n8n(Node.jsベース)と Skyvia Agent(.NETベース)の2つを常時稼働させていました。これが1週間ほど動かすと、メモリが詰まってフリーズするようになりました。
⚠️ 誤解のないように書いておくと、「メモリリークである」と計測して確かめたわけではありません。Windows本体のフットプリント、常駐する2つのランタイム、そして後述するSQLiteの肥大化が積み上がった結果、16GBが足りなくなっていた、というのが実態に近いです。
そこで、ツールを乗り換えるのではなく実行基盤を作り直す方針にしました。
[変更前] Windows 11 Pro
├─ n8n(Windows上のプロセス)
└─ Skyvia Agent(Windows上のプロセス)
↑ どちらかが暴れると全部巻き込んでフリーズ
[変更後] Ubuntu Server 24.04 LTS
├─ Docker ─ n8n
└─ KVM ─ Windows 11 VM ─ Skyvia Agent
↑ Windows側が固まっても、n8nは無傷。VMだけ再起動すればいい
Skyvia Agent をわざわざVMに閉じ込めているのは、Agent の Linux 版がまだ提供されていないためです(2026年8月時点で公式サイトのLinux/macOS版は "Coming soon")。影響範囲をVMに封じ込める方向で妥協しました。
本記事はこのうち n8n のデータ移行でハマった部分だけを扱います。KVM側のネットワーク(NAT配下のVMに外から繋ぐ話)は後編で扱います。
2. 事件の始まり:docker load が通らない
旧環境から持ち出したバックアップが n8n_backup.tar.gz という名前だったので、Dockerイメージのつもりで読み込もうとしました。gzipを解いても同じでした。
$ docker load -i ./n8n_backup.tar.gz
unrecognized image format
中身を見ます。
$ tar -tvf ./n8n_backup.tar | head
./database.sqlite
./database.sqlite-wal ← WALファイル
./database.sqlite-shm ← 共有メモリファイル
./config
./binaryData/
./nodes/package.json
Dockerイメージではなく、.n8n ディレクトリのバックアップでした。 docker load が読めるのは docker save の出力だけなので、当然です。
| tarの中身 | 正体 | 使うコマンド |
|---|---|---|
manifest.json、ハッシュ名のディレクトリ、layer.tar
|
Dockerイメージ | docker load -i |
| コンテナのファイルシステムそのもの |
docker export の出力 |
docker import |
database.sqlite、config、binaryData/
|
n8nのデータ | 展開してバインドマウント |
そして、この出力にはもう一つ重要な情報が写り込んでいます。-wal と -shm が同梱されている=n8nを止めずにバックアップを取っているということです。これが後の破損に効いてきます。
3. compose の書き換えで踏んだ小さな穴
データを展開して起動しようとしたら、composeが弾いてきました。
$ docker compose up -d
service "n8n" refers to undefined volume n8n-data: invalid compose project
docker-compose.yml のYAMLが崩れており、named volume を参照しているのにトップレベルの volumes: 定義が壊れていました。
# NG
volumes:
- n8n-data:/home/node/.n8nvolumes: # ← 改行が消えて結合している
n8n-data:
driver: local~ # ← 余計な文字
今回はtarから展開したディレクトリをそのまま読ませたいので、named volume をやめてバインドマウントに統一します。あわせて所有権も合わせます。n8n公式イメージは node ユーザー(UID/GID 1000)で動くためです。
volumes:
- ./n8n_data:/home/node/.n8n # ← 展開先ディレクトリを直接マウント
sudo chown -R 1000:1000 ./n8n_data
⚠️
chmod -R 775を反射で打たないこと。 私はこの後うまくいかない焦りから打ってしまいましたが、必要だったのはchownだけでした。パーミッションの緩和は原因究明ではなく、原因を見えにくくする行為です。
4. 起動した。でもワークフローが1件も無い
コンテナは上がり、ブラウザで http://<HOST_IP>:5678 を開くと……アカウント新規登録画面が出ました。復旧できていれば旧環境のログイン画面が出るはずです。N8N_ENCRYPTION_KEY を .env に入れて再起動しても変わりません。
コマンド履歴を見返して分かりました。
69 git clone git@github.com:<ORG>/n8n-compose.git
70 cd n8n-compose/
71 docker compose up -d # ← ここ!バックアップを展開する前に起動している
...
84 tar -xvf ./n8n_backup.tar -C ~/restore/n8n_data/ # ← 本物はこっちに展開した
71行目で、リポジトリのcomposeがそのまま起動し、空のDBを自動生成していました。
~/restore/n8n_data/ ← 本物のバックアップ(84行目で展開)
~/work/n8n-compose/n8n_data/ ← 空のDB(71行目で自動生成)※composeが見ているのはこっち
n8nは「DBが無ければ黙って新規作成する」ので、間違ったディレクトリを見ていても、エラーではなく「まっさらな画面」として現れます。これが一番タチが悪いところでした。
💡
git clone直後に何も考えずdocker compose up -dしない。 バインドマウント先が空だと、その瞬間に初期化済みの状態が作られます。
5. 本命の事故:SQLITE_CORRUPT: orphan index
本物のデータを正しいディレクトリにコピーして起動したところ、今度はコンテナが起動ループに入りました。
n8n-1 | Failed to load Custom API options for the node "n8n-nodes-base.confluence": Unknown credential name "confluenceCloudOAuth2Api"
n8n-1 | Initial database connection attempt 1 failed: SQLITE_CORRUPT: malformed database schema (sqlite_autoindex_binary_data_1) - orphan index. Retrying in 1000ms
n8n-1 | (5回リトライして)There was an error initializing DB
n8n-1 | --> in Database#run('PRAGMA journal_mode = WAL', [Function (anonymous)])
n8n-1 | n8n-1 exited with code 1 (restarting)
binary_data テーブルの自動インデックスが本体を失った「孤児インデックス」状態です。PRAGMA journal_mode = WAL を投げた瞬間にスキーマ読み込みが死ぬので、アプリからは手も足も出ません。
原因は明確で、n8nを稼働させたまま .n8n を tar で固め、さらに cp -r で上書きコピーしたためです。SQLiteはWALモードで動いており、database.sqlite 本体・-wal・-shm の3ファイルが整合していないと壊れます。3つを別々のタイミングでコピーすれば、当然こうなります。
修復コマンドは全滅した
| 試したこと | 結果 |
|---|---|
sqlite3 database.sqlite .recover |
sql error: no such table: sqlite_dbpage (1) — .recover は sqlite_dbpage 仮想テーブルに依存するが、Ubuntu 24.04 標準の sqlite3 では使えない |
sqlite3 database.sqlite .dump |
malformed database schema ... orphan index — .dump も最初にスキーマ全体を読むので同じ場所で死ぬ |
テーブルを指定して .dump
|
同上。テーブルを絞ってもスキーマ読み込みは全体に走る |
唯一効いたのは strings での生サルベージ
SQLiteとしてではなく、ただのバイト列としてJSONを引っこ抜きます。
sudo apt install -y binutils # Command 'strings' not found と言われたらこれ
strings database.sqlite | grep -E '"nodes":\[.*\]' > recovered_raw.txt
取り出したJSONは、n8nのキャンバス上で Ctrl+V(Mac は Cmd+V)するだけでノード群として復元できます。ただしn8nが受け付けるのは nodes と connections を持つオブジェクトなので、拾った断片を1ワークフロー分ずつ {"nodes": [...], "connections": {...}} の形に整えてから貼ってください。connections が拾えなければ {} で構いません。ノードは全部並ぶので、線は引き直せます。
インポート機能を使わずに済むのが利点です。 CLIの export:workflow --all は、古いノードが1つ混ざっているだけで全件が出せなくなります(冒頭のログにある Unknown credential name がそれです)。import:workflow にも罠があり、
$ docker compose exec n8n n8n import:workflow --input=/home/node/.n8n/extracted_workflows.json
ENOENT: no such file or directory, open '/home/node/.n8n/extracted_workflows.json'
--output / --input に渡すのはコンテナ内のパスです。ホスト側の ~/work/n8n-compose/n8n_data/ に出力したファイルを ~ 付きで参照しても見つかりません。
結論:ノードのバージョン互換で詰まるなら、CLIよりブラウザUIの Import from File / キャンバスへの貼り付けのほうが強い。
6. 解決:修復しない。壊れていない世代を探す
.recover も .dump も潰えた時点で、視点を変えました。
「そもそも、壊れているのはどのファイルなのか?」
| 場所 | 中身 | 状態 |
|---|---|---|
n8n_backup.tar |
旧環境からのアーカイブ | 無傷 |
~/restore/n8n_data/ |
tarを展開したもの | 無傷 |
~/work/n8n-compose/n8n_data/ |
上をコピー+起動+権限変更を繰り返した作業場 | 破損 |
つまり、壊れていたのは「コピー先」だけで、大元は生きていました。 破損DBの修復に費やしていた時間は、丸ごと無駄だったわけです。
cd ~/work/n8n-compose
docker compose down
# 汚染された作業ディレクトリを「消さずに」退避して、作り直す
mv n8n_data n8n_data.broken
mkdir n8n_data
# 無傷の展開物から入れ直す(chown だけでいい。chmod は不要)
cp -a ~/restore/n8n_data/. ./n8n_data/
sudo chown -R 1000:1000 ./n8n_data
docker compose up -d
ワークフローも Data tables も、旧環境のまま完全に復活しました。
この件の教訓
破損DBの修復コマンドを試す前に、
findを打て。
find ~ -name 'database.sqlite*' -exec ls -lh {} \;
30秒で終わります。.recover のオプションを調べていた1時間より、こちらのほうがはるかに価値がありました。
同じ理由で、復旧作業は必ず「コピーの上」で行い、元バックアップには絶対に触らないこと。今回助かったのは、たまたま cp で作業していて mv していなかったからです。
7. 再発防止:正しいバックアップの取り方
今回の事故は、バックアップの取り方を間違えていたことに尽きます。
# ❌ n8n を動かしたまま固める(WAL/SHMが中途半端に混ざる)
tar czf n8n_backup.tar.gz -C ~/work/n8n-compose/n8n_data .
# ⭕ 止めてから取る(停止は数秒。cronで日次実行すれば十分)
cd ~/work/n8n-compose
docker compose stop n8n
tar czf "backup/n8n_$(date +%Y%m%d).tar.gz" -C ./n8n_data .
docker compose start n8n
ls -1t backup/n8n_*.tar.gz | tail -n +4 | xargs -r rm -- # 3世代より古いものを削除
止めたくないなら、SQLiteのオンラインバックアップAPIを使います。.backup は書き込み中でも整合性のあるスナップショットを取るので、ファイルコピーとは根本的に別物です。
sqlite3 ~/work/n8n-compose/n8n_data/database.sqlite ".backup '/path/to/backup.sqlite'"
N8N_ENCRYPTION_KEY は別の場所にも保管してください。 DBだけ戻しても、このキーが無いと Credentials(外部サービスの接続情報)が一切復号できません。.env と同じ場所にしか無いのでは、バックアップの意味が半減します。
そしてDBが肥大化しなければ事故の確率も下がるので、実行履歴の自動削除も入れておきます。常時稼働で履歴を貯め続ける前提なら、素直に PostgreSQL に移すべきです。今回の orphan index は、SQLiteのWAL構成をファイルコピーで扱ったがゆえの事故でした。
environment:
- EXECUTIONS_DATA_PRUNE=true
- EXECUTIONS_DATA_MAX_AGE=168 # 168時間 = 7日分だけ保持
8. まとめ
| 遭遇したエラー | 真因 | 対処 |
|---|---|---|
unrecognized image format |
イメージではなくデータのtarだった |
tar -tvf で中身を見てから判断 |
refers to undefined volume |
YAML崩れ+named volumeの定義漏れ | バインドマウントに統一 |
| ワークフローが0件 |
n8n_data が2箇所にあり、空のほうを見ていた |
composeのマウント先を確認 |
SQLITE_CORRUPT: orphan index |
稼働中の .n8n をファイルコピーした |
修復せず、無傷の世代から入れ直す |
no such table: sqlite_dbpage |
.recover 非対応ビルド |
strings で生サルベージ |
Unknown credential name |
古いノードでCLIエクスポートが全滅 | ブラウザUIから読み込む |
ENOENT |
ホストパスとコンテナ内パスの混同 | マウントの対応関係を確認 |
一番言いたいのはこれです。
修復は最後の手段。まず「壊れていないコピー」を探す。
後編では、このサーバのKVM上に建てた Windows VM に外からRDPしようとして踏んだネットワークの地雷を書きます。ping が 100% loss なのにTCPは通っていた話、iptables -A FORWARD が効かない理由、そして iptables -t nat -F でVMがインターネットから消えた話です。
→ [libvirtのNAT配下VMにRDPしたくて iptables -t nat -F を打ったら、VMがインターネットから消えた]
参考
- Skyvia Agent の対応OS(2026年8月時点でWindowsのみ提供、Linux/macOSは Coming soon):https://skyvia.com/agent