웹 서비스를 운영하다가 갑자기 트래픽이 몰리면 서버 CPU나 메모리는 널널한데 새로운 사용자의 접속이 먹통이 되는 황당한 상황을 겪곤 합니다.
서버에 들어가서 네트워크 연결 상태를 확인해 보면 수만 개의 연결이 TIME_WAIT 상태로 쌓여 있거나, 대기열이 꽉 차서 패킷이 버려지는 경우가 대부분입니다.
리눅스 서버에서 왜 이런 현상이 발생하는지, 그리고 대규모 동시 접속자를 안정적으로 수용하기 위해 커널 네트워크 파라미터(sysctl)를 어떻게 튜닝해야 하는지 핵심 내용을 정리해 드립니다.
TIME_WAIT 소켓이 폭증하는 이유
TCP 통신은 연결을 끊을 때 양방향 확인 절차(4-Way Handshake)를 거칩니다. 이때 연결을 먼저 끊겠다고 요청한 쪽(Active Closer)은 상대방이 보낸 마지막 패킷이 유실되거나 지연되어 늦게 도착할 경우를 대비해 일정 시간 연결을 닫지 않고 대기 상태로 머무르는데, 이것이 바로 TIME_WAIT입니다.
-
포트 고갈(Ephemeral Port Exhaustion): 클라이언트나 API 게이트웨이, 리버스 프록시(Nginx 등)가 백엔드 서버로 단기 요청을 쉴 새 없이 보내고 끊으면, 로컬 포트가 TIME_WAIT 상태로 묶이면서 새로운 연결을 맺을 포트가 고갈됩니다.
-
기본 대기 시간: 리눅스 커널에서 TIME_WAIT 대기 시간은 통상 60초(2MSL)로 고정되어 있어 단기간에 요청이 몰리면 소켓이 기하급수적으로 누적됩니다.
핵심 sysctl 파라미터 튜닝 가이드
서버의 네트워크 처리량을 늘리고 소켓 자원을 빠르게 재활용하기 위해 /etc/sysctl.conf 파일에서 반드시 조정해야 하는 핵심 설정값들입니다.
① TIME_WAIT 소켓 재사용 설정: net.ipv4.tcp_tw_reuse
외부로 나가는 아웃바운드 연결(예: 리버스 프록시에서 백엔드로 연결 시)에서 TIME_WAIT 상태의 소켓을 안전하게 재사용할 수 있도록 허용합니다.
Bash
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
※ 주의: 과거에 사용되던 tcp_tw_recycle 파라미터는 NAT 환경에서 패킷 드롭 문제를 일으켜 리눅스 커널 4.12 버전 이후 완전히 삭제되었습니다. 현재는 반드시 tcp_tw_reuse = 1과 타임스탬프(tcp_timestamps = 1) 조합만 사용해야 합니다.
② 연결 대기열(Backlog) 확장: net.core.somaxconn & net.ipv4.tcp_max_syn_backlog
클라이언트의 3-Way Handshake 과정에서 연결 요청을 임시로 담아두는 큐(Queue)의 크기입니다. 기본값이 작으면 트래픽 급증 시 대기열이 넘쳐 패킷이 유실됩니다.
Bash
# 완전한 3-Way Handshake를 마친 연결이 애플리케이션의 accept()를 기다리는 큐 크기
net.core.somaxconn = 65535
# TCP 3-Way Handshake 중 첫 번째 SYN 패킷을 받고 ACK를 기다리는 반개방(Half-open) 큐 크기
net.ipv4.tcp_max_syn_backlog = 65535
# 네트워크 인터페이스 카드(NIC)가 커널로 패킷을 넘기기 전 대기하는 큐 크기
net.core.netdev_max_backlog = 65535
③ 로컬 포트 범위 확장: net.ipv4.ip_local_port_range
아웃바운드 연결에 할당할 수 있는 가용 포트의 범위를 최대로 늘려 포트 고갈을 방지합니다.
Bash
net.ipv4.ip_local_port_range = 1024 65535
④ 파일 디스크립터(File Descriptor) 한도 확장: fs.file-max
리눅스에서는 모든 네트워크 소켓을 하나의 ‘파일’로 취급합니다. 동시 접속자 수가 많아지면 시스템 전체와 사용자 프로세스가 열 수 있는 파일 개수의 한계를 늘려주어야 Too many open files 에러를 막을 수 있습니다.
Bash
# 시스템 전체에서 열 수 있는 최대 파일 디스크립터 수
fs.file-max = 2097152
프로세스별 제한은 /etc/security/limits.conf 파일에 함께 설정해 주어야 합니다.
Plaintext
* soft nofile 65535
* hard nofile 65535
⑤ 비정상 연결 조기 정리: net.ipv4.tcp_fin_timeout
연결 종료 과정에서 FIN 패킷을 보낸 후 소켓이 닫히기까지 기다리는 타임아웃 시간을 기본 60초에서 단축하여 소켓 메모리를 빠르게 회수합니다.
Bash
net.ipv4.tcp_fin_timeout = 15
설정 적용 및 확인 방법
수정한 설정값을 서버 재부팅 없이 즉시 커널에 반영하려면 터미널에서 다음 명령어를 실행합니다.
Bash
# sysctl 설정 즉시 적용
sudo sysctl -p
# 현재 소켓 상태 확인 (TIME_WAIT 개수 확인)
ss -s
# 또는
netstat -napo | awk '/^tcp/ {++S[$6]} END {for(a in S) print a, S[a]}'
애플리케이션 및 아키텍처 관점의 보완책
커널 튜닝만으로 소켓 폭증을 완벽히 막는 데는 한계가 있습니다. 시스템 설계 차원에서 다음 사항을 병행하는 것이 가장 근본적인 해결책입니다.
-
HTTP Keep-Alive 활성화: 프록시(Nginx, Envoy)와 백엔드 서버 간의 연결을 매번 새로 맺지 않고 연결을 재사용(Connection Pooling)하도록 설정하면 TIME_WAIT 소켓 발생 자체를 90% 이상 줄일 수 있습니다.
-
애플리케이션 백로그 값 동기화: Nginx의
listen디렉티브나 Node.js/Tomcat의backlog설정값도somaxconn크기(예: 65535)에 맞추어 함께 올려주어야 커널 대기열 확장의 효과를 온전히 누릴 수 있습니다.