Ports, sockets and the handshake
A program listens on an address and a port, and TCP opens a connection in three packets. Learn to tell a fast "refused" from a slow timeout, and fix the classic "works on my machine" bind.
An address gets a packet to a machine. A port gets it to one program on that machine. A port is a 16-bit number, 0 to 65535. A web server usually listens on 443, a Postgres database on 5432, and the app you just wrote on whatever you told it.
A program that wants connections opens a listening socket: an address, a port, and a promise to answer. Two details decide who can reach it:
- The port. Connect to 8081 when the program listens on 8080 and you reach nobody.
- The address it listens on.
0.0.0.0means every address this machine has.127.0.0.1means loopback only, so only programs on the same machine can connect. Many development servers default to127.0.0.1, deliberately, so a half-finished app isn’t exposed to the whole network.
Three packets
TCP opens a connection with a three-way handshake (RFC 9293). The client sends SYN (“let’s talk”). The server replies SYN, ACK (“yes, and let’s talk”), and the client sends ACK. Only then does any real data flow.
How the handshake fails is the useful part, because each failure has its own shape:
- Every connection starts the same way: the client sends a SYN to an address and a port.
- A program is listening there: SYN, ACK comes back and the connection opens after one round trip.
- Nothing listens on that address and port: the machine answers at once with a reset. A fast "no" means the network worked.
- Nothing answers at all - usually a firewall dropping the packet. The client retries, waits, and finally gives up. Slowness is the clue.
When the SYN gets no answer, the client sends it again. Classic TCP waits one second and doubles each wait after that (RFC 6298), and that is the schedule the simulator above shows. Since Linux 6.5 the first few retries are one second apart and only then start doubling (tcp_syn_linear_timeouts, default 4). Either way, with the default tcp_syn_retries an attempt to a silent address takes about two minutes to fail (131 seconds on current kernels). That’s why a dropped connection without a --connect-timeout can hang for two minutes.
Works on my machine
The app runs, and ss -tln on the server shows it listening. A teammate on another machine still gets “Could not connect to server”. Find out why, and fix it properly.
curl fails in 2 ms with exit 7 and 'Connection refused'. What do you know?
A fast refusal is an answer: a reset (or a firewall's port-unreachable) came back. Packets got there and back, so the route is fine. Check the port, whether the program is running, and what address it listens on. (A fast 'No route to host' would be different: then the path is the problem.)
ss -tln on the server shows LISTEN 127.0.0.1:8080. Other machines get refused. What's the fix?
The program only accepts connections arriving on loopback. Opening the firewall doesn't help, because the refusal comes from the empty port on the external address, not from a filter. Changing the port keeps the same bind problem.
Everything above ran in your browser. Now do it on a real cluster withthe tilt platform. You need Docker, kind and Tilt, about 15 GB of free disk and 8 GB of spare RAM. Setup guide · Can my machine run it?
On any Linux machine or WSL, watch the three outcomes for real:
python3 -m http.server 8080 --bind 127.0.0.1 & # listening on loopback only
ss -tln | grep 8080 # LISTEN 127.0.0.1:8080
curl -sS http://127.0.0.1:8080/ -o /dev/null; echo "exit $?" # 0
curl -sS http://127.0.0.1:8081/; echo "exit $?" # 7, instant
curl -sS --connect-timeout 3 http://10.255.255.1:8080/; echo "exit $?" # 28, after 3 s
kill %110.255.255.1 is a private address that nothing on most networks answers, so it stands in for a dropped packet.
Sources
Missions and quizzes above earn their own XP. This marks the lesson done.