TCP handshake simulator

Every TCP connection opens with three segments, SYN, SYN-ACK and ACK, and usually closes with four. Below, a client and a server run TCP's state machine. Open a connection, send data, close it, lose a segment on purpose, and see every sequence number. Further down: a worked example, TIME_WAIT, SYN floods and common errors.

Client
CLOSED
Server
LISTEN
Segments
0
Resent
0
Client
Server
Network
Time
Segments over time · now 0 ms
ClientServer0 msCLOSEDLISTEN

SYN data ACK only FIN RST still in flight

Connection
Client sends next (SND.NXT)
–
Client expects next (RCV.NXT)
–
Server sends next
–
Server expects next
–
Retransmission timeout
client 1 s, server 1 s
Bytes received
client 0, server 0
What happened
  1. 0 msThe server has called listen() and is in LISTEN. The client is CLOSED.

How to use the simulator

The two vertical lines are the client and the server; time runs downwards. Each arrow is one TCP segment, labelled with its flags, its sequence number, its acknowledgement number and the number of data bytes it carries. The state each side enters is written beside its line, using the names from the TCP specification that tools such as ss and netstat also show.

Buttons like Connect, Send and Close are what the applications do: they make a side send segments. Next event moves time forward to the next thing that happens, usually a segment arriving or a timer running out; Play does that every second. Try these:

  • Press Connect, then Next event until both sides are ESTABLISHED. Send some data from each side, close the client, then close the server. The client ends in TIME_WAIT for 60 seconds of simulated time.
  • Press Connect and one Next event, then turn on Lose the next segment before the next step. Watch the retransmission timer resend what was lost, and how the timer doubles.
  • Close the client and stop: the server sits in CLOSE_WAIT until you press its Close. That is what a program that forgets to close sockets looks like.
  • Choose Not listening before connecting. The server's TCP answers the SYN with an RST, which an application sees as "connection refused".
  • After the handshake, press Close on both sides before any FIN arrives. Both go through CLOSING, a state most connections never visit.

The model follows the state machine and segment-handling rules of RFC 9293 and the retransmission timer of RFC 6298. To keep the diagram readable it leaves out flow and congestion control, acknowledges every segment at once instead of delaying ACKs, drops segments that arrive out of order instead of queuing them, and exchanges no options. Initial sequence numbers are random but seeded, so a fresh page always gives the same numbers as the example below.

What is the TCP three-way handshake?

The TCP three-way handshake is the exchange of three segments that opens a TCP connection. The client sends a SYN, the server answers with a SYN-ACK, and the client replies with an ACK. Web pages, API calls, database queries and SSH sessions that run over TCP all start this way.

TCP gives applications a reliable, ordered stream of bytes on top of IP, which delivers individual packets with no promises: they can be lost, duplicated or arrive out of order. To make that work, TCP numbers every byte it sends, and the receiver acknowledges the numbers it has received. The handshake exists so that each side can tell the other where its numbering starts, and hear back that the other side got it.

A segment is TCP's name for one unit of data plus its TCP header. SYN, ACK, FIN and RST are single-bit flags in that header.

The handshake step by step

Before anything happens, the server's application has created a socket, bound it to a port and called listen(), so the server's TCP is in LISTEN. The client's application then calls connect().

  1. SYN. The client picks a random 32-bit initial sequence number, call it x, and sends a segment with the SYN flag set and sequence number x. It enters SYN_SENT.
  2. SYN-ACK. The server picks its own random initial sequence number y and replies with SYN and ACK both set: sequence number y, acknowledgement number x + 1. The acknowledgement number is always the next byte the sender expects to receive. The server enters SYN_RCVD, and the connection waits in the server's SYN queue.
  3. ACK. The client acknowledges the server's SYN with acknowledgement number y + 1, and enters ESTABLISHED; connect() returns. When the ACK reaches the server, the server enters ESTABLISHED too, and the connection moves to the accept queue, where the application's accept() picks it up.

A SYN carries no data, yet it uses up one sequence number, which is why the acknowledgement is x + 1 and the first byte of data is numbered x + 1. A FIN also uses one. Pure ACKs use none, so they do not need to be acknowledged themselves.

