Node.jsのHTTPリクエストで'Error: read ECONNRESET'を修正する方法

intermediate💚 Node.js2026-03-25| Node.js 12以降、任意のOS(Linux/macOS/Windows)、http/https/axios/node-fetch使用環境

Error Message

Error: read ECONNRESET at TLSSocket.onConnectEnd (_tls_wrap.js:1495:19) at Object.onceWrapper (events.js:422:26) at TLSSocket.emit (events.js:327:22)
#nodejs#http#ネットワーク#econnreset#ソケット#axios

エラーの内容

HTTPまたはHTTPSリクエストを行うと、Node.jsが次のエラーをスローします:

Error: read ECONNRESET
    at TLSSocket.onConnectEnd (_tls_wrap.js:1495:19)
    at Object.onceWrapper (events.js:422:26)
    at TLSSocket.emit (events.js:327:22)

リクエストは送信途中で失敗します — ステータスコードもレスポンスボディもなく、ソケットが切断されるだけです。axios、組み込みのhttpsモジュール、node-fetch、そしてNodeのソケット層の上に構築されたあらゆるライブラリで発生します。

何が起きているのか

ECONNRESETは、リモートサーバーがTCP接続を予期せず切断したことを意味します — 正常なFINパケットではなくRST(リセット)パケットを送信しています。Nodeの視点から見ると、接続は生きていたのに突然なくなった状態です。

このエラーは次のような原因で発生します:

  • サーバーのアイドルタイムアウトが短く、接続がキープアライブプール内に長く留まりすぎた
  • リバースプロキシ(nginx、AWS ALB、Cloudflare)がNodeの読み取り完了前に接続を切断した
  • サーバーが高負荷状態にあり、積極的に接続を破棄している
  • ファイアウォールまたはNATデバイスがアイドルソケットを強制終了した
  • サーバーがすでに閉じたコネクションプールのソケットを再利用している
  • TLSハンドシェイクが途中で失敗した(頻度は低いが、証明書の設定ミスで発生する)

修正1:リトライロジックを追加する

ECONNRESETは通常一時的なエラーです — 1回リトライするだけでほとんどの場合は解決します。以下はaxios向けのシンプルなリトライラッパーです:

const axios = require('axios');

async function requestWithRetry(url, options = {}, retries = 3) {
  for (let attempt = 1; attempt  setTimeout(r, delay));
        continue;
      }
      throw err;
    }
  }
}

// 使用例
requestWithRetry('https://api.example.com/data')
  .then(res => console.log(res.data))
  .catch(err => console.error('All retries failed:', err.message));

ライブラリを使いたい場合は、axios-retryがすっきり解決してくれます:

npm install axios-retry
const axios = require('axios');
const axiosRetry = require('axios-retry').default;

axiosRetry(axios, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    return axiosRetry.isNetworkError(error) || error.code === 'ECONNRESET';
  },
});

修正2:キープアライブを無効化または調整する

NodeのHTTPエージェントはリクエスト間でソケットを再利用します。問題は、次のリクエストが届いた時点でサーバーがすでにそのソケットを閉じている可能性があることです。キープアライブを完全に無効にするか、サーバーのアイドルタイムアウトより短いソケットタイムアウトを設定することができます。

オプションA — キープアライブを無効にする(最も手軽な修正)

const https = require('https');
const axios = require('axios');

const agent = new https.Agent({ keepAlive: false });

const client = axios.create({ httpsAgent: agent });

client.get('https://api.example.com/data')
  .then(res => console.log(res.data));

オプションB — キープアライブを維持しつつ、より短いソケットタイムアウトを設定する

const https = require('https');
const axios = require('axios');

// ほとんどのサーバーは60〜120秒後にアイドル接続を閉じる。
// サーバーより先にリサイクルするため、タイムアウトを45秒に設定する。
const agent = new https.Agent({
  keepAlive: true,
  timeout: 45000, // ms — ソケットのアイドルタイムアウト
  maxSockets: 10,
});

const client = axios.create({
  httpsAgent: agent,
  timeout: 30000, // リクエストタイムアウト
});

修正3:リクエストタイムアウトを設定する

