Diagnosing "Connection timed out"

Diagnosing "Connection timed out"

What Does "Connection timed out" Mean?

Conclusion: You sent a connection request and nothing came back. The peer — or a device on the path — is silently discarding packets (DROP), or the peer is down. The kernel retries, then gives up. The defining trait is the multi-second to tens-of-seconds wait before it fails.

Connection timed out appears when you send a SYN to the destination, no SYN+ACK returns, and the kernel exhausts its retransmissions and gives up. At the system-call level, connect() returns ETIMEDOUT, which apps surface as "Connection timed out".

$ ssh user@192.0.2.10
ssh: connect to host 192.0.2.10 port 22: Connection timed out
$ curl http://192.0.2.10/
curl: (28) Failed to connect to 192.0.2.10 port 80 after 129000 ms: Connection timed out

What sets it apart from refused (rejected instantly) and No route to host (returned unreachable instantly) is that it fails after a long silence. That silence is the signature of "someone is dropping packets without a word" — almost always a firewall DROP or a missing peer.

Prerequisites

  • OS: Ubuntu / a typical Linux (uses ss / traceroute / tcpdump)
  • Target: a host you want to reach by IP and port
  • Some checks need sudo (tcpdump / nft, etc.)

How Is It Different from refused and No route to host?

Conclusion: Refused means "arrived but the port rejected it (instant RST)", No route to host means "there is no path (instant ICMP)", and timed out means "no reply at all (long wait)". Same failed connection — the time before a reply tells you which layer is at fault.

Connection errors fall into three families by how the reply comes back. Settle which one you have first.

Error What comes back Layer of the cause
Connection refused TCP RST (instant) Service / port (L4, app)
No route to host ICMP unreachable (instant) Route / ARP / REJECT (L2–L3)
Connection timed out No response (long wait) DROP / silent path (L3–L4)
$ time curl -sS http://192.0.2.10/
curl: (28) Failed to connect to 192.0.2.10 port 80 after 129000 ms: Connection timed out

real    2m9.012s

If it takes several seconds or more to fail, you have the timed-out family: someone is silently discarding packets. If it failed instantly, go elsewhere. For a stopped service or closed port see Diagnosing Connection refused; for no path at all see Diagnosing No route to host. This article covers Connection timed out.

Refused is "the host is alive but the port turned me away"; timed out is "nobody answers at all". The former gives a clear reply (RST); the latter is silence. Silence is the hallmark of DROP — a firewall discarding with -j DROP, or a peer that is not there.

Why Does "Connection timed out" Happen?

Conclusion: The cause reduces to four things — ① a firewall DROP (most common), ② an unopened cloud security group, ③ a dead or non-listening peer, or ④ a blackhole on the path. Work from the layer nearest your host outward.

The paths that produce ETIMEDOUT, organized by cause:

Cause What is happening Where to start
Firewall DROP The server / a device on the path silently drops SYN nft list ruleset
Unopened security group A cloud SG / NACL does not allow the port Cloud console
Dead / non-listening peer The server is down / the service is not started ss -ltn on server
Path blackhole Router misconfig / a VPN or routing hole eats packets traceroute / mtr

Triage from the layer nearest your host outward. First confirm it really is timed out by the wait, then see how far on the path packets reach, and finally inspect the server side and its firewall.

DROP and REJECT are opposites. DROP discards the packet silently, so the client sees no reply and times out. REJECT returns an ICMP or RST, so the client sees No route to host or refused. In short: "a long silence = DROP", "an instant error = REJECT" — the symptom reveals how the firewall is discarding.

What Do You Check First? (Measure the Wait)

Conclusion: Cap the wait with curl --connect-timeout or nc -w and test the connect alone. If it waits out the full timeout before failing, no-response (timed out) is confirmed. There is no need to sit through the long default.

First set a short, explicit timeout and test only the connect phase. Waiting out the default (about 2 minutes for curl) is a waste.

# Cap the connect establishment at 5 seconds
$ curl -sS --connect-timeout 5 http://192.0.2.10/
curl: (28) Failed to connect to 192.0.2.10 port 80 after 5001 ms: Connection timed out

nc (netcat) gives an even more direct reachability check.

$ nc -vz -w3 192.0.2.10 80
nc: connect to 192.0.2.10 port 80 (tcp) failed: Connection timed out

If it fails only after the full wait (3 seconds above), the peer is returning nothing to your SYN. If it says refused instantly, the port is closed but the host is responding — that is not timed out.

--connect-timeout caps establishing the connection, while -m (--max-time) caps the whole transfer. For triage you want the connect phase only, so use --connect-timeout. The nc -w timeout likewise bounds the connect wait.

Where on the Path Does It Stop? (traceroute / mtr)

Conclusion: Use traceroute / mtr to see how many hops your packets reach toward the destination. The point past which everything turns to * * * is the boundary where packets vanish (are DROPped).

Visualize where on the path packets disappear.

$ traceroute -n 192.0.2.10
 1  10.0.0.1    0.4 ms   0.3 ms   0.3 ms
 2  100.64.0.1  1.2 ms   1.1 ms   1.0 ms
 3  * * *
 4  * * *

If hops up to 2 answer and 3 onward are all * * *, packets are being silently discarded at that boundary. Beyond it is a blackhole, and a firewall or a routing hole is the suspect.