The client may put data in the same segment as its ACK, or right after it. So the client can start sending after one round trip, and the first byte reaches the server one and a half round trips after connect() was called.

TCP handshake example

This is the simulator's own run on a fresh page with a 100 ms round trip: connect, send 1,000 bytes from the client, close the client, then close the server. The client's initial sequence number was 342874228 and the server's 1754884970. Relative numbers, in brackets, count from each side's initial sequence number, which is how Wireshark shows them by default.

Sent atDirectionFlagsSequenceAcknowledgementData
0 msClient → serverSYN342874228 (0)–
50 msServer → clientSYN, ACK1754884970 (0)342874229 (1)
100 msClient → serverACK342874229 (1)1754884971 (1)
150 msClient → serverPSH, ACK342874229 (1)1754884971 (1)1,000 bytes
200 msServer → clientACK1754884971 (1)342875229 (1001)
250 msClient → serverFIN, ACK342875229 (1001)1754884971 (1)
300 msServer → clientACK1754884971 (1)342875230 (1002)
350 msServer → clientFIN, ACK1754884971 (1)342875230 (1002)
400 msClient → serverACK342875230 (1002)1754884972 (2)

Read it from the top. The SYN has relative sequence 0, and the SYN-ACK acknowledges 1, because the SYN used one number. The data segment starts at 1 and carries 1,000 bytes, so the server acknowledges 1001. The client's FIN has sequence 1001 and is acknowledged with 1002. The server, which sent no data, sends its FIN with sequence 1, and the client's final ACK acknowledges 2. 9 segments carried 1,000 bytes of data.

Why three segments and not two

Each side has a sequence number to announce, and each announcement needs an acknowledgement. That is four messages: the client's SYN, the server's ACK of it, the server's SYN, and the client's ACK. The server sends its ACK and its SYN in the same segment, so four becomes three.

Could the server skip waiting for the third segment? RFC 793 explains why not: "The principal reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion." A SYN from an earlier, abandoned attempt can be delayed in the network and arrive long after the client gave up. With two segments, the server would treat it as a new connection and start sending. With three, the server waits; the client, which knows it never sent that SYN for its current connection, answers the server's SYN-ACK with an RST, and the stray connection is torn down.

Two other cases are worth knowing. If both sides send a SYN to each other at the same time, a simultaneous open, they each acknowledge the other's SYN and the connection opens with four segments. And TCP Fast Open (RFC 7413) lets a client that has connected before put data in its SYN, saving a round trip, using a cookie the server handed out earlier.

Initial sequence numbers

Sequence numbers do not start at zero. Each side picks a new initial sequence number (ISN) for every connection, for two reasons.

The first is old segments. A connection between the same two addresses and ports can be closed and reopened quickly. If both started at zero, a delayed segment from the previous connection could fall inside the new one's range and be accepted as data. Starting somewhere different makes this very unlikely.

The second is forgery. If an attacker can guess the server's ISN, they can complete a handshake while pretending to be another IP address, without ever seeing the server's replies, and send commands that the server trusts because of the source address. Robert Morris described the attack in 1985, and Kevin Mitnick used it against Tsutomu Shimomura's computers in December 1994. RFC 6528 (2012) describes how ISNs are chosen today: a clock that ticks every 4 microseconds, plus a keyed hash of the connection's addresses and ports, so they are hard to predict from outside.

What the handshake costs

A new connection costs one round trip before the client can send its request. The round-trip time depends on distance: a few milliseconds within a city, around 60 to 70 ms between the east and west coasts of the United States, and well over 100 ms between continents. HTTPS adds a TLS handshake on top: one more round trip with TLS 1.3, two with TLS 1.2.

New connectionRound trips before the request is sentAt 100 ms round trip
Plain TCP1100 ms
TCP and TLS 1.32200 ms
TCP and TLS 1.23300 ms
QUIC (HTTP/3), first visit1100 ms
QUIC with 0-RTT resumption00 ms

