Connection refused, timed out, and the rest
Six error strings account for nearly every failed connection on a Linux system. Each one is generated at a different point in the journey, by a different party, and each one eliminates a different set of possible causes. Learning to read them is the highest-value hour in network troubleshooting — and "what's the difference between connection refused and connection timed out?" is asked in interviews precisely because the answer reveals whether someone understands what a packet actually does.
The table to memorise
| Error | Kernel errno | Who produced it | What it proves |
|---|---|---|---|
Name or service not known |
— | Your own resolver | No packet was ever sent. This is a DNS problem, not a network one |
Network is unreachable |
ENETUNREACH |
Your own kernel | No packet was sent. Your routing table has no route for that destination |
No route to host |
EHOSTUNREACH |
Usually a router on the path | The packet travelled, and something along the way reported it couldn't deliver it |
Connection refused |
ECONNREFUSED |
The destination host | The packet arrived and a live host answered — with a TCP reset, because nothing is listening on that port |
Connection timed out |
ETIMEDOUT |
Your own kernel, after giving up | The packet went out and nothing came back. Something dropped it silently |
Connection reset by peer |
ECONNRESET |
The other end, mid-conversation | The connection was established and then killed |
The whole table pivots on one question: did anything come back? A refusal means yes, something answered. A timeout means no, and silence is a deliberate choice somebody's firewall made.
Refused: the host is alive, the port isn't
On the wire, this is one packet out and one packet back:
10:31:07.412885 IP 10.20.0.12.51402 > 10.20.0.31.5432: Flags [S], seq 2847193021, win 64240, length 0
10:31:07.413094 IP 10.20.0.31.5432 > 10.20.0.12.51402: Flags [R.], seq 0, ack 2847193022, win 0, length 0
The [R.] is a TCP reset, sent by the destination's own kernel. It's the standard, correct response when a SYN arrives for a port with no listening socket.
What this rules out is substantial: DNS resolved, routing works in both directions, no firewall dropped anything, and the host is powered on and running a TCP/IP stack. The failure is entirely local to that machine — either the service isn't running, or it is running but bound to the wrong address.
That second case is the one that wastes afternoons. Check it on the server itself:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 244 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=1204,fd=7))
There's the whole bug. Postgres is running and healthy; it's listening on loopback, so it can only ever be reached from the machine itself. Nothing about the network needs changing — listen_addresses in the service's own configuration does.
Note the speed: the refusal came back in 0.2 milliseconds. A connection that fails instantly is almost always refused; one that fails slowly is almost always dropped. You can often tell which of the two you're dealing with before reading the message.
Timed out: something is dropping packets and not admitting it
On the wire, only your side speaks — the same SYN, retried on an exponential backoff, with no reply of any kind:
10:33:02.104881 IP 10.20.0.12.51410 > 10.20.0.31.9200: Flags [S], seq 918273645, win 64240, length 0
10:33:03.121004 IP 10.20.0.12.51410 > 10.20.0.31.9200: Flags [S], seq 918273645, win 64240, length 0
10:33:05.153221 IP 10.20.0.12.51410 > 10.20.0.31.9200: Flags [S], seq 918273645, win 64240, length 0
Same sequence number every time — this is one connection attempt being retransmitted, not three attempts.
Silence is a policy. Firewalls offer two ways to block traffic, and the choice between them is exactly what you're observing:
DROP(netfilter) / deny (a cloud security group) — discard the packet, send nothing. The client waits out its full retry sequence and reports a timeout. This is the common default, chosen because it gives a scanner no information.REJECT— discard the packet and send back an ICMP administratively-prohibited message or a TCP reset. The client fails immediately withConnection refusedorNo route to host.
Which means a timeout narrows things to a short list: a firewall rule with DROP, a cloud security group or network ACL that doesn't allow the port, a machine that's powered off, or an address that doesn't exist. It is not usually the application, because the application never saw anything.
Note also how long the wait is when nothing bounds it. Linux retries a SYN several times with doubling delays, which adds up to well over two minutes before ETIMEDOUT. That's why -w 3 belongs in every reachability test you type, and why an application without a connect timeout configured can hold a worker thread hostage for minutes on a single dead backend.
The one-line difference worth being able to say out loud
Refused: your packet arrived and was actively rejected. Timed out: your packet vanished and nobody replied. The first is a service or bind-address problem on a reachable host; the second is a filtering or reachability problem in between.
The two "unreachable" errors, which are not the same
This one never left your machine. The kernel consulted its routing table, found no entry matching that destination — not even a default route — and gave up before generating a packet. It's a local configuration problem, and one command confirms it:
The usual causes are a missing default route (common in containers with custom networking, and on hosts where a DHCP lease expired) and an IPv6 address on a host with no IPv6 route.
This one is different despite the similar wording. A packet did leave, and something reported back that it couldn't be delivered — typically an ICMP Destination Unreachable message from a router on the path, or, on your own LAN, your host's own failure to resolve the target's MAC address because nothing at that address answered ARP. On a local subnet, that means the IP is unused or the machine is off.
Distinguishing them: Network is unreachable is answered by ip route, No route to host is answered by ip neigh and by asking whether that host exists at all.
Reset by peer: it worked, and then it didn't
This is a different category from all of the above, because the connection succeeded. Data was flowing, and then one side sent an RST. Something killed a live conversation.
The realistic causes, roughly in order of how often they turn out to be the answer:
- The server-side process crashed or was restarted mid-request — check its own logs at that timestamp first.
- A load balancer or proxy hit an idle timeout and closed a connection the client thought was still usable. Classic in connection pools: the pool hands out a socket the far end closed thirty seconds ago.
- A stateful firewall dropped its connection-tracking entry for an idle connection, then reset the next packet because it no longer recognised it. TCP keepalives on a shorter interval than the firewall's idle timeout are the standard fix.
- The application deliberately rejected the request after reading part of it — a TLS handshake failure or a request body exceeding a size limit both look like this from the client's side.
A capture settles which end sent the reset, and that alone splits the search in half:
Read the source address of the RST packet. If it came from the server's address, the server (or something impersonating it, like a transparent proxy) ended the connection. If it came from your own address, your side did — and the reason is in your application, not theirs.
Putting it together: one symptom, four verdicts
An internal service at 10.20.0.31:9200 is unreachable from app-02. Same command, four possible outcomes, four different next steps:
| Result | Verdict | Next command |
|---|---|---|
succeeded! |
Network is fine; the problem is above TCP | curl -v against the service |
Connection refused |
Host is up, service isn't listening there | sudo ss -tlpn on 10.20.0.31 |
Connection timed out |
Something is silently dropping the packet | sudo nft list ruleset on both ends, and the cloud security group |
No route to host |
The address isn't answering on the local segment | ip neigh show 10.20.0.31, and confirm the host is running |
Four seconds of work, and each branch has eliminated three of the four possible worlds. That's what the error strings are for.
Practice
- On your own machine, produce each of the first five errors in the table deliberately: pick a closed port for
refused, a firewalled port fortimed out, an unrouted address fornetwork unreachable, a nonexistent LAN address forno route to host, and a typo'd hostname for the DNS error. Note the elapsed time for each. - Add a
DROPrule for a port on a lab VM, test it, change the rule toREJECT, and test again. Explain, from the client's error message alone, which rule was active. - Capture the traffic during a
Connection refusedand during aConnection timed out. Count the packets in each capture and explain the difference in one sentence. - Find a connection pool or HTTP client in a codebase you work on and check whether it sets a connect timeout. If it doesn't, work out how long a request would block against a silently-dropping destination, using the retry behaviour described above.
Exercise 4 is not a networking exercise disguised as a coding one — it's the reason this distinction matters in production. A service that treats "refused" and "timed out" identically will retry a dead backend for minutes and exhaust its own worker pool, turning one unreachable dependency into a full outage. The two failures deserve different timeouts and different retry policies, and you can only write those if you know which one you're getting.
Sources
- Linux man-pages, connect(2)
- Linux man-pages, tcp(7) — SYN retry behaviour and
tcp_syn_retries. - IETF, RFC 9293 – Transmission Control Protocol (TCP) — the reset behaviour for a segment arriving at a closed port.