Describe the bug
With the http adapter and maxRedirects: 0, configured timeout values are ignored while the TCP connect phase is still pending. If the host's SYN is blackholed or the address is unroutable, the request only settles when the kernel's TCP retry gives up — roughly 5s on macOS, longer on Linux — regardless of how short timeout is.
maxRedirects > 0 is unaffected, because the follow-redirects transport starts its own timer at request construction.
Reproduction
Tested against current v1.x HEAD:
import axios from 'axios';
// 192.0.2.1 is TEST-NET-1 (RFC 5737); kernel drops the SYN.
const url = 'http://192.0.2.1/';
async function attempt(label, config) {
const t0 = Date.now();
try { await axios.get(url, config); }
catch (err) {
console.log(`[${label}] ${Date.now() - t0}ms code=${err.code}`);
}
}
await attempt('maxRedirects=0, timeout=500', { maxRedirects: 0, timeout: 500 });
await attempt('maxRedirects=0, timeout=2000', { maxRedirects: 0, timeout: 2000 });
await attempt('maxRedirects=5, timeout=500', { maxRedirects: 5, timeout: 500 });
Output (Node 24, macOS):
[maxRedirects=0, timeout=500] 5006ms code=ECONNABORTED
[maxRedirects=0, timeout=2000] 5002ms code=ECONNABORTED
[maxRedirects=5, timeout=500] 506ms code=ECONNABORTED
Expected
The configured timeout should fire during the connect phase too, not only after the socket is established.
Root cause
ClientRequest.setTimeout(ms, cb) from Node's native http module arms a socket-inactivity timer. That timer doesn't tick until the socket is bound and connected. If connect never completes, the timer never starts and the reject is driven by whatever connect error the kernel surfaces.
follow-redirects runs its own wall-clock timer that starts at request construction, which is why maxRedirects > 0 honors the configured value immediately.
Suggested fix
For the maxRedirects === 0 path in lib/adapters/http.js, attach a wall-clock setTimeout(config.timeout) from the moment the request is created, fire the existing timeout error when it elapses, and clear it on close / response / successful settle. The existing message and code resolution can be reused so timeoutErrorMessage and transitional.clarifyTimeoutError keep working.
Related
Axios version
v1.x HEAD (verified at 25387ae)
Adapter
http
Describe the bug
With the http adapter and
maxRedirects: 0, configuredtimeoutvalues are ignored while the TCP connect phase is still pending. If the host's SYN is blackholed or the address is unroutable, the request only settles when the kernel's TCP retry gives up — roughly 5s on macOS, longer on Linux — regardless of how shorttimeoutis.maxRedirects > 0is unaffected, because the follow-redirects transport starts its own timer at request construction.Reproduction
Tested against current v1.x HEAD:
Output (Node 24, macOS):
Expected
The configured timeout should fire during the connect phase too, not only after the socket is established.
Root cause
ClientRequest.setTimeout(ms, cb)from Node's native http module arms a socket-inactivity timer. That timer doesn't tick until the socket is bound and connected. If connect never completes, the timer never starts and the reject is driven by whatever connect error the kernel surfaces.follow-redirects runs its own wall-clock timer that starts at request construction, which is why
maxRedirects > 0honors the configured value immediately.Suggested fix
For the
maxRedirects === 0path inlib/adapters/http.js, attach a wall-clocksetTimeout(config.timeout)from the moment the request is created, fire the existing timeout error when it elapses, and clear it onclose/response/ successful settle. The existing message and code resolution can be reused sotimeoutErrorMessageandtransitional.clarifyTimeoutErrorkeep working.Related
lib/adapters/http.js:740(transport selection),lib/adapters/http.js:1023(currentreq.setTimeouthandler)Axios version
v1.x HEAD (verified at 25387ae)
Adapter
http