TCP, UDP, Ports & Sockets
Why this matters
The IP layer gets a packet to the right machine; the transport layer gets it to the right program on that machine and decides whether delivery is reliable. Every "connection refused," "connection timed out," and "address already in use" you will ever debug is a transport-layer event. Understanding TCP's handshake and states, UDP's fire-and-forget model, and how ports and sockets identify a conversation is what lets you read ss output and immediately know whether a service is even listening, let alone reachable.
Concepts
Ports: which program on the host
An IP address identifies a host; a port number (0–65535) identifies which program on that host a message is for. It's the apartment number after the street address. A web server listens on port 80 (HTTP) or 443 (HTTPS); SSH on 22; DNS on 53. Ports 0–1023 are well-known (privileged — binding them needs root); 1024–49151 are registered; and the high range is used for ephemeral ports — the temporary source port your client picks for each outbound connection.
A connection is fully identified by a 4-tuple: source IP, source port, destination IP, destination port. This is why one server on port 443 can serve thousands of clients at once — each client connection differs in at least the source IP or source port, so the tuples are all distinct. The server's side of every one of them is the same IP:443.
Sockets
A socket is the OS's handle for one end of a network conversation — the programming object your code reads from and writes to. There are two roles. A listening socket is bound to a port and waits for incoming connections (IP:port, e.g. 0.0.0.0:443). When a client connects, the OS creates a new connected socket representing that specific 4-tuple, so the listener stays free to accept more. When you run ss -tlnp and see a program bound to a port, you are looking at its listening socket. If nothing is listening on the port you're connecting to, you get "connection refused" — the host answered, but no socket was there.
TCP: reliable, ordered, connection-oriented
TCP gives the application a reliable, ordered byte stream on top of best-effort IP. It achieves this with sequence numbers (so lost or reordered segments are detected and fixed), acknowledgements (the receiver confirms what it got), retransmission (unacknowledged data is resent), and flow/congestion control (it slows down rather than drowning the network). The cost is setup latency and per-connection state. Use TCP when every byte must arrive correctly and in order: web pages, APIs, file transfers, SSH, databases.
The three-way handshake and connection teardown
Before any data flows, TCP establishes a connection with a three-way handshake:
- Client → server: SYN ("I want to talk; here's my starting sequence number").
- Server → client: SYN-ACK ("OK, here's mine, and I acknowledge yours").
- Client → server: ACK ("acknowledged — we're connected").
Only now does application data move. This round-trip is why a fresh TCP connection has a latency cost before the first byte. Teardown is a polite four-way exchange of FIN/ACK in each direction (either side can close). A RST (reset) is the abrupt alternative — sent, for example, when you try to connect to a port with nothing listening, producing "connection refused." Recognizing SYN, SYN-ACK, FIN, and RST in a tcpdump capture tells you exactly how far a connection got.
TCP connection states
A TCP connection is a state machine, and ss/netstat show you the state, which is diagnostic gold:
- LISTEN — a server socket waiting for connections. If your service isn't in LISTEN on the expected port, it isn't ready; nothing else matters yet.
- ESTABLISHED — the handshake completed; data can flow.
- SYN-SENT — the client sent SYN and is waiting. Stuck here means the SYN-ACK never came back (firewall dropping, host down, or wrong address).
- TIME-WAIT — a closed connection lingering briefly to catch stray packets. Many of these is normal on a busy client; they're not a leak.
- CLOSE-WAIT — the remote closed but the local app hasn't; piles of these usually mean an application bug (not closing sockets).
UDP: fast, connectionless, best-effort
UDP adds almost nothing to IP: just source/destination ports and a checksum. There's no handshake, no acknowledgement, no retransmission, no ordering — you send a datagram and hope. That sounds worse, but for the right jobs it's better: no setup round-trip, no head-of-line blocking, minimal state. DNS queries, video/voice calls, gaming, and DHCP use UDP because a lost packet is cheaper to ignore (or handle in the application) than to wait for TCP to recover. The mental rule: TCP when correctness matters more than speed; UDP when timeliness matters more than completeness. With UDP there's no "connection refused" in the TCP sense — a closed UDP port may reply with an ICMP "port unreachable" or simply stay silent, which is why UDP problems are harder to diagnose.
Command reference
| Command | What it does | Example |
|---|---|---|
ss |
Shows sockets: listening ports, connections, and their states | ss -tlnp |
ss (filtered) |
Filters sockets by state or port | ss -tan state established |
nc (netcat) |
Opens or listens on TCP/UDP ports; a Swiss-army connection tool | nc -zv example.com 443 |
nc -l |
Listens on a port (acts as a tiny server) | nc -l 9000 |
curl |
Connects over TCP to test an application port | curl -v telnet://example.com:80 |
tcpdump |
Captures packets so you can see SYN/ACK/FIN/RST | sudo tcpdump -n -i any tcp port 80 |
/dev/tcp |
Bash built-in for a raw TCP connection test | bash -c 'echo > /dev/tcp/example.com/443' |
Flag breakdowns:
ss -tlnp—-tTCP only;-llistening sockets only;-nnumeric (don't resolve ports to names, faster and clearer);-pshow the owning process (needs sudo for others' processes). This is the single most useful "what's listening here?" command.ss -tan state established—-tTCP;-aall sockets (listening and non);-nnumeric;state establishedfilters to just established connections.nc -zv example.com 443—-zzero-I/O mode (just check if the port is open, send no data);-vverbose (prints success/failure); the host and port to test. Perfect for "is this port reachable?"nc -l 9000—-llisten mode, turning netcat into a minimal server on port 9000 so you can test connectivity to yourself.sudo tcpdump -n -i any tcp port 80—-ndon't resolve IPs/ports to names;-i anycapture on all interfaces;tcp port 80a filter expression limiting capture to TCP traffic on port 80.
Hands-on exercises
See what's listening. Run
ss -tlnp. Read each line: localaddress:port, state LISTEN, and the process. Note the difference between something bound to127.0.0.1:x(loopback only) and0.0.0.0:x(all interfaces). Expected: at minimum some system services; possibly nothing on user ports.Stand up a listener and connect. In terminal A run
nc -l 9000. In terminal B runnc -zv 127.0.0.1 9000— expectsucceeded/open. Then in B runss -tlnp | grep 9000to see your listener's socket. Stop A and re-run thenc -zv— now expectConnection refused.Watch the handshake. In terminal A run
sudo tcpdump -n -i any tcp port 443. In terminal B runcurl -s https://example.com >/dev/null. In A's capture, identify the[S](SYN),[S.](SYN-ACK), and[.](ACK) flags of the three-way handshake, then the[F]/[R]at teardown. Expected: SYN, SYN-ACK, ACK in sequence before data.Test remote ports. Run
nc -zv example.com 443(expect open) andnc -zv example.com 81(expect timeout or refused). This is the fastest "is the port reachable?" check. Expected: 443 succeeds, 81 hangs/refuses.Read connection states. With a connection open (
curla slow endpoint, or keep an SSH session), runss -tan. Find ESTABLISHED entries and, right after closing something, a TIME-WAIT. Expected: mix of LISTEN and ESTABLISHED; transient TIME-WAIT after closes.Ephemeral ports and the 4-tuple. Run
curl -s https://example.com >/dev/null &a few times quickly, and during themss -tan state established. Notice each connection has the same remoteIP:443but a different local ephemeral port — the 4-tuple in action. Expected: several rows differing only in local port.UDP vs TCP behavior. Run
dig example.comwhile capturing withsudo tcpdump -n -i any udp port 53. Note DNS uses UDP — a single query and response, no handshake. Contrast with the TCP handshake you saw in exercise 3. Expected: two UDP packets (query, response), no SYN/ACK.Diagnose and fix: service on the wrong interface. Start a listener bound only to loopback:
nc -l 127.0.0.1 9000in terminal A. From terminal B,nc -zv 127.0.0.1 9000succeeds, butnc -zv <your-eth0-IP> 9000fails — the service is listening on127.0.0.1only, so it's unreachable from any real interface. Confirm withss -tlnp | grep 9000(you'll see127.0.0.1:9000, not0.0.0.0:9000). Fix: stop it and rebind to all interfaces —nc -l 0.0.0.0 9000— thennc -zv <your-eth0-IP> 9000succeeds. "Works on localhost but not from other hosts" almost always means a service bound to127.0.0.1instead of0.0.0.0;ss -tlnpshows it instantly.Diagnose and fix: connect to a dead port. Run
curl -v --connect-timeout 3 http://127.0.0.1:9999. You getConnection refusedimmediately (a RST — the host is up, no socket listening). Contrast withcurl -v --connect-timeout 3 http://10.255.255.1:9999, which hangs then times out (no host / packets dropped, no RST). The distinction — instant refused vs slow timeout — tells you "host up, nothing listening" versus "can't even reach the host," a diagnostic split you'll use constantly.
Independent challenge
You're told an internal HTTP service "should be running on port 8080 on host X but clients can't reach it." Without being given any commands, construct a diagnosis that determines, in order: whether anything is listening on 8080 at all, whether it's bound to the right interface, whether the TCP handshake completes from a client, and — if it does complete but the client still fails — whether the problem has moved above the transport layer. This requires combining this module with module 02 (DNS): part of "clients can't reach it" may be that clients resolve host X's name to the wrong address entirely, so verify the name-to-address step before ever touching ports.
Stuck? One hint
Work bottom-up but start one layer higher than you think: first confirm the client resolves host X to the IP you expect (getent hosts X — module 02), because a wrong IP makes every port test meaningless. Then on the server use ss -tlnp to prove something is in LISTEN on 8080 and check whether it's 0.0.0.0:8080 or 127.0.0.1:8080. From the client, nc -zv X 8080 tests whether the handshake completes. If the handshake completes but the app still fails, the transport layer did its job and the fault is application-layer (HTTP), which is the next module.
Common mistakes & troubleshooting
- Confusing "connection refused" with "connection timed out." Refused = host reachable, nothing listening (a RST came back, fast). Timed out = no response at all, usually a firewall dropping packets or a wrong/dead address (slow). These point to completely different fixes.
- Binding to
127.0.0.1and expecting external access. A service on loopback is invisible to other hosts. Bind to0.0.0.0(or a specific external interface) for reachability.ss -tlnpreveals which. - Panicking over TIME-WAIT. Many TIME-WAIT sockets on a busy client are normal and harmless; they clear on their own. CLOSE-WAIT piling up, by contrast, is a real app bug.
- Assuming UDP "connections." UDP is connectionless — there's no handshake and often no error when a port is closed, so
nc -zv -uresults are unreliable. UDP reachability often needs application-level confirmation. - Forgetting privileged ports need root. Binding a port below 1024 without privileges fails with "permission denied," which looks like a code bug but is a permissions issue.
- "Address already in use." A previous process still holds the port (often in TIME-WAIT or still running). Find it with
ss -tlnpbefore assuming your code is wrong.
Checkpoint quiz
Write down your answer to each question before expanding it — checking without attempting first is the single easiest way to fool yourself into thinking you've learned this.
- What does a port number identify, and what does an IP address identify? Why does a connection need both ends' IPs and ports?
- List the three steps of the TCP three-way handshake and what each one signals.
- You see a socket in state
LISTENvsESTABLISHEDvsSYN-SENT. What does each tell you about where a connection stands? - Give two concrete differences between TCP and UDP, and one service that fits each and why.
- Why can one web server on
0.0.0.0:443handle thousands of simultaneous clients? Reference the 4-tuple. - What's the difference in observed behavior between connecting to a port with nothing listening versus a port a firewall is dropping?
- A service works via
curl 127.0.0.1:8080but not from another host. What's the most likely cause and which command confirms it? - What does a growing pile of
CLOSE-WAITsockets usually indicate, and how does that differ from manyTIME-WAITsockets?
Show answers
- A port identifies which program/socket on a host; an IP identifies which host. A connection is a 4-tuple (source IP+port, destination IP+port); both ends' ports are needed so the OS can demultiplex which specific conversation each packet belongs to.
- SYN (client requests a connection, sends its initial sequence number), SYN-ACK (server accepts, sends its own sequence number and acknowledges the client's), ACK (client acknowledges the server's — connection established, data can flow).
- LISTEN: a server socket waiting for incoming connections (service is ready). ESTABLISHED: handshake complete, data flowing. SYN-SENT: the client sent a SYN and is still waiting for the SYN-ACK — stuck here means the reply never came (firewall drop, host down, wrong address).
- TCP is connection-oriented, reliable, ordered (handshake, acks, retransmission); UDP is connectionless, best-effort, unordered, no handshake. HTTP/SSH/databases use TCP (every byte must arrive correctly); DNS/voice/video/gaming use UDP (timeliness beats completeness, loss is cheaper to tolerate or retry).
- Every client connection is a distinct 4-tuple (differing in source IP and/or source port), so the server distinguishes them all even though its side is always
IP:443. The single listening socket only accepts; each accepted connection is its own connected socket keyed by the 4-tuple. - Nothing listening → an immediate RST → "connection refused" (fast). Firewall dropping → no response at all → "connection timed out" (slow).
- The service is bound to
127.0.0.1(loopback only) instead of0.0.0.0(all interfaces).ss -tlnpconfirms it by showing127.0.0.1:8080rather than0.0.0.0:8080. - Many CLOSE-WAIT usually means an application bug — the remote closed but the local app isn't closing its sockets (a leak). Many TIME-WAIT is normal on a busy client (recently closed connections lingering briefly) and clears on its own.
Cumulative review
Closed-book. Don't reopen earlier modules while attempting these. These deliberately mix concepts across modules 00–03.
- You run
curl http://shop.example. Walk from the name to the first byte of the HTTP request, naming which layer/step handles: resolving the name, deciding whether the destination is local or via the gateway, choosing a source port, and establishing the connection. - A host is
10.0.5.40/29. It tries to reach10.0.5.44on port 22 and it fails. Explain, using addressing and transport reasoning, at least two distinct things that could be wrong and how you'd tell them apart. dig example.comreturns the correct IP, butnc -zv example.com 443times out. Which layers are proven working and which is suspect? What single follow-up test narrows it further?- Why does DNS traditionally use UDP while HTTP uses TCP? Tie your answer to what each protocol guarantees and what each application can tolerate.
- A connection sits in SYN-SENT and never advances. Relate this to the three-way handshake: which specific packet is missing, and name two lower-layer causes.
- Explain how one server on
0.0.0.0:443handles 5,000 simultaneous clients, referencing the 4-tuple and the difference between a listening socket and a connected socket. getent hosts api.examplereturns169.254.10.5. Before you even test the port, why is this address already a red flag, and what does it usually mean?- Put SYN, SYN-ACK, ACK, and RST on the mental-model layer stack: which layer are they, and what does each tell you when you see it in a
tcpdumpcapture?
Show answers
- Name → IP is DNS at the application layer (module 02), a separate step before any packet. Local-vs-gateway is the IP layer applying the subnet mask (module 01/00). Choosing a source port and doing the handshake is TCP at the transport layer (module 03), using an ephemeral source port and destination port 80. Only after the SYN/SYN-ACK/ACK completes does the HTTP request go out (module 00 encapsulation).
10.0.5.40/29covers10.0.5.40–.47, so.44is on the same subnet — good. Possibilities: (a) nothing is listening on 22 on.44(would give an immediate "connection refused"/RST); (b) a firewall is dropping the SYN (would give a slow timeout, connection stuck in SYN-SENT); (c) the host is down (also timeout). Tell them apart by the speed and nature of failure: instant refused vs slow timeout, and by checkingss -tlnpon.44and any firewall rules.- DNS/application resolution works and the name is correct. The suspect is transport/IP reachability to port 443 — likely a firewall dropping the SYN or the host being down (timeout, not refused). Follow up with
ping <IP>and/orcurl -v --connect-timeout 3to distinguish "host unreachable" from "port filtered," and try another port to see if it's port-specific. - DNS queries are small, single request/response, and can simply be retried if lost — timeliness beats the overhead of a TCP handshake, so UDP fits. HTTP needs every byte of a page/response delivered correctly and in order, so it needs TCP's reliability and ordering despite the setup cost.
- The SYN-ACK (server's reply) never came back. Lower-layer causes: a firewall dropping the SYN or the SYN-ACK, the destination host being down/unreachable, or the wrong IP so no one answers.
- Every client's connection is a distinct 4-tuple (their source IP+port differs), so the server distinguishes them even though its side is always
IP:443. The single listening socket only accepts; each accepted connection becomes its own connected socket keyed by the 4-tuple, so thousands coexist. 169.254.x.xis link-local — a self-assigned address that appears when the host failed to get a real one (module 01). It's not a routable service address, so any port test is pointless; fix address assignment/DNS first.- They're transport-layer (TCP). SYN = a client is trying to open a connection; SYN-ACK = the server accepted and replied (path works both ways so far); ACK = handshake complete, data can flow; RST = abrupt refusal/reset, typically "no socket listening on that port" or a forced close. Seeing SYN but no SYN-ACK means the request left but nothing came back — look lower (firewall/host).
Free PDF notes
Download the notes for each video on this page as a printable PDF, with every diagram and interview questions: SD-B-003: TCP Explained, SD-B-004: TCP Handshake, FIN/RST & TIME_WAIT, SD-B-005: UDP vs TCP, SD-B-007: Ports, Sockets & Routing. Each section in one file: Phase 2, Networking (PDF).
Next
05 — Routing, NAT & firewalls: you have a reliable TCP connection between two hosts — now learn how packets actually choose a path between them, get their addresses translated, and are permitted or blocked along the way. The application protocols that ride on TCP are taught in the backend track: HTTP protocol basics and TLS, SSL and HTTPS.