1. Library
  2. Computer Networks
  3. Tcp and Udp
  4. Tcp

Updated 9 hours ago

Every developer has seen it: "Connection reset by peer." The error that tells you almost nothing. Something went wrong. The other side hung up. But how they hung up—politely or abruptly—changes everything about what happened and how to fix it.

TCP has two ways to end a connection. FIN says "I'm done talking, but I'm still listening." RST says "This conversation never happened."

FIN: The Graceful Goodbye

When you close a socket normally, TCP initiates what's called a four-way handshake. It's exactly what it sounds like: both sides formally agree to end the conversation.

  1. You send FIN: "I have nothing more to say."
  2. They send ACK: "Got it."
  3. They send FIN: "I have nothing more to say either."
  4. You send ACK: "Got it. We're done."

Why four steps instead of two? Because TCP is full-duplex—data flows independently in both directions. When you send FIN, you're only closing your direction. The other side might still have data to send. They acknowledge your FIN, finish sending their data, then send their own FIN when ready.

The connection enters a half-closed state between steps 2 and 3. You can't send anymore, but you can still receive. This is TCP being polite: even though you're done talking, you wait to hear if they have anything else to say.

In practice, steps 2 and 3 often combine into a single packet (ACK+FIN together), making it look like a three-way close. But the logic remains: each direction closes independently.

RST: The Line Goes Dead

RST is different. When a connection receives RST, it's over. Immediately. No acknowledgment, no handshake, no chance to finish what you were doing. Pending data? Gone. Buffers? Cleared. The connection ceases to exist as if it never was.

TCP sends RST in specific situations:

Nothing's listening. You try to connect to port 8080, but no service is running there. The operating system responds with RST: "Nobody home." This is how port scanners identify closed ports—they get RST instead of SYN-ACK.

The connection doesn't exist. A packet arrives for a connection that was already closed, or that never existed on this machine. RST says: "I don't know what you're talking about."

The application crashed. Or explicitly aborted the connection. When software dies unexpectedly, the OS cleans up by sending RST to any open connections.

Resources exhausted. The system can't maintain the connection anymore. RST is the emergency release valve.

Why "Connection Reset by Peer" Haunts You

When you see this error, you received an RST. Something on the other end—or between you and the other end—decided this connection needed to die immediately. The frustrating part: the error doesn't tell you who sent it or why.

The Firewall That Lies

Here's something unsettling: firewalls often send RST packets pretending to be from your destination. You think the server rejected your connection, but actually an invisible middlebox blocked you.

Stateful firewalls maintain tables of active connections. When a connection violates policy, goes idle too long, or looks suspicious, the firewall forges an RST from the "server" and sends it to you. The server never knew you existed.

Packet captures reveal the truth. If RST comes from an IP that isn't your destination, or has TTL values that don't match other packets from that host, you've found your invisible blocker.

NAT Timeout: The Silent Killer

NAT devices (your router, cloud load balancers, corporate firewalls) track connections in state tables. These tables have timeouts—often 5-30 minutes for idle TCP connections.

Here's the trap: your application thinks the connection is alive. The server thinks the connection is alive. But the NAT device in between has forgotten it exists. When either side sends the next packet, the NAT doesn't recognize it. Depending on configuration, it either drops the packet silently or sends RST.

This is why long-lived connections—database pools, message queues, SSH sessions—randomly die after periods of inactivity. The connection was healthy; the middlebox just forgot about it.

Load Balancers Under Pressure

Load balancers send RST when backend servers disappear, health checks fail, or connection limits are exceeded. These resets often correlate with deployments (backends cycling) or traffic spikes (hitting limits).

If you see intermittent resets that cluster around specific times, check what else was happening in your infrastructure at those moments.

Debugging RST: Where to Look

Packet captures on both ends reveal who actually sent RST. Compare source IPs, TTLs, and timing. If RST comes from somewhere unexpected, you've found your culprit.

Application logs show whether your software intentionally aborted the connection. Many applications log reasons for sending RST: protocol violations, security policy, timeout exceeded.

Firewall and load balancer logs expose policy violations, connection limits, and health check failures that trigger RST.

TCP keepalive prevents NAT timeout deaths. Enable keepalive and set intervals shorter than the shortest timeout in your network path. The probes reset the idle timer before middleboxes forget about you.

FIN vs. RST: Choosing Your Exit

You rarely choose explicitly. Your code's socket handling determines which TCP uses:

Normal close (calling close() on a socket): TCP sends FIN and completes the four-way handshake. Data in flight gets delivered. Both sides agree the conversation is over.

Abortive close (setting SO_LINGER to zero before closing): TCP sends RST immediately. No handshake, no waiting, no guarantee that pending data arrives.

Use normal close for almost everything. Reserve abortive close for emergencies: connections that have become invalid, resources that must be freed immediately, or detected security violations where you want to terminate without negotiation.

Frequently Asked Questions About TCP FIN and RST

Was this page helpful?

😔
🤨
😃