This is why clients reuse connections: HTTP keep-alive, HTTP/2 sending many requests over one connection, and connection pools in database drivers and HTTP clients all pay the handshake once and spread it over many requests. It is also why QUIC, which HTTP/3 runs on, merges the transport and TLS handshakes.

Options agreed in the handshake

The SYN and SYN-ACK also carry TCP options. Some can only be sent in these two segments, so they are fixed for the life of the connection.

  • Maximum segment size (MSS). The largest amount of data each side wants in one segment. On Ethernet with a 1,500-byte MTU it is usually 1,460 bytes for IPv4. If no MSS option is sent, the default is 536 bytes.
  • Window scale (RFC 7323). Multiplies the 16-bit window field so that the receive window can exceed 64 KB, which fast, long-distance connections need.
  • SACK permitted (RFC 2018). Allows selective acknowledgements, which tell the sender exactly which blocks arrived so it resends only the missing ones.
  • Timestamps (RFC 7323). Used to measure round-trip time and to reject very old duplicates on fast connections where sequence numbers wrap around.

Each option is used only if both sides include it in the handshake. The simulator sends none of them.

Closing a connection: FIN and RST

TCP closes each direction separately. When an application calls close() (or shutdown() for writing), its TCP sends a FIN, meaning "I have no more data". The other side acknowledges it, and its application sees end of file: read() returns 0. That side can keep sending until its own application closes, which sends the second FIN. This is sometimes called the four-way handshake:

  1. The side that closes first sends FIN and enters FIN_WAIT_1.
  2. The other side acknowledges it and enters CLOSE_WAIT; the first side, on receiving that ACK, enters FIN_WAIT_2.
  3. When the second application closes, its side sends FIN and enters LAST_ACK.
  4. The first side acknowledges it and enters TIME_WAIT. When that ACK arrives, the second side is CLOSED.

If the second application closes immediately, the ACK of step 2 and the FIN of step 3 often travel in one segment, giving three segments in total. If both applications close at the same moment, both send FIN, both pass through CLOSING, and both end in TIME_WAIT.

An RST (reset) ends a connection abruptly, with no acknowledgement and no TIME_WAIT. A TCP sends one when it receives a segment for a connection that does not exist, such as a SYN to a port with nothing listening, or when an application aborts, for example by closing a socket with unread data in its receive buffer or with the SO_LINGER option set to zero. The other side's application sees "connection reset by peer".

TIME_WAIT and CLOSE_WAIT

TIME_WAIT

The side that closes first does not go straight to CLOSED. It waits in TIME_WAIT for twice the maximum segment lifetime (MSL), the longest time a segment is assumed to survive in the network. RFC 793 set the MSL at two minutes, which would make TIME_WAIT four; Linux uses a fixed 60 seconds. The wait does two jobs:

  • If the final ACK is lost, the other side resends its FIN. A side still in TIME_WAIT can acknowledge it again; one that had already forgotten the connection would answer with an RST.
  • Delayed segments from this connection die out before a new connection can reuse the same pair of addresses and ports.

TIME_WAIT is normal. It becomes a problem only on a machine that opens and closes many short connections to the same destination, such as a proxy or a service calling a database without a connection pool. Each connection holds one local port for 60 seconds, and with about 28,000 ports in Linux's default ephemeral range, a client making more than about 470 new connections a second to one destination address and port runs out. Reusing connections is the real fix. Linux's net.ipv4.tcp_tw_reuse lets new outgoing connections reuse ports in TIME_WAIT when TCP timestamps make it safe. The older tcp_tw_recycle setting broke clients behind NAT and was removed in Linux 4.12.

A common mix-up: net.ipv4.tcp_fin_timeout does not set the length of TIME_WAIT. It limits how long an orphaned connection, one whose application has already closed it, stays in FIN_WAIT_2.

CLOSE_WAIT

CLOSE_WAIT means the peer has closed and this side's application has not yet called close(). TCP will not leave it on its own. A server with thousands of connections in CLOSE_WAIT almost always has a bug: a code path that stops reading from a connection without closing it, often after an error. The connections hold file descriptors and memory until the process exits.

The TCP state diagram

