Timeouts Everywhere
Set timeouts on server requests, outbound HTTP, database queries, and queues - unbounded waits are the most common cause of cascading Node.js API outages.
Recipe
Quick-reference recipe card - copy-paste ready.
import { createServer } from "node:http";
const server = createServer(app);
server.requestTimeout = 30_000;
server.headersTimeout = 35_000;
server.keepAliveTimeout = 65_000;
const data = await fetch(url, { signal: AbortSignal.timeout(5_000) });When to reach for this:
- p99 latency spikes with flat CPU (threads waiting, not working).
- Connection pool exhausted under moderate load.
- Load balancer 502/504 during downstream slowness.
- Any
fetchoraxioscall without explicit timeout.
Working Example
import express from "express";
import { createServer } from "node:http";
import pg from "pg";
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
connectionTimeoutMillis: 3_000,
query_timeout: 5_000, // node-pg option wrapping statement timeout
});
const app = express();
app.get("/reports/:id", async (req, res) => {
const controller = AbortController ? new AbortController() : null;
const deadline = setTimeout(() => controller?.abort(), 8_000);
try {
const report = await pool.query(
"SELECT * FROM reports WHERE id = $1",
[req.params.id]
);
const enrichment = await fetch(
`https://analytics.internal/enrich/${req.params.id}`,
{ signal: controller?.signal ?? AbortSignal.timeout(8_000) }
);
if (!enrichment.ok) {
return res.status(502).json({ error: "Analytics unavailable" });
}
res.json({ report: report.rows[0], analytics: await enrichment.json() });
} catch (err) {
if ((err as Error).name === "AbortError") {
return res.status(504).json({ error: "Upstream timeout" });
}
throw err;
} finally {
clearTimeout(deadline);
}
});
const server = createServer(app);
server.requestTimeout = 30_000;
server.listen(3000);What this demonstrates:
- DB pool connection timeout vs query timeout - different failure modes.
- Outbound fetch bounded to 8s - less than server 30s request timeout.
AbortErrormapped to 504 Gateway Timeout for clients.createServertimeouts protect against slowloris and hung sockets.
Deep Dive
How It Works
- Server
requestTimeout- max time for full request on socket (Node HTTP server). AbortSignal.timeout- abortsfetchafter N ms in Node 18+.statement_timeout(Postgres) - DB kills long queries regardless of app state.- Timeouts should cascade: outbound < handler deadline < server < load balancer.
Timeout Budget Table
| Layer | Typical value | Failure response |
|---|---|---|
| Outbound HTTP | 2-5s user path | 502/504 |
| DB query | 3-10s | 500 + log |
| Handler budget | 10-15s | 504 |
Server requestTimeout | 30s | connection reset |
| LB idle | 60s | 502 |
Postgres statement_timeout
SET statement_timeout = '5s';
-- or per role:
ALTER ROLE app_user SET statement_timeout = '5s';await pool.query("SET statement_timeout = 5000");- Server-side kill prevents one bad query from holding connections forever.
Axios Equivalent
import axios from "axios";
const client = axios.create({ timeout: 5_000 });timeoutcovers connection + response; usesignal: AbortSignal.timeout()for finer control.
Gotchas
- No timeout on fetch - waits until LB kills - pool exhaustion. Fix: always
AbortSignal.timeout. - Same timeout all layers - race between LB and app 502. Fix: stagger budgets.
- Retry without timeout reduction - amplifies load. Fix: shorter per-attempt timeout.
- Ignoring
headersTimeout- must exceedkeepAliveTimeoutslightly on Node HTTP server. - Long transactions without statement_timeout - holds pool connections. Fix: DB-level limit.
- Timeout only in happy path - clear timers in
finally. Fix: avoid leaked handles.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| AbortSignal.timeout | Native fetch in Node 18+ | Legacy Node 16 |
| undici Agent timeouts | Shared client defaults | One-off fetch |
| axios timeout | Axios-based codebase | Native fetch only |
| Deadline context (gRPC) | gRPC services | REST JSON APIs |
FAQs
What timeout for user-facing APIs?
2-5s per outbound hop; 10-15s total handler budget; 30s server max. Tune per SLO.
Should DB timeout match HTTP?
DB query timeout should be less than handler budget - fail query before HTTP deadline.
Node 24 fetch default timeout?
No default - infinite wait. Always pass signal: AbortSignal.timeout(ms).
Fastify server timeouts?
Use serverFactory with createServer options or @fastify/request-timeout plugin patterns.
BullMQ job timeout?
Set timeout on job options - separate from HTTP timeouts but same philosophy.
504 vs 503?
504 gateway timeout - upstream too slow. 503 service unavailable - dependency down or circuit open.
Client-side timeout?
Mobile apps should timeout too - but server must not wait for client patience.
Relation to circuit breakers?
Timeouts trigger failures that increment breaker counters. See Circuit Breakers.
Related
- Resilience Basics - introductory patterns
- Retries with Backoff - per-attempt timeouts
- Circuit Breakers - fail fast when downstream sick
- Performance on Express - server timeout tuning
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.