ERR_CONNECTION_TIMED_OUT / ETIMEDOUT (tested)
ERR_CONNECTION_TIMED_OUT (Chrome) and ETIMEDOUT (Node, Linux) mean the TCP connection was never set up. Packets went out and nothing came back. We pointed four clients at an address that drops every packet and timed exactly how long each waited and what it said.
1. What the error means
To open a connection, the client sends a SYN packet and waits for a SYN-ACK. If nothing at all comes back, the client resends the SYN a few times and then gives up. That is a connection timeout.
It is different from a refused connection (ECONNREFUSED; see ERR_CONNECTION_REFUSED / ECONNREFUSED). Refused means the host answered with a reset because nothing listens on the port. That fails at once. A timeout means silence: the packets are dropped by a firewall, a wrong route, a down host or a wrong IP.
It is also different from a server that accepts the connection and then never answers. That gives other errors, or none at all (section 4).
2. Our test setup
We connected to http://10.255.255.1/. It is a private address that nothing on our network answers, so every SYN is silently dropped. 192.0.2.1 (TEST-NET-1, reserved for documentation) did the same. No firewall rules were changed. Each client ran with its defaults, then with an explicit timeout.
3. Results: blackholed address
| Client | Error | Time to error |
|---|---|---|
| Chromium 153, page navigation | net::ERR_CONNECTION_TIMED_OUT | 134.7 s |
Chromium 153, fetch() in a page | TypeError: Failed to fetch | 135.1 s |
| curl, no options | exit 28, "Failed to connect to 10.255.255.1 port 80 after 134745 ms: Could not connect to server" | 134.7 s |
curl --connect-timeout 5 | exit 28, "Connection timed out after 5002 milliseconds" | 5.0 s |
Node 24 net.connect() | connect ETIMEDOUT 10.255.255.1:80, code ETIMEDOUT, errno -110 | 134.7 s |
Node 24 fetch() | TypeError: fetch failed, cause ConnectTimeoutError, code UND_ERR_CONNECT_TIMEOUT, "Connect Timeout Error (attempted address: 10.255.255.1:80, timeout: 10000ms)" | 10.5 s |
| Python urllib, no timeout | URLError(TimeoutError(110, 'Connection timed out')) | 134.7 s |
Python urllib timeout=5 | URLError(TimeoutError('timed out')) | 5.0 s |
The 134.7 s is not a browser or library setting. It is the Linux kernel giving up after its default SYN retries. Chromium, curl, Node net and Python all hit the same wall within a few milliseconds of each other. curl's own default connect timeout is 300 s (per the curl docs, not measured), so on Linux the kernel always wins.
Node's built-in fetch is the exception. Its undici connect timeout of 10 s fired first. Catch it like this:
try {
await fetch(url);
} catch (e) {
if (e.cause?.code === 'UND_ERR_CONNECT_TIMEOUT') { /* host unreachable */ }
if (e.cause?.code === 'ETIMEDOUT') { /* kernel timeout */ }
}
Note that Chromium's fetch() only says "Failed to fetch". Page code cannot see the reason. The DevTools console and network panel show the net:: code.
4. Contrast: the server accepts but never answers
Next we ran a Node server on 127.0.0.1 that accepts connections and never sends a byte. One version was a raw TCP server, one an HTTP server whose handler never calls res.end(). Both gave the same client results.
| Client | Result | Time |
|---|---|---|
curl -m 3 | exit 28, "Operation timed out after 3003 milliseconds with 0 bytes received", status 000 | 3.0 s |
curl --connect-timeout 2 -m 5 | exit 28, "Operation timed out after 5002 milliseconds with 0 bytes received" | 5.0 s (connect timeout never fired) |
Python urllib timeout=3 | TimeoutError('timed out') (not wrapped in URLError) | 3.0 s |
Node 24 fetch(), defaults | fetch failed, cause HeadersTimeoutError, code UND_ERR_HEADERS_TIMEOUT | 300.8 s |
| Chromium 153 navigation | No error from the browser. Playwright's own 420 s limit fired first. | > 420 s |
So ERR_CONNECTION_TIMED_OUT and ETIMEDOUT never appear here. The connection worked. curl's --connect-timeout does not help either; only -m/--max-time caps the whole request. We did not see Chromium's net::ERR_TIMED_OUT in this test. Chromium simply kept waiting for at least 7 minutes.
On the server side, Node's headersTimeout (60 s) and requestTimeout (300 s) only cover receiving the request. They did nothing to stop our handler hanging. When a client connected and sent nothing, or only half the headers, Node replied HTTP/1.1 408 Request Timeout and closed after 77 s (60 s plus the 30 s check interval). See 408 Request Timeout.
Behind a proxy, a hanging upstream turns into 504 Gateway Timeout. If the client gives up first, nginx logs 499.
5. Common causes
- Firewall drops the port. A cloud security group, host firewall or ISP filter with a DROP rule. A REJECT rule usually gives "refused" instead (not measured here).
- Wrong IP. Stale DNS, an old A record, or a hosts file entry pointing at a machine that is gone.
- Server is down or the port is not exposed. A container port not published, a VM stopped, a load balancer with no healthy targets.
- Private address from outside. Connecting to 10.x, 172.16-31.x or 192.168.x from another network, like our test.
- IPv6 route broken. The name resolves to an AAAA record but IPv6 traffic goes nowhere.
6. How to fix it
- Check what the name resolves to:
dig +short example.com. Is that the right IP? - Test the port with a short limit:
curl -v --connect-timeout 5 http://HOST:PORT/. Exit 28 with "Connection timed out" means silence. Exit 7 after 0 ms (we measured "Failed to connect to 127.0.0.1 port 8987 after 0 ms: Could not connect to server" on a closed port) means refused. Without--connect-timeout, a timeout prints the same "Could not connect to server" text, so check the exit code: 28 vs 7. - Test from the server itself:
curl http://127.0.0.1:PORT/. If that works, the problem is a firewall or routing between you and it. - Open the port in every layer: cloud security group, host firewall, container port mapping.
- In code, always set your own timeouts so a dead host fails in seconds, not 135 s: curl
--connect-timeoutplus-m, Pythontimeout=, NodeAbortSignal.timeout().
If connections open and then drop, read ERR_CONNECTION_RESET / ECONNRESET.
Frequently asked questions
How long does Chrome wait before ERR_CONNECTION_TIMED_OUT?
On our Linux box, 134.7 s. That is the kernel's default SYN retry limit, not a Chrome setting. curl, Node net.connect and Python with no timeout failed at the same moment.
Why does Node fetch fail after 10 seconds?
Node's built-in fetch (undici) has its own 10 s connect timeout. We measured 10.5 s and the cause code UND_ERR_CONNECT_TIMEOUT. Plain net.connect has no such limit and waited 134.7 s for the kernel's ETIMEDOUT.
What is the difference between ETIMEDOUT and ECONNREFUSED?
ETIMEDOUT: no reply at all, usually a firewall drop, wrong IP or down host. ECONNREFUSED: the host replied that nothing listens on the port. Refused fails at once; timed out takes seconds to minutes.
Does curl --connect-timeout stop a server that never answers?
No. It only covers setting up the connection. Against our server that accepted but never replied, curl --connect-timeout 2 -m 5 ran the full 5 s and failed with Operation timed out, 0 bytes received. Use -m (--max-time).
Does ERR_TIMED_OUT appear when the server hangs?
Not in our test. Chromium 153 waited over 7 minutes on a server that accepted but never replied, without any error. The connection succeeded, so ERR_CONNECTION_TIMED_OUT does not apply there either.
Written with AI assistance. Measured on 2026-10-02 on Linux 6.12 with headless Chromium 153.0.8010.12 (Playwright), Node.js 24.21.0, curl 8.14.1 and Python 3.13.5 urllib. Blackholed target: 10.255.255.1:80 (no reply to SYN; 192.0.2.1 behaved the same). Hanging-server tests used a local Node server that accepts but never answers. Kernel and firewall settings were left at their defaults. PlainHeaders is our extension; curl, Python, Node.js and Chromium are free.