But traceroute uses ICMP/UDP, so in an environment where TCP passes but only ICMP is blocked, it can wrongly look like "it stops on the path". To probe with the port you actually want, use TCP mode.

# Trace with TCP SYN to destination port 80
$ sudo traceroute -T -p 80 -n 192.0.2.10

For a continuous view, mtr is handy.

$ mtr -n -T -P 80 192.0.2.10

If only the final hop is * * * while everything before it answers, the peer's firewall may drop ICMP yet pass TCP. Do not conclude DROP from "traceroute stops" alone — always confirm with the protocol and port you want to connect on (-T -p).

How Do You Confirm SYN Goes Unanswered? (tcpdump)

Conclusion: Watching real packets with tcpdump shows directly that SYN is sent but no SYN+ACK returns. "Sent but unanswered" on the client and "SYN never arrived" on the server narrow down where the DROP sits.

When you want proof, look at the packets directly. Capture on the client while you attempt the connection.

# Run in another terminal, then attempt the connection
$ sudo tcpdump -ni any host 192.0.2.10 and port 80
10:00:00.100 IP 10.0.0.42.51000 > 192.0.2.10.80: Flags [S], seq 1, ...
10:00:01.100 IP 10.0.0.42.51000 > 192.0.2.10.80: Flags [S], seq 1, ...
10:00:03.100 IP 10.0.0.42.51000 > 192.0.2.10.80: Flags [S], seq 1, ...

Only Flags [S] (SYN) is retransmitted at growing intervals (1s, 2s, 4s…), and no Flags [S.] (SYN+ACK) returns. That is timed out itself: the packet does not reach the peer, or it arrives and is ignored.

If you can log in to the server, capturing on the server too, at the same time, makes it decisive.

# Run on the server
$ sudo tcpdump -ni any tcp port 80
  • SYN does not arrive on the server → DROP on the path (a router / cloud SG)
  • SYN arrives but is not answered → the server's local firewall DROP, or no listener

Seeing the SYN retransmit intervals (the roughly 1, 2, 4, 8 second exponential backoff) is itself hard proof of "no response". The retry count is set by net.ipv4.tcp_syn_retries (default 6), and it governs the client's total wait.

How Do You Pin Down a Firewall DROP?

Conclusion: If it times out yet packets reach the peer, a -j DROP rule is the prime suspect. On the server, check nft list ruleset / iptables -L to see whether the port is allowed. In the cloud, the security group in front of the OS firewall is the cause far more often.

Inspect the filtering rules on the server.

# Inspect nftables rules
$ sudo nft list ruleset | grep -i -E 'drop|policy'

# On systems using iptables
$ sudo iptables -L -n -v --line-numbers
Chain INPUT (policy DROP)
pkts bytes target  prot  ...  destination
 ...  ...  ACCEPT  tcp   ...  tcp dpt:22

With policy DROP and only port 22 allowed, a SYN to port 80 is silently discarded and the client times out. If you use ufw, check what is allowed.

$ sudo ufw status verbose

For recovering when ufw locks out even SSH, see ufw SSH Troubleshooting.

In the cloud (AWS / GCP / Azure, etc.), the security group or network ACL in front of the OS firewall is by far the most frequent culprit. If nft list ruleset is empty yet it still times out, first check in the console that the cloud SG allows the port (and the source IP range). A security group defaults to "deny all inbound" (effectively DROP).

Is the Server Even Listening?

Conclusion: Even with the firewall open, nothing connects if the service is not running. On the server, check ss -ltn to see whether the port is LISTENing and whether it is bound only to 127.0.0.1.

If you can log in to the server, look directly at whether the port is listening.

$ ss -ltn
State   Recv-Q  Send-Q  Local Address:Port   Peer Address:Port
LISTEN  0       128         127.0.0.1:80          0.0.0.0:*
LISTEN  0       128           0.0.0.0:22          0.0.0.0:*

If port 80 is listening on 127.0.0.1:80, it accepts local connections only and nothing arrives from outside (opening the firewall will not help). Fix the service's bind setting to listen on 0.0.0.0:80 (all interfaces) or the relevant IP.

If the port is not in the list at all, the service is not running. Check its state.

$ systemctl status nginx

A 127.0.0.1 bind should normally produce an instant refused, not a timeout — but if a firewall in front of it DROPs, the symptom is masked as timed out. When "I opened the firewall but it still won't connect", always suspect the bind address.

Checklist When It Still Fails

Conclusion: Timed out means "no response". Working from your host outward — wait time → path → SYN reply → firewall → listener — collapses the cause into one of DROP, a security group, a dead service, or a path blackhole.

  • [ ] Did it take several seconds or more to fail (an instant error is refused / No route to host)?
  • [ ] Did you cap the connect phase with curl --connect-timeout / nc -w?
  • [ ] Did you trace with traceroute -T -p <port> on the port you actually want?
  • [ ] Did you find which hop turns to * * * (the DROP boundary)?
  • [ ] Did tcpdump show SYN retransmitting with no SYN+ACK?
  • [ ] Does SYN arrive on the server too (no = path / SG, yes = local FW / no listener)?
  • [ ] Is there a DROP for the port in nft list ruleset / iptables -L?
  • [ ] Does the cloud security group / NACL allow the port and source?
  • [ ] Is the port LISTENing on 0.0.0.0 (publicly) in ss -ltn?

Next Reading