Firebase Hosting + Cloud Functions で 502 Bad Gateway が出たときの切り分けと対処法

長時間のスキャン処理で 502 に悩んだときの構造的理解と手順を、未来の自分向けに整理したメモです。

現象:timeoutSeconds を延ばしても 502 が消えない

Hosting の rewrites/api/** を Cloud Functions(2nd gen/Cloud Run 上)に流している構成で、実行時間の長い処理(例:外部 API を繰り返し叩くスキャン)を同期 HTTP で呼び出すと、ブラウザでは 502 Bad Gateway が返ることがあります。

Cloud Functions 側の timeoutSeconds を大きくしても改善しない場合、原因は「バックエンドが落ちている」だけではなく、リクエストの経路(どのレイヤーが先に諦めるか)にあります。

原因:前段(Hosting 層)と後段(Functions)のタイムアウトは別物

Hosting 経由で Functions に届ける経路では、クライアントが応答を待てる時間に、プラットフォーム側の上限が別レイヤーでかかる、と理解すると切り分けしやすいです。実務上・コミュニティ上は、おおよそ 60 秒前後で接続が切れ、クライアントからは 502 に見える、という観測がよく共有されます(数値や挙動は更新で変わりうるため、詰まったときは当該時点の公式情報も確認してください)。

イメージとしては、後段の Functions はまだ処理を続けていても、前段の門番が先に接続を閉じるため、ブラウザだけが 502 を見る、という状態です。timeoutSeconds を伸ばしても、この前段の壁は増えない、と覚えておくと混乱が減ります。

教訓: 502 のときは「バックエンドのバグ」一択と決めつけず、Hosting リライト経由か、Functions(Cloud Run)の直 URL かを切り分ける。

回避策:Hosting をバイパスして直 URL を叩く

設定ファイルを後から見返すときの対応関係は次のとおりです。

手順の例です。

  1. URL の確認: firebase deploy 完了時のログに出る Function URL を控える(例:https://api-xxxxxx-xx.a.run.app)。
  2. 環境変数: フロントのビルド時変数(例:VITE_API_ORIGIN)に、https:// からホストまで(末尾スラッシュなし)を設定する。
  3. API クライアント: /api 相対ではなく ${VITE_API_ORIGIN}/api/... のように、直オリジンに向ける。
  4. CORS: 別オリジンから叩くため、Functions 側で cors: true 等、ブラウザからの呼び出しに必要な設定をしておく。
  5. 再ビルド・再デプロイ: Vite は VITE_* をビルド時に埋め込むため、.env 変更後はビルドからやり直す。

App Check を入れる場合のメモ

個人ツールでは App Check を入れず、直 URL + Firebase Auth の ID トークンで済ませたケースです。将来 App Check で締める場合、検証フローが Hosting 前提になりやすく、直 URL だけでは運用が重くなる可能性があります。そのときは、後述の非同期ジョブ化へ寄せて、長い処理を同期 HTTP に載せない設計が現実的です。

中長期的な改善:非同期ジョブ化

直 URL でも、処理時間やクライアント側の制約は残ります。理想的には、リクエストを受け取ったらすぐジョブ ID(例:scanId)を返し、処理はバックグラウンドで実行し、フロントは Firestore 等をポーリングして進捗を確認する、という形に移行すると、ゲートウェイの種類に依存しにくくなります。

運用メモ:デプロイログはどこに出るか

Firebase Console の Hosting「リリース履歴」は、いつデプロイしたかの記録であり、CLI の詳細ログの代替にはなりません。firebase deploy を実行したターミナルに、Function URL を含む出力が出ます。ファイルに残す例:

npm run build && firebase deploy 2>&1 | tee firebase-deploy.log

まとめ