RFC 9293 defines eleven states for a connection. These are the ones you see in the simulator and in ss -tan:

StateMeaningLeaves when
CLOSEDNo connection.The application connects or listens.
LISTENA server socket waiting for SYNs.A SYN arrives (a new connection is created; the listener stays).
SYN_SENTThe client sent SYN and waits for SYN-ACK.SYN-ACK arrives, or RST, or retries run out.
SYN_RCVDThe server sent SYN-ACK and waits for the ACK.The ACK arrives.
ESTABLISHEDOpen; data flows both ways.The application closes, or a FIN arrives.
FIN_WAIT_1Sent FIN, waiting for its ACK.The ACK arrives, or the peer's FIN.
FIN_WAIT_2Our FIN is acknowledged; waiting for the peer's FIN.The peer's FIN arrives.
CLOSE_WAITThe peer closed; our application has not.Our application closes.
CLOSINGBoth sent FIN at the same time.Our FIN is acknowledged.
LAST_ACKClosed second; waiting for the ACK of our FIN.The ACK arrives.
TIME_WAITClosed first; waiting out 2 × MSL.The timer ends.

When a handshake segment is lost

TCP keeps a retransmission timer for anything that needs an acknowledgement: SYNs, FINs and data. If the acknowledgement does not arrive in time, it sends the segment again and doubles the timeout. Before any round trip has been measured, RFC 6298 sets the timeout at one second. After that it is computed from measured round-trip times; Linux never goes below 200 ms.

Here is the simulator with the third segment of the handshake lost. The client thinks it is connected; the server is still in SYN_RCVD, so after one second it resends its SYN-ACK, and the client acknowledges it again.

Sent atDirectionFlagsSequenceAcknowledgementResult
0 msClient → serverSYN342874228 (0)–arrives
50 msServer → clientSYN, ACK1754884970 (0)342874229 (1)arrives
100 msClient → serverACK342874229 (1)1754884971 (1)arrives

If the client had sent data in the meantime, that data segment would also have carried the acknowledgement, and the server would have moved to ESTABLISHED without waiting.

A lost SYN is retried the same way. Linux sends up to six retries by default (net.ipv4.tcp_syn_retries), waiting 1, 2, 4, 8, 16 and 32 seconds before each and 64 seconds after the last, so connect() fails with "connection timed out" about 127 seconds after it started, unless the application set a shorter timeout. The server retries a SYN-ACK up to five times (tcp_synack_retries).

SYN floods and SYN cookies

Between the SYN-ACK and the final ACK, the server has to remember the connection: the client's address and port, both sequence numbers and the options. Linux keeps these half-open connections in a SYN queue, and completed ones in an accept queue until the application calls accept(). The accept queue's size is the backlog argument to listen(), capped by net.core.somaxconn, whose default rose from 128 to 4,096 in Linux 5.4.

A SYN flood sends SYNs as fast as possible, usually from forged source addresses, and never completes the handshake. The SYN queue fills with connections that will never finish, and real clients are turned away. The attack became widely known in 1996.

SYN cookies, devised by Daniel J. Bernstein and Eric Schenk in 1996, remove the need to store anything. When the queue is full, the server encodes the connection's details, a timestamp and a secret into the initial sequence number of its SYN-ACK and forgets the connection. A real client's ACK carries that number plus one, so the server can check it and rebuild the connection from it. The cost is that only a few options fit in the cookie. Linux turns SYN cookies on automatically when the SYN queue overflows (net.ipv4.tcp_syncookies = 1). RFC 4987 surveys this and other defences.

Connection refused, timed out, reset

The handshake explains the three connection errors programs report most often.

ErrorWhat happened on the wireUsual cause
Connection refused (ECONNREFUSED)The SYN was answered with an RST.Nothing listening on that port, or the service listening on a different address, such as 127.0.0.1 only.
Connection timed out (ETIMEDOUT)No answer to the SYN, despite retries.A firewall or security group dropping packets, a wrong address, or the host is down.
Connection reset by peer (ECONNRESET)An RST arrived on an open connection.The peer process crashed or aborted, a load balancer or firewall dropped its idle state, or a write reached a connection the peer had already closed.
Broken pipe (EPIPE)A write after the connection was reset or shut down.Writing to a connection the peer reset earlier.

