SO_LINGER Demystified: Controlling TCP Close Behavior in Linux
This article explains the SO_LINGER socket option's three closure modes, demonstrates why SO_LINGER=0 sends RST instead of FIN, and outlines Linux's preferred solutions for TIME_WAIT accumulation—connection reuse, tcp_tw_reuse, tcp_fin_timeout, and port range expansion—over abrupt connection termination.
1. Issue
TCP connection closure follows a four-way handshake where the active closer enters TIME_WAIT for 2MSL before releasing resources. Linux provides the SO_LINGER socket option that alters this standard flow.
2. Dialogue: Understanding SO_LINGER
2.1 Normal TCP Close (without SO_LINGER)
Standard TCP performs a graceful close:
Application calls close() Kernel sends remaining data in send buffer, then sends FIN Peer acknowledges with ACK Peer sends its FIN Local end acknowledges with ACK Connection enters
TIME_WAIT close()does not mean the connection is fully closed; the kernel takes over remaining send work.
2.2 Problems with Early BSD Socket Design
Application cannot know if data was actually sent successfully
Process exit leaves resource cleanup uncertain
Some programs want to terminate connections immediately.
2.3 SO_LINGER Design Goal
BSD introduced SO_LINGER to let applications control close() semantics (immediate return vs. wait for send completion). The option uses a struct linger:
struct linger {
int l_onoff;
int l_linger;
};It provides three closure strategies:
Default (off): Kernel graceful close linger > 0: Blocking close() waits for send completion linger = 0: Immediate RST sent
When SO_LINGER > 0
close()
↓
Wait up to N seconds
↓
Until:
- Data sent
- ACK received
- TimeoutOn timeout, returns -1 with errno set to ETIMEDOUT.
When SO_LINGER = 0
close()
↓
Send RST
↓
Discard send buffer
↓
Connection destroyed immediatelyNo FIN is sent, no TIME_WAIT state entered. This is a deliberate abort mechanism.
2.4 Why Some Libraries Default to SO_LINGER=0
Most libraries do not enable SO_LINGER by default. When seen, it is usually SO_LINGER=0 because RST releases resources faster than FIN, avoiding TIME_WAIT accumulation.
2.5 Better Solutions for TIME_WAIT Accumulation
Linux offers superior approaches at three layers, in priority order:
Architecture optimization > TCP parameter tuning > Port/connection reuse > (rarely) abort closeKey Insight: TIME_WAIT Is Not a Bug
TIME_WAITguarantees two correctness properties:
Delayed packets from old connections do not corrupt new ones
Lost final ACK can be retransmitted
Eliminating TIME_WAIT entirely is wrong; the goal is to prevent it from exhausting resources.
Layer 1: Avoid Active Close on Server
Let clients initiate closure so TIME_WAIT appears on client side, removing server pressure.
Layer 2: Connection Reuse
Excessive TIME_WAIT often signals too many short connections. Fundamental fix: reduce connection creation via:
HTTP KeepAlive
HTTP/2
Connection pools
Layer 3: Linux Kernel Tuning
tcp_tw_reuse
net.ipv4.tcp_tw_reuse = 1Allows TIME_WAIT sockets to be reused for new connections when the new connection's timestamp exceeds the old one's (PAWS check).
tcp_fin_timeout
net.ipv4.tcp_fin_timeoutDefault 60 seconds; can be lowered but not too small. Accelerates release of orphan sockets stuck in FIN_WAIT_2.
Expand Port Range
net.ipv4.ip_local_port_rangeWider range reduces port exhaustion from TIME_WAIT sockets.
Layer 4: Modern Protocols
Protocols like QUIC bypass the TIME_WAIT issue entirely.
Summary of Correct Strategy
Reduce short connections
Reuse connections
Enable tcp_tw_reuse Expand port range
3. Experiment
Code to set SO_LINGER=0:
void set_so_linger_zero(int sockfd) {
struct linger linger_opt;
linger_opt.l_onoff = 1; /* Enable SO_LINGER */
linger_opt.l_linger = 0; /* Linger time = 0 */
if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &linger_opt,
sizeof(linger_opt)) < 0) {
perror("setsockopt(SO_LINGER)");
exit(EXIT_FAILURE);
}
}Client Sets SO_LINGER=0 (tcpdump)
23:51:38.414443 IP 127.0.0.1.40854 > 127.0.0.1.8888: Flags [S], seq 2975794497, win 43690, options [mss 65495,sackOK,TS val 855851546 ecr 0,nop,wscale 10], length 0
23:51:38.414452 IP 127.0.0.1.8888 > 127.0.0.1.40854: Flags [S.], seq 1521860988, ack 2975794498, win 43690, options [mss 65495,sackOK,TS val 855851546 ecr 855851546,nop,wscale 10], length 0
23:51:38.414458 IP 127.0.0.1.40854 > 127.0.0.1.8888: Flags [.], ack 1521860989, win 43, options [nop,nop,TS val 855851546 ecr 855851546], length 0
23:51:38.414492 IP 127.0.0.1.40854 > 127.0.0.1.8888: Flags [P.], seq 2975794498:2975794504, ack 1521860989, win 43, options [nop,nop,TS val 855851546 ecr 855851546], length 6
23:51:38.414495 IP 127.0.0.1.8888 > 127.0.0.1.40854: Flags [.], ack 2975794504, win 43, options [nop,nop,TS val 855851546 ecr 855851546], length 0
23:51:38.414513 IP 127.0.0.1.8888 > 127.0.0.1.40854: Flags [P.], seq 1521860989:1521861006, ack 2975794504, win 43, options [nop,nop,TS val 855851546 ecr 855851546], length 17
23:51:38.414517 IP 127.0.0.1.40854 > 127.0.0.1.8888: Flags [.], ack 1521861006, win 43, options [nop,nop,TS val 855851546 ecr 855851546], length 0
23:51:38.414520 IP 127.0.0.1.8888 > 127.0.0.1.40854: Flags [P.], seq 1521861006:1521861012, ack 2975794504, win 43, options [nop,nop,TS val 855851546 ecr 855851546], length 6
23:51:38.414522 IP 127.0.0.1.40854 > 127.0.0.1.8888: Flags [.], ack 1521861012, win 43, options [nop,nop,TS val 855851546 ecr 855851546], length 0
23:51:38.414539 IP 127.0.0.1.40854 > 127.0.0.1.8888: Flags [R.], seq 2975794504, ack 1521861012, win 43, options [nop,nop,TS val 855851546 ecr 855851546], length 0Client sends final RST packet.
Server Sets SO_LINGER=0 (tcpdump)
23:57:33.950675 IP 127.0.0.1.45936 > 127.0.0.1.8888: Flags [S], seq 3408802811, win 43690, options [mss 65495,sackOK,TS val 856207082 ecr 0,nop,wscale 10], length 0
23:57:33.950680 IP 127.0.0.1.8888 > 127.0.0.1.45936: Flags [S.], seq 2475716703, ack 3408802812, win 43690, options [mss 65495,sackOK,TS val 856207082 ecr 856207082,nop,wscale 10], length 0
23:57:33.950687 IP 127.0.0.1.45936 > 127.0.0.1.8888: Flags [.], ack 2475716704, win 43, options [nop,nop,TS val 856207082 ecr 856207082], length 0
23:57:33.950707 IP 127.0.0.1.45936 > 127.0.0.1.8888: Flags [P.], seq 3408802812:3408802818, ack 2475716704, win 43, options [nop,nop,TS val 856207082 ecr 856207082], length 6
23:57:33.950710 IP 127.0.0.1.8888 > 127.0.0.1.45936: Flags [.], ack 3408802818, win 43, options [nop,nop,TS val 856207082 ecr 856207082], length 0
23:57:33.950763 IP 127.0.0.1.8888 > 127.0.0.1.45936: Flags [R.], seq 2475716704, ack 3408802818, win 43, options [nop,nop,TS val 856207082 ecr 856207082], length 0Server sends final RST packet.
4. Kernel Implementation (Linux 5.15.198)
SO_LINGER Handling Flow
User space
│ setsockopt(SOL_SOCKET, SO_LINGER, &linger, ...)
▼
net/core/sock.c: sock_setsockopt()
│ case SO_LINGER
│ ├─ Set sock_flag(sk, SOCK_LINGER)
│ └─ Set sk→sk_lingertime
▼
User space
│ close(sockfd)
▼
net/ipv4/tcp.c: tcp_close()
│ Check sock_flag(sk, SOCK_LINGER)
│
├─ l_linger = 0 → tcp_send_active_reset() [hard close]
│
└─ l_linger > 0 → Wait for timeout or data senttcp_tw_reuse Check Flow
New connection (connect())
│
▼
Check if port occupied by TIME_WAIT
│
▼
net/ipv4/tcp_ipv4.c: tcp_twsk_unique()
│
├─ Check tw_substate == TCP_FIN_WAIT2
│ └─ Yes → Reuse forbidden
│
├─ Check tcp_tw_reuse value
│ │
│ ├─ 0 → Reuse forbidden
│ │
│ └─ 2 → Check loopback interface
│ └─ Non-loopback → Reuse forbidden
│
├─ Check timestamp (PAWS)
│ └─ Timestamp unsafe → Reuse forbidden
│
└─ Adjust sequence number
└─ write_seq = old_seq + 65535 + 2tcp_fin_timeout Timeout Flow
Enter FIN_WAIT2 state
│
▼
include/net/tcp.h: tcp_fin_time()
│
├─ Use tp→linger2 (if set)
│
└─ Use sysctl_tcp_fin_timeout
│
├─ Ensure minimum >= 3.5 * RTO
│
└─ Return timeout value
│
▼
net/ipv4/tcp.c: tcp_close()
│
├─ tmo > TCP_TIMEWAIT_LEN (60s)
│ └─ Set keepalive timer
│
└─ tmo <= 60s
└─ tcp_time_wait(sk, TCP_FIN_WAIT2, tmo)
│
└─ Create TIME_WAIT socket
│
└─ Schedule timeout timerSigned-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
