← 아티클 목록

TIME_WAIT 소켓이 너무 많을 때 — 원인과 대처

2028-01-24#networking#tcp#트러블슈팅

부하가 몰리면 connect: cannot assign requested address 같은 에러가 뜨고, ss로 보면 TIME_WAIT 소켓이 수만 개 쌓여 있습니다. TIME_WAIT는 TCP가 연결을 깔끔하게 닫기 위한 정상 상태지만, **연결을 능동으로 닫는 쪽(주로 클라이언트나 프록시)**에 과하게 쌓이면 로컬 포트가 고갈돼 새 연결을 못 맺습니다.

진단 — 얼마나 쌓였나

상태별 소켓 개수를 셉니다.

로컬 터미널
ss -tan state time-wait | wc -l        # TIME_WAIT 소켓 개수
ss -s                                  # 상태별 요약
OUTPUT
TCP:   52310 (estab 210, closed 51800, timewait 51020/0), ports 0

timewait이 수만 단위이고 ip_local_port_range로 잡힌 가용 포트(보통 약 28000개)에 근접하면 포트 고갈 구간입니다. 가용 포트 범위도 확인합니다.

로컬 터미널
cat /proc/sys/net/ipv4/ip_local_port_range

개념 — 왜 생기고 누구에게 쌓이나

TIME_WAIT는 연결을 먼저 닫은 쪽에서 약 60초(2*MSL) 유지됩니다. 지연 도착한 패킷이 다음 연결을 오염시키는 걸 막는 안전장치라 함부로 없애면 안 됩니다. 핵심은 누가 먼저 닫느냐입니다. 서버가 Connection: close로 매 요청 후 끊으면 TIME_WAIT는 클라이언트가 아니라 서버 쪽에 쌓입니다.

패턴TIME_WAIT 누적 위치1순위 대처
매 요청마다 새 연결(단명 커넥션)능동으로 닫는 쪽keep-alive·커넥션 풀
백엔드로 짧은 연결 폭주(프록시)프록시업스트림 keep-alive
동일 목적지로 대량 아웃바운드발신 서버tcp_tw_reuse

대처 — 근본부터, 커널은 보조

1. 애플리케이션: 연결 재사용이 정답

가장 확실한 해결은 연결을 재사용하는 것입니다. HTTP keep-alive를 켜고 커넥션 풀을 쓰면 매 요청마다 새 소켓을 만들고 닫지 않으므로 TIME_WAIT 자체가 거의 생기지 않습니다. nginx 업스트림이라면 keepalive 지시자를 추가합니다.

2. 커널 튜닝 (발신 측에 한해)

같은 목적지로 나가는 아웃바운드가 많다면 tcp_tw_reuse로 TIME_WAIT 소켓을 새 연결에 안전하게 재활용할 수 있습니다(타임스탬프 기반).

로컬 터미널
sysctl -w net.ipv4.tcp_tw_reuse=1          # 발신 연결에 한해 재사용
sysctl -w net.ipv4.ip_local_port_range="10000 65535"   # 포트 범위 확대

tcp_tw_reuse는 능동으로 연결을 여는 쪽(클라이언트)에만 효과가 있습니다. 절대 tcp_tw_recycle은 켜지 마세요. NAT 뒤의 클라이언트 연결을 깨뜨려 오래전 커널에서 제거된 위험한 옵션입니다.

체크리스트

로컬 터미널
ss -tan state time-wait | wc -l        # 규모 확인
ss -s                                  # 상태 분포
cat /proc/sys/net/ipv4/ip_local_port_range   # 가용 포트
# 1순위: keep-alive·커넥션 풀 적용
# 발신 다량일 때만: sysctl net.ipv4.tcp_tw_reuse=1

TIME_WAIT는 "줄이는" 문제가 아니라 "연결을 재사용해서 안 만드는" 문제입니다.


TCP 상태 머신과 소켓 진단을 직접 패킷 단위로 따라가 보고 싶다면 회원가입 없이 무료로 네트워크 트랙에서 익힐 수 있습니다.