Dify APIで「504 Gateway Timeout」が発生する原因と解決策
はじめに
Dify のワークフローAPIを使ったアプリケーションを運用していたところ、間欠的に謎のエラーが発生するようになりました。
エラーメッセージは「Failed to get response from AI workflow」。しかし、Difyの管理画面を確認すると、すべての実行がSUCCESSになっているという不思議な状況でした。
この記事では、この問題の原因と解決策を共有します。
症状
- ワークフローを実行すると、約60秒後にエラーが返ってくる
- エラーメッセージ: 「Failed to get response from AI workflow」
- Dify管理画面では実行成功(SUCCESS)と表示される
- 毎回ではなく、処理時間が長いときだけ発生
原因の調査
デバッグコードの追加
まず、PHPのcURL呼び出し部分にデバッグコードを追加して、実際のエラー内容を確認しました。
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode >= 400) {
error_log("HTTP Error: $httpCode");
error_log("Response: " . substr($response, 0, 500));
}
判明した事実
レスポンスを確認すると、以下のようなHTMLが返ってきていました:
<html>
<head><title>504 Gateway Time-out</title></head>
<body>
<center><h1>504 Gateway Time-out</h1></center>
</body>
</html>
504 Gateway Timeout でした!
根本原因
アーキテクチャの問題
全体のリクエストフローは次のようになっていました。
ブラウザ (タイムアウト: 5分)
↓
PHPサーバー (タイムアウト: 5分)
↓
nginx / ロードバランサー / Cloudflare (タイムアウト: 約60秒) ← ★ここ!
↓
Dify API (処理完了まで60〜90秒)
何が起きていたか
- PHPが
blockingモードでDify APIを呼び出す - Difyはワークフローを実行(60〜90秒かかる)
- 60秒経過時点で、中間のnginx/ロードバランサーがタイムアウト
- Difyは処理を完了するが、レスポンスは途中で切断される
- PHPは504エラーを受け取り、エラーレスポンスを返す
- Dify管理画面では「成功」と表示される(実際に処理は完了しているため)
Dify公式ドキュメントの記載
Difyの公式ドキュメントにも以下の記載があります:
“Cloudflare timeout is 100s for blocking”
実際には、nginx や各種ロードバランサーのデフォルト設定は60秒程度のことが多いです。
解決策
blockingモードからstreamingモードへ変更
Dify APIには2つのレスポンスモードがあります:
| モード | 説明 |
|---|---|
blocking |
処理完了まで待ってから、一括でレスポンスを返す |
streaming |
処理中も逐次データを送信する(Server-Sent Events形式) |
streamingモードでは、処理中も定期的にデータが送信されるため、接続がアイドル状態にならず、タイムアウトしにくくなります。
実装方法
方法1: フロントエンドでストリーミング処理(推奨)
フロントエンドでEventSourceや fetch + ReadableStream を使ってストリーミングを処理する方法です。リアルタイムで進捗を表示できます。
方法2: バックエンドで内部処理(今回採用)
既存のフロントエンドを変更せずに対応したい場合、バックエンド(PHP)でストリーミングを内部処理し、最終結果だけをJSONで返す方法があります。
以下は方法2の実装例です。
PHPコード例
<?php
// Dify API設定
$config = [
'api_url' => 'https://your-dify-instance.com/v1/workflows/run',
'api_key' => 'your-api-key',
'timeout' => 300,
];
// ワークフロー実行
function callDifyWorkflow($config, $inputs, $userId) {
$ch = curl_init($config['api_url']);
$payload = [
'inputs' => $inputs,
'response_mode' => 'streaming', // ★ ここがポイント!
'user' => $userId
];
$headers = [
'Content-Type: application/json',
'Authorization: Bearer ' . $config['api_key']
];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => false,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => $config['timeout'],
CURLOPT_CONNECTTIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_FOLLOWLOCATION => true,
]);
// ストリーミングデータを内部で処理
$buffer = '';
$finalResult = null;
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($curl, $data) use (&$buffer, &$finalResult) {
$buffer .= $data;
// 改行で区切られた各行を処理
while (($pos = strpos($buffer, "\n")) !== false) {
$line = substr($buffer, 0, $pos);
$buffer = substr($buffer, $pos + 1);
// "data: " で始まる行を解析
if (strpos($line, 'data: ') === 0) {
$jsonStr = substr($line, 6);
$event = json_decode($jsonStr, true);
// workflow_finished イベントから最終結果を取得
if ($event && $event['event'] === 'workflow_finished') {
$finalResult = $event['data']['outputs'] ?? null;
}
}
}
return strlen($data); // 必ずデータ長を返す
});
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400 || $finalResult === null) {
return ['success' => false, 'error' => 'Workflow execution failed'];
}
return ['success' => true, 'result' => $finalResult];
}
// 使用例
$inputs = [
'input_text' => 'ここに入力テキスト',
];
$result = callDifyWorkflow($config, $inputs, 'user_123');
header('Content-Type: application/json');
echo json_encode($result);
ストリーミングイベントの種類
Difyのストリーミングモードでは、以下のイベントが送信されます:
| イベント | 説明 |
|---|---|
workflow_started |
ワークフロー開始 |
node_started |
ノード処理開始 |
node_finished |
ノード処理完了 |
workflow_finished |
ワークフロー完了(★最終結果はここに含まれる) |
なぜstreamingで解決するのか
【blockingモードの場合】
PHP → nginx → Dify
(60秒間データなし = タイムアウト)
【streamingモードの場合】
PHP → nginx → Dify
← node_started (10秒後)
← node_finished (30秒後)
← node_started (31秒後)
...
← workflow_finished (90秒後)
データが流れ続けるので、接続が維持される!
streamingモードではこのように一定間隔でデータが流れ続けるため、HTTP接続がアイドル状態にならず、中間のnginxやロードバランサーでのタイムアウトを回避できます。
その他の解決策
nginxのタイムアウト延長
サーバー設定を変更できる場合は、nginxのタイムアウトを延長する方法もあります。
location /api/ {
proxy_pass http://backend;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
}
ただし、この方法は次のような制約があります。
- サーバー管理権限が必要
- 他のサービスに影響する可能性がある
- Cloudflareなど外部サービスのタイムアウトは変更できない
LLMモデルと処理時間
今回の問題は、使用するLLMモデルによって処理時間が変わることも関係していました。
| モデル | 処理時間(目安) | タイムアウトリスク |
|---|---|---|
| GPT-4o-mini | 20〜40秒 | 低 |
| GPT-4o | 30〜60秒 | 中 |
| GPT-5-nano | 60〜90秒 | 高 |
| GPT-5-mini | 60〜120秒 | 高 |
新しいモデルや、複雑なワークフローを使う場合は、処理時間が60秒を超えることを想定して、最初から streaming モードを使うことをおすすめします。
まとめ
問題
- Dify APIで間欠的にエラーが発生
- Dify管理画面ではSUCCESSなのに、クライアントにはエラーが返る
原因
blocking モードで60秒以上の処理を行うと、中間のnginx/ロードバランサーがタイムアウトしてしまうことが原因でした。
解決策
response_modeをstreamingに変更- PHP内部でストリーミングを処理し、最終結果だけを返す
教訓
- Dify管理画面のSUCCESSを信じてはいけない(クライアントに届いているとは限らない)
- 60秒を超える可能性がある処理はstreamingモードを使う
- エラー発生時は、HTTPステータスコードとレスポンス内容を必ず確認する
この記事が同じ問題で悩んでいる方の参考になれば幸いです!




