TL;DR
Error: socket hang up は、サーバーがレスポンスを完了する前にTCP接続を切断したことを意味します。最も速い解決策:
- リクエストに長めの
timeoutを設定する - サーバーがHTTPキープアライブをサポートしていない場合は無効化または再設定する
- ECONNRESET / socket hang up エラー時にリトライする
- アイドル接続を切断するロードバランサーの背後にサーバーがないか確認する
実際の原因
TCPソケットがリクエスト処理中に破棄されたときにNode.jsがこのエラーをスローします。サーバーがTCP RST(リセット)を送信した、タイムアウト後に接続を閉じた、またはネットワークが完全に切断された場合などが考えられます。完全なエラーは通常次のように表示されます:
Error: socket hang up
at createHangUpError (node:_http_client:333:15)
at Socket.socketOnEnd (node:_http_client:437:23)
at Socket.emit (node:events:525:35)
at endReadableNT (node:internal/streams/readable:1358:12)
最もよくある原因:
- サーバー側のタイムアウト:上流のサーバーまたはプロキシのタイムアウトが、リクエストの完了にかかる時間より短い
- キープアライブの不一致:サーバーがすでに閉じた接続を再利用しようとしている
- ロードバランサーのアイドルタイムアウト:AWS ALB、nginx、HAProxy がアイドル接続を(多くの場合60秒後に)閉じる一方、エージェントが接続を保持し続けている
- サーバーのクラッシュまたは再起動:レスポンス途中でリモートプロセスが終了した
- SSL/TLSハンドシェイクの失敗:まれではあるが、HTTPSリクエストがネゴシエーション中にハングアップすることがある
修正1:リクエストタイムアウトを追加する
サーバーが遅いリクエストを切断した場合、自分側に明示的なタイムアウトを設定しておくと、より分かりやすいエラーが得られ、リトライの機会も生まれます。設定しないと、Node.jsはただ待ち続けます。
ネイティブ https モジュールを使用する場合
const https = require('https');
const req = https.request({
hostname: 'api.example.com',
path: '/data',
method: 'GET',
timeout: 10000, // 10秒
}, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => console.log(body));
});
req.on('timeout', () => {
req.destroy(); // 'error' イベントをトリガー
});
req.on('error', (err) => {
console.error('Request failed:', err.message);
});
req.end();
axiosを使用する場合
const axios = require('axios');
const response = await axios.get('https://api.example.com/data', {
timeout: 10000, // 10秒 — 接続とレスポンスの両方に適用
});
修正2:キープアライブを無効化またはエージェントを調整する
これは最も気づきにくい原因です。HTTPエージェントでキープアライブが有効になっている状態で、サーバーが静かに接続を閉じると、次のリクエストでNode.jsがその死んだソケットを再利用しようとしてハングアップが発生します。
キープアライブを完全に無効化する(手軽な修正)
const https = require('https');
const agent = new https.Agent({ keepAlive: false });
const req = https.request({
hostname: 'api.example.com',
path: '/data',
agent,
}, (res) => { /* ... */ });
サーバーのタイムアウトに合わせてキープアライブを調整する(より良い修正)
AWS ALB のデフォルトのアイドルタイムアウトは60秒です。エージェントの keepAliveMsecs をそれより低く設定します。30秒が安全な目安です:
const https = require('https');
const agent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 30000, // 30秒ごとにTCPキープアライブを送信
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000,
});
// このエージェントをaxiosにグローバルに渡す
const axios = require('axios');
const client = axios.create({
httpsAgent: agent,
timeout: 15000,
});
修正3:ソケットエラー時にリトライする
一時的なネットワークの問題は避けられません。シンプルなリトライラッパーを使うと、ハングアップがアプリのクラッシュにつながる前に捕捉できます:
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let attempt = 1; attempt err.message.includes(code) || err.code === code);
if (isSocketError && attempt setTimeout(r, delay));
continue;
}
throw err;
}
}
}
修正4:プロキシまたはロードバランサーの設定を確認する
nginxやAWS ALBの背後で動作するアプリには、接続をひそかに切断する可能性のある追加レイヤーがあります。確認すべき点は以下の通りです:
nginxのupstreamキープアライブ
upstream backend {
server 127.0.0.1:3000;
keepalive 32; # 最大32個のアイドル接続を保持
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # upstreamのキープアライブに必要
proxy_read_timeout 90s; # アプリのレスポンス時間に合わせる
}
}
AWS ALBのアイドルタイムアウト
ALBのデフォルトのアイドルタイムアウトは60秒です。それより長くかかるリクエストは切断されます。AWSコンソールの Load Balancers → Attributes → Idle timeout から値を増やしてください。あるいは、修正2で示したように、HTTPエージェントのキープアライブ間隔を60秒未満に保つ方法もあります。
修正5:HTTPS証明書の問題
内部サービスやステージング環境での証明書エラーにより、サーバーが即座に接続を切断することがあります。その場合、証明書エラーではなくソケットハングアップとして表示されるため、混乱しやすいです。
// デバッグ用の一時的な設定 — 本番環境では絶対に使用しないこと
const agent = new https.Agent({ rejectUnauthorized: false });
// より良い方法:内部CAの証明書を指定する
const fs = require('fs');
const agent = new https.Agent({
ca: fs.readFileSync('/path/to/internal-ca.crt'),
});
rejectUnauthorized を無効にするとエラーが消える場合は、ネットワークの問題ではなく証明書の信頼性の問題です。
修正の確認
修正が実際に機能しているか確認します。以下のチェックを実行してください:
# curlでエンドポイントを直接テスト — curlは通るがNodeは通らない場合はエージェントの設定を確認
curl -v --max-time 15 https://api.example.com/data
# キープアライブが実際に使用されているか確認
curl -v --keepalive-time 30 https://api.example.com/data 2>&1 | grep -i 'keep-alive\|connection'
# Nodeでソケットの再利用をログに記録
agent.on('free', (socket, options) => {
console.log('Socket returned to pool:', options.hostname);
});
ループでリクエストを5〜10回実行してみてください。古くなったキープアライブの失敗は、60秒のアイドル期間後の最初のリクエストで発生する傾向があります。1分待ってからリトライすることでそれをシミュレートします。待機後にのみ失敗する場合は、修正2が解決策です。
補足
コンテナ化された環境でのNode.jsと上流APIの間のネットワーク接続のデバッグは、特にルーティングとサブネット設定に関わることが多いです。ToolCraftのSubnet Calculatorは、CIDRレンジの確認や送信元と宛先が実際にルーティング可能かどうかの確認に役立ちます。ファイアウォールルールを調べ始める前に多くの時間を節約できます。
もう一つ注目すべきパターンがあります。本番環境の高負荷時にのみ断続的にエラーが発生する場合は、HTTPコールの周辺に構造化ログを追加してください。err.code、対象のホスト名、リクエストの所要時間をログに記録します。58〜62秒の間に失敗が集中している場合は、ほぼ間違いなくロードバランサーのアイドルタイムアウトが原因です。