プロセスがクラッシュするよりもハングする方が深刻です。タイムアウトがないと、リセットされたソケットがアプリを無期限に停止させる可能性があります。必ず設定しましょう:

// axios
const client = axios.create({ timeout: 10000 }); // 10秒

// 組み込みhttpsモジュール
const req = https.request(options, (res) => { /* ... */ });
req.setTimeout(10000, () => {
  req.destroy(new Error('Request timed out'));
});
req.on('error', (err) => console.error(err.message));

修正4:生ソケットにerrorイベントハンドラーを設定する

ECONNRESETをスローする生のhttp/httpsリクエストは、エラーリスナーが設定されていないとプロセスをクラッシュさせます。Nodeは未処理の'error'イベントを致命的なエラーとして扱います。次のように修正してください:

const https = require('https');

const req = https.request('https://api.example.com/data', (res) => {
  let body = '';
  res.on('data', chunk => body += chunk);
  res.on('end', () => console.log(body));
});

req.on('error', (err) => {
  // これがないと、ECONNRESET = 未処理の例外 = クラッシュ
  console.error('Request error:', err.code, err.message);
});

req.end();

修正5:プロキシまたはロードバランサーのタイムアウトを確認する

ロードバランサーの背後にあるアプリには追加の問題があります。AWS ALB、nginx、および類似のプロキシにはそれぞれ独自のアイドルタイムアウトがあります — ALBのデフォルトは60秒です。プロキシが接続を切断すると、NodeアプリはECONNRESETを受け取ります。

AWS ALBの場合は、コンソールでアイドルタイムアウトを延長してください:EC2 → ロードバランサー → 属性 → アイドルタイムアウト。または、修正2で示したようにキープアライブのソケットタイムアウトを60秒未満に設定してください。

nginxをリバースプロキシとして使用している場合:

# nginx.conf
proxy_read_timeout 120s;
proxy_send_timeout 120s;
keepalive_timeout 75s;

修正の確認

  • catchブロックにログ行を追加します:console.log('err.code:', err.code)。修正後はECONNRESETが表示されなくなるはずです。
  • 30〜60秒間ループでリクエストを実行し、ソケットが正しくリサイクルされていることを確認します:
setInterval(() => {
client.get('https://api.example.com/ping')
  .then(() => console.log('OK'))
  .catch(err => console.error(err.code));
}, 5000);

  • netstatまたはssを監視して、CLOSE_WAIT状態のソケットが残っていないことを確認します:
watch -n 2 'ss -tan | grep CLOSE_WAIT | wc -l'

ヒント

  • 適切なフィールドをログに記録するerr.codeerr.addresserr.portを必ずキャプチャしてください — どのホストが接続をリセットしたかを正確に把握でき、原因究明の手間が大幅に省けます。
  • ネットワークデバッグ:Nodeアプリとターゲットサーバーのサブネットルーティングやファイアウォールルールに問題があると疑う場合、ToolCraftのサブネット計算ツールを使うとCIDR範囲やネットワークアドレスを素早く確認できます — アプリがVPCや企業ネットワーク内で動作している場合に便利です。
  • TLSの問題:HTTPSでのみECONNRESETが発生する場合は、期限切れまたは自己署名証明書を確認してください。NODE_TLS_REJECT_UNAUTHORIZED=0を一時的に設定して(本番環境では絶対に使用しないこと)TLSが原因かどうかを確認します。
  • 並行性maxSocketsを大きくしすぎると、サーバーに大量の並行リクエストが殺到します。まず10から始めて、そこから調整してください。

まとめ

  • 指数バックオフを使ったリトライロジックを追加する — 一時的なリセットのほとんどを修正できる
  • キープアライブを無効にするか、サーバーのアイドルタイムアウトより短いソケットタイムアウトを設定する
  • リセットによるプロセスの停止を防ぐため、必ずリクエストタイムアウトを設定する
  • 生のHTTP/HTTPSリクエストには必ず.on('error', ...)を設定する
  • プロキシの背後にある場合は、Nodeとプロキシレイヤーのタイムアウトを揃える

Related Error Notes