To see the handshake on a real machine, run sudo tcpdump -n 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0' and make a request; to count connections by state, run ss -tan state time-wait | wc -l or plain ss -tan.

The TCP handshake in interviews

The handshake is a standard question in networking courses and backend interviews, usually followed by one of these:

  • Draw it with sequence numbers. SYN seq = x; SYN-ACK seq = y, ack = x + 1; ACK ack = y + 1. Mention that SYN and FIN each use one sequence number.
  • Why not two? Old duplicate SYNs, and the need for both sides to have their own starting number acknowledged.
  • Why random ISNs? Old segments from earlier connections, and attackers forging connections.
  • Who ends up in TIME_WAIT, and why? The side that closes first, so it can acknowledge a resent FIN and let old segments expire.
  • What happens when you type a URL? DNS lookup, then this handshake, then TLS, then the HTTP request. Know roughly how many round trips each takes.
  • How do you defend against a SYN flood? SYN cookies, larger queues, and filtering upstream.

TCP handshake questions and answers

What is the TCP three-way handshake?

It is the exchange that opens every TCP connection. The client sends a SYN segment carrying its initial sequence number, the server replies with a SYN-ACK carrying its own initial sequence number and acknowledging the client’s, and the client replies with an ACK. After these three segments both sides know the other’s starting sequence number and have seen that the other can send and receive.

Why does TCP use a three-way handshake and not two?

Each side has to choose a starting sequence number and hear the other side confirm it. The SYN and the server’s acknowledgement of it settle the client’s number; the server’s SYN and the client’s acknowledgement settle the server’s. The server can send its SYN and its acknowledgement in one segment, which gives three segments. RFC 793 gives the main reason for the third: it stops an old, delayed SYN from opening a connection the client never wanted, because the client can refuse to acknowledge it.

What are SYN, ACK, FIN and RST in TCP?

They are flag bits in the TCP header. SYN asks to synchronize sequence numbers and opens a connection. ACK says the acknowledgement number field is valid; every segment after the first SYN carries it. FIN says the sender has no more data to send. RST aborts the connection at once, or answers a segment that does not belong to any connection.

How many packets does it take to close a TCP connection?

Usually four: a FIN from the side that closes first, an ACK of it, a FIN from the other side when its application closes, and a final ACK. When the second side closes straight away, its ACK and FIN are often sent in one segment, which makes three. A connection can also be aborted with a single RST.

What is TIME_WAIT and why does it last so long?

TIME_WAIT is the state the side that closed first enters after acknowledging the other side’s FIN. It waits so that, if that last ACK is lost, it can acknowledge the resent FIN, and so that delayed segments from the old connection die out before the same addresses and ports are used again. RFC 9293 sets the wait at twice the maximum segment lifetime; Linux uses a fixed 60 seconds.

What is the difference between CLOSE_WAIT and TIME_WAIT?

CLOSE_WAIT means the peer has closed its side and this side’s application has not yet called close(). Many sockets stuck in CLOSE_WAIT usually mean an application is not closing connections. TIME_WAIT is the normal last state of the side that closed first, and it ends on its own after a timeout.

What happens if the SYN or SYN-ACK is lost?

The sender retransmits it after a timeout. RFC 6298 sets the first timeout at one second and doubles it after each retry. Linux retries a SYN six times by default, so connect() gives up with "connection timed out" after about 127 seconds; the server retries a SYN-ACK five times.

What does "connection refused" mean?

The client’s SYN reached the host, but nothing was listening on that port, so the host’s TCP replied with an RST. A timeout instead of a refusal usually means the SYN or the reply was dropped on the way, often by a firewall.

What is a SYN flood?

A denial-of-service attack that sends many SYN segments, often from forged addresses, and never completes the handshake. Each one leaves a half-open connection in the server’s SYN queue until it times out. SYN cookies defend against it by encoding the connection’s details in the server’s initial sequence number, so the server stores nothing until the final ACK arrives.

Further reading

Published Updated