Short answer: TCP, the Transmission Control Protocol, is the internet’s reliable delivery layer. It opens a connection between two programs with a three-way handshake, numbers every byte, resends whatever gets lost and hands the data over complete and in order. Web pages, SSH, email and database connections run over TCP; its lighter sibling UDP skips those guarantees.
TCP in plain words
The Internet Protocol (IP) moves packets from one machine to another, but only on a best-effort basis: packets can be lost, duplicated or arrive in the wrong order, and IP does not notice. TCP runs on top of IP, at both ends of the conversation, and turns that unreliable packet service into a reliable, ordered stream of bytes between two programs. A program writes bytes into a TCP socket; the program on the other side reads exactly those bytes, in that order, or gets an error. Everything in between (cutting the stream into segments, confirming them, resending, slowing down when the network is full) is the operating system’s TCP stack doing its job.
TCP was first specified in RFC 793 in 1981. The current specification is RFC 9293 (August 2022), which gathers forty years of updates into one document. Each TCP segment carries a header of at least 20 bytes (up to 60 with options) in front of its data; on a normal Ethernet path with a 1,500-byte MTU, one segment carries up to 1,460 bytes of data over IPv4.
The three-way handshake
Before any data flows, the two sides agree to talk and exchange starting numbers:
client server (listening on port 443)
| SYN seq=x |
| ----------------------------------------> |
| SYN-ACK seq=y, ack=x+1 |
| <---------------------------------------- |
| ACK ack=y+1 |
| ----------------------------------------> |
| connection ESTABLISHED |- SYN: the client asks to open a connection and sends its initial sequence number, a random starting value.
- SYN-ACK: the server accepts, acknowledges the client’s number and sends its own.
- ACK: the client acknowledges the server’s number. Both sides now know the other is there and where its byte count starts.
That costs one round trip before the first byte of data, and TLS adds its own handshake on top (one more round trip with TLS 1.3), which is why distance to a server matters so much for page loads. If nothing listens on the port, the server answers the SYN with a RST (reset) instead, and the client reports “connection refused”. If a firewall silently drops the SYN, the client retries and finally reports a timeout. Those two outcomes are what every port test reads.
Sequence numbers, ACKs and retransmission
Every byte in a TCP stream has a sequence number. The receiver sends back acknowledgements (ACKs) that say “I have everything up to byte N, send N next”. From that the sender knows what arrived:
- Lost data is resent. If no ACK arrives within the retransmission timeout (RTO), the sender sends the segment again. The RTO is calculated from the measured round-trip time (RFC 6298); Linux uses a floor of 200 milliseconds, visible as
rto:204inss -tion a fast local path. - Fast retransmit. When the receiver keeps acknowledging the same byte because a later segment arrived but an earlier one did not, the classic rule is that three duplicate ACKs make the sender resend at once instead of waiting for the timer. Current Linux kernels also detect loss by timing (RACK, enabled by
net.ipv4.tcp_recovery = 1on the same Debian 12 server). - Selective ACKs (SACK) let the receiver say exactly which blocks it has, so only the missing pieces are resent. Linux enables SACK by default.
- Order and duplicates are handled with the same numbers: segments that arrive early wait in a buffer, duplicates are discarded, and the application only ever sees the stream in order.
The price of in-order delivery is head-of-line blocking: one lost segment holds back everything behind it until the resend arrives, even data that is already sitting in the receiver’s buffer.
Flow control and congestion control
Flow control: do not overrun the receiver
Every ACK carries a receive window: how many more bytes the receiver has buffer space for. The sender never has more unacknowledged data in flight than that. The window field is only 16 bits, so the window scaling option (RFC 7323) multiplies it, up to about 1 GiB. A window of zero tells the sender to pause until the application on the other side reads its data. Because a sender has to wait for ACKs once the window is full, the window divided by the round-trip time caps the speed of one connection; the numbers are worked out in bandwidth vs latency.
Congestion control: do not overrun the network
The sender also keeps a congestion window of its own. A new connection starts small (ten segments on Linux, the initial window from RFC 6928, shown as cwnd:10 in ss -ti), grows it quickly while ACKs come back (slow start), then more carefully, and cuts it when packets are lost or delayed, since loss usually means a queue somewhere overflowed. The algorithm that decides how fast to grow and how hard to back off is pluggable:
- CUBIC is the Linux default, and has been since 2006. On a Debian 12 server with kernel 6.1, checked for this guide,
sysctl net.ipv4.tcp_congestion_controlreportscubic, withreno cubicas the algorithms loaded. - BBR, available in Linux since 4.9, estimates the bottleneck bandwidth and round-trip time instead of reacting to every loss, and often moves data faster over long or slightly lossy paths. It is optional; switching takes two sysctl lines, shown in the bandwidth vs latency guide.
- ECN (Explicit Congestion Notification) lets routers mark packets instead of dropping them. The same Debian 12 server has the kernel default
net.ipv4.tcp_ecn = 2: it accepts ECN when the other side asks for it but does not request it itself.
Closing a connection, RST and TIME_WAIT
Each direction is closed separately: one side sends FIN (“I have no more data”), the other acknowledges it, and later sends its own FIN, which is acknowledged in turn. Until both FINs are through, data can still flow the other way. A RST instead aborts a connection on the spot, without the exchange; it is what a crashed program, a refused port or some firewalls send.
The side that closes first then waits in TIME_WAIT before the connection is gone for good, so that late packets from the old connection cannot be mistaken for a new one using the same ports. On Linux that wait is fixed at 60 seconds. Thousands of TIME_WAIT entries on a busy web server or reverse proxy are normal; the other states and when they do point at a problem are in the TCP states table.
What is a TCP port?
A port is a 16-bit number, 0 to 65535, that tells the operating system which program a segment belongs to. A server program listens on a fixed port (443 for HTTPS, 22 for SSH); the client side gets a temporary ephemeral port from the operating system for each connection. On Debian 12 that range is 32768 to 60999 (/proc/sys/net/ipv4/ip_local_port_range); Windows uses 49152 to 65535.
A TCP connection is identified by four values: source IP, source port, destination IP, destination port. That is how one web server on port 443 holds thousands of connections at once: every client brings its own address and port. TCP and UDP have separate port spaces, so TCP port 53 and UDP port 53 are different doors, and a firewall rule names the protocol as well as the number. Which service uses which number is in the list of common ports.
TCP vs UDP
| TCP | UDP | |
|---|---|---|
| Connection | Opened with a handshake, closed with FIN or RST | None: each datagram stands alone |
| Delivery | Guaranteed or an error: lost data is resent | Best effort: a lost datagram is simply gone |
| Order | Always in order | As the datagrams arrive |
| Unit | A byte stream with no message boundaries | Separate messages (datagrams) |
| Header | 20 to 60 bytes | 8 bytes |
| Flow and congestion control | Built in | None; the application has to do it |
| First data | After one round trip (the handshake) | In the very first packet |
| Broadcast and multicast | No | Yes |
| Typical uses | Web, SSH, email, file transfer, databases | DNS lookups, VoIP, video calls, games, VPN tunnels, QUIC |
UDP is not “worse TCP”. For a voice call, a packet that arrives late is as useless as a lost one, so waiting for a resend would only add delay; a game would rather get the newest position than every old one. Those applications pick UDP and handle loss themselves.
Which protocols use TCP, and which use UDP?
| Protocol | Transport | Default port |
|---|---|---|
| HTTP/1.1, HTTP/2 (with TLS) | TCP | 80, 443 |
| HTTP/3 | QUIC over UDP | 443 |
| SSH, SFTP | TCP | 22 |
| SMTP, IMAP, POP3 | TCP | 25, 465, 587, 143, 993, 110, 995 |
| DNS | UDP, with TCP for large (truncated) answers and zone transfers | 53 |
| DNS over TLS / over HTTPS | TCP (DoH over HTTP/3 uses UDP) | 853 / 443 |
| MySQL, PostgreSQL, Redis | TCP | 3306, 5432, 6379 |
| RDP | TCP, plus UDP for a smoother picture | 3389 |
| NTP, DHCP | UDP | 123; 67 and 68 |
| WireGuard | UDP | 51820 by convention |
| OpenVPN | UDP by default, TCP optional | 1194 |
| VoIP media (RTP), most online games | UDP | Varies |
| Minecraft Java / Bedrock | TCP / UDP | 25565 / 19132 |
HTTP/3 is the interesting move. TCP lives in the operating system kernel and in every router and firewall along the path, which makes it very hard to change. QUIC (RFC 9000) rebuilds reliability, ordering and congestion control on top of UDP, inside the application, merges the transport and TLS 1.3 handshakes, and keeps a lost packet from stalling unrelated streams. HTTP/3 (RFC 9114) runs on it, while HTTP/1.1 and HTTP/2 stay on TCP. DNS shows the other pattern: small questions over UDP, and TCP whenever an answer does not fit, as explained in port 53.
How to see TCP connections on Linux
ss -tan # all TCP sockets, numeric
ss -tln # only listening ports
ss -tan state established # only open connections
ss -ti dst 198.51.100.7 # internals: RTT, congestion window, retransmissionsState Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 511 0.0.0.0:443 0.0.0.0:*
ESTAB 0 0 203.0.113.10:22 198.51.100.7:51514
TIME-WAIT 0 0 203.0.113.10:443 198.51.100.9:40218ESTAB 0 0 203.0.113.10:22 198.51.100.7:51514
cubic wscale:7,7 rto:204 rtt:1.2/0.4 ato:40 mss:1448 pmtu:1500 cwnd:10 bytes_sent:4512 bytes_acked:4512 bytes_received:3104 segs_out:40 segs_in:52 send 96533333bps minrtt:0.9-t is TCP, -a all states, -n numbers instead of names, -l listening only, -i the kernel’s own TCP information. In the second line you can read the congestion control algorithm (cubic), the smoothed RTT and its variation in milliseconds, the RTO, the congestion window in segments and any retransmissions. The layout matches iproute2 6.1 on Debian 12. To find which program owns a port, see checking open ports on Linux; if you know netstat, the netstat command guide translates every flag to ss.
Watching a handshake with tcpdump
To see the segments themselves, tcpdump captures them. The commands and the flag letters below are written from the tcpdump and pcap-filter man pages (tcpdump 4.99 on Debian 12); capturing needs root:
sudo tcpdump -ni eth0 -c 20 'tcp port 443' # 20 packets to or from port 443
sudo tcpdump -ni eth0 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0' # only opens, closes and resets10:15:02.114203 IP 198.51.100.7.51514 > 203.0.113.10.443: Flags [S], seq 1830519470, win 64240, options [mss 1460,sackOK,TS val 3171 ecr 0,nop,wscale 7], length 0
10:15:02.114251 IP 203.0.113.10.443 > 198.51.100.7.51514: Flags [S.], seq 2209371554, ack 1830519471, win 65160, options [mss 1460,sackOK,TS val 9921 ecr 3171,nop,wscale 7], length 0
10:15:02.125872 IP 198.51.100.7.51514 > 203.0.113.10.443: Flags [.], ack 1, win 502, length 0Per the man page, S is SYN, F FIN, P PUSH (data), R RST and a dot is ACK, so [S.] is the SYN-ACK. After the first packets tcpdump prints sequence numbers relative to the start (ack 1); -S shows the absolute values. A [S] that is never answered, or answered with [R.], is the timeout or refusal from the handshake section, seen from the wire.
TCP on a VPS or dedicated server
- Open ports by protocol. A web server needs TCP 80 and 443, plus UDP 443 if it serves HTTP/3; a firewall rule like
ufw allow 443/tcpleaves the UDP side closed. - SYN floods are a classic attack on the handshake: many SYNs that never complete fill the queue of half-open connections (
SYN-RECVinss). Linux answers with SYN cookies when the queue overflows, and VMHeaven runs always-on DDoS mitigation in front of every server for the volumetric kind. - Long distances are where window sizes and the congestion control algorithm show. A server far from its users is the case where BBR, bigger buffers or a closer location pay off.
Frequently asked
What is TCP in simple terms?
TCP (Transmission Control Protocol) is the part of the internet's protocol stack that delivers data reliably between two programs. It opens a connection with a handshake, numbers every byte, resends anything that gets lost and hands the data over in order. Web browsing, SSH, email and databases all use it.
What is the difference between TCP and UDP?
TCP sets up a connection and guarantees complete, ordered delivery with retransmission and congestion control. UDP sends independent datagrams with no handshake and no delivery guarantee, which makes it lighter and quicker to start. DNS lookups, voice and video calls, games, VPN tunnels and HTTP/3 (through QUIC) use UDP.
What is the TCP three-way handshake?
The client sends SYN, the server answers SYN-ACK, and the client confirms with ACK. The exchange shows both sides that the other is reachable and sets their starting sequence numbers. If nothing listens on the port, the server answers with a reset (RST) instead, which the client reports as connection refused.
Which TCP congestion control does Linux use?
CUBIC is the Linux default; sysctl net.ipv4.tcp_congestion_control shows the algorithm in use. BBR, available since Linux 4.9, is an optional alternative that often moves data faster over long or slightly lossy paths.