hubeiqiao/skills · Archived

node-http-proxy-ttfb-timeout

Fix HTTP proxy requests hanging forever on keep-alive connections in Node.js. Use when: (1) First request works but second request hangs indefinitely, (2) socket.setTimeout or manual timer on 'socket' event doesn't fire for reused connections, (3) Proxy sits in front of a tunnel/origin that may go down (e.g., laptop sleeping), (4) Need reliable TTFB (time-to-first-byte) timeout that works for both fresh and keep-alive connections. Applies to http.request, https.request, and reverse proxy implem…

First seen Apr 16, 2026

Installation

$ npx skills add hubeiqiao/skills --skill node-http-proxy-ttfb-timeout

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from hubeiqiao/skills · top by installs.

npx skills add hubeiqiao/skills

Browse all from hubeiqiao/skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Default branch main
Open issues 0
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.0.0
Declared agents claude-code

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 5,735 B
  • docs SUMMARY.md 545 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Node.js HTTP Proxy TTFB Timeout (Keep-Alive Safe)

Problem

When implementing HTTP proxy/reverse-proxy code with TTFB (time-to-first-byte) timeouts, socket-based timeout logic fails on keep-alive connections. The first request works fine, but subsequent requests on reused sockets hang forever even though the upstream is dead.

Context / Trigger Conditions

  • First HTTP request through proxy succeeds
  • Second request (on same keep-alive connection) hangs indefinitely
  • Upstream is a tunnel (cloudflared, ngrok) or service that can go offline
  • Using socket.setTimeout() or proxyReq.on('socket', ...) for timeout logic
  • Timeout callback never fires for reused connections
  • Half-open TCP connections (remote closed, local doesn't know)

Root Cause

Socket-based timeouts have race conditions with keep-alive connections:

  1. Fresh connection: socket.connecting === true, timer starts on 'connect' event ✓
  2. Reused connection: socket.connecting === false, timer starts immediately BUT

the response callback may already be pending, causing a race

  1. Half-open sockets: TCP connection is open locally but remote closed silently.

Writes succeed but reads hang forever. Socket-level checks don't detect this.

Solution

Use request.setTimeout() instead of socket-based timers. This works at the HTTP layer and fires reliably for both fresh and reused connections.

Pattern: Two-Phase Timeout

const TTFB_TIMEOUT = 8_000;     // 8s to get response headers
const BODY_TIMEOUT = 45_000;    // 45s for streaming body

const proxyReq = https.request(targetUrl, options, (proxyRes) => {
  // SUCCESS - extend timeout for body streaming
  proxyReq.setTimeout(BODY_TIMEOUT);

  // Handle response...
  proxyRes.pipe(res);
});

proxyReq.on('error', (err) => {
  // Handle connection/timeout errors
  console.error('Request failed:', err.message);
});

// TTFB timeout: abort if no response headers within 8s
// Works reliably for both fresh and keep-alive connections
proxyReq.setTimeout(TTFB_TIMEOUT, () => {
  console.error('TTFB timeout - upstream unreachable');
  proxyReq.destroy(new Error('TTFB timeout'));
});

proxyReq.write(body);
proxyReq.end();

What NOT to Do (Anti-Pattern)

// DON'T: Socket-based timeout - fails on keep-alive connections
proxyReq.on('socket', (socket) => {
  if (socket.connecting) {
    socket.setTimeout(CONNECT_TIMEOUT, () => { /* ... */ });
    socket.once('connect', () => {
      // Start TTFB timer after connect
      const ttfbTimer = setTimeout(() => { /* ... */ }, TTFB_TIMEOUT);
    });
  } else {
    // Reused socket - this branch races with response callback!
    const ttfbTimer = setTimeout(() => { /* ... */ }, TTFB_TIMEOUT);
  }
});

Verification

  1. Start upstream service, send request → should succeed
  2. Send second request immediately → should succeed (keep-alive reuse)
  3. Kill upstream, send request → should timeout in TTFB_TIMEOUT, not hang forever
  4. Restart upstream, send request → should succeed

Example: Complete Proxy Implementation

function tryProvider(targetUrl, body) {
  return new Promise((resolve, reject) => {
    let settled = false;

    const proxyReq = https.request(targetUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
    }, (proxyRes) => {
      if (settled) return;

      // Extend timeout for body streaming
      proxyReq.setTimeout(45_000);
      settled = true;
      resolve(proxyRes);
    });

    proxyReq.on('error', (err) => {
      if (settled) return;
      settled = true;
      reject(err);
    });

    // TTFB timeout - catches "CDN up, origin dead" scenarios
    proxyReq.setTimeout(8_000, () => {
      if (!settled) {
        proxyReq.destroy(new Error('TTFB timeout'));
      }
    });

    proxyReq.write(body);
    proxyReq.end();
  });
}

Notes

  • request.setTimeout() resets the timer on each data event, making it suitable for

both TTFB detection and stalled-body detection

  • The settled guard prevents double-resolution in timeout vs response races
  • For circuit breaker patterns, combine with health probes that pre-emptively open

the circuit when the upstream is detected as down

  • Keep-alive connections are the default in Node.js (via http.Agent). The issue

manifests when the upstream dies while connections are pooled.

Related Patterns

  • Health Probe: Run periodic checks against upstream to detect failures early
  • Circuit Breaker: After N failures, skip the dead upstream entirely
  • Connection Pool Tuning: Reduce agent.keepAlive timeout to match upstream

References