웹서버 5,000 연결까지는 잘 받다가 그 이상에서 갑자기 멈추는 경험이 있었다면, 거의 모든 경우 커널의 기본값이 원인입니다. Linux는 데스크톱부터 서버까지 폭넓게 쓰이도록 보수적인 기본값을 설정해두는데, 일본 IDC의 고성능 서버에서는 이 값을 풀어줘야 본래 처리량이 나옵니다.
어디부터 봐야 할까
네트워크 패킷이 서버에 도달해서 애플리케이션까지 가는 경로는 4계층입니다.
| 계층 | 병목 발생 시 증상 | 핵심 파라미터 |
|---|---|---|
| NIC | 패킷 드롭, RX/TX 카운터 증가 | ring buffer, RSS 큐 |
| 커널 TCP/IP | accept 큐 풀, conntrack 가득 | somaxconn, conntrack_max |
| 연결 관리 | TIME_WAIT 누적, 임시 포트 고갈 | tcp_tw_reuse, port_range |
| 애플리케이션 | 워커 부족, 파일 디스크립터 부족 | nofile, worker_connections |
이 글에서는 가운데 두 계층을 중심으로, 안전한 값을 제시합니다.
1단계 — 현재 상태 진단
설정을 만지기 전에 무엇이 부족한지 확인합니다.
# 패킷 드롭 발생 여부
ip -s link show eth0 | grep -A1 RX
netstat -s | grep -E "drop|overflow"
# accept 큐 오버플로
ss -s
nstat -a | grep -i listen
# conntrack 사용량
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# TIME_WAIT 개수
ss -tan | awk '{print $1}' | sort | uniq -c
“ListenOverflows”나 “drops” 카운터가 증가 중이라면 그 부분의 튜닝이 필요합니다. 모든 카운터가 0이고 처리량이 부족하다면 NIC나 애플리케이션 쪽을 봐야 합니다.
2단계 — TCP 백로그 / accept 큐
somaxconn은 수락(accept) 대기 큐의 최대 크기입니다. 기본값 4096이 작아 보이지 않지만, Nginx의 listen ... backlog=도 이 값에 의해 제한되므로 실제론 자주 부족합니다.
# /etc/sysctl.d/99-network-tune.conf
# accept 대기 큐
net.core.somaxconn = 8192
# SYN 백로그
net.ipv4.tcp_max_syn_backlog = 8192
# 연결 추적 테이블
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_buckets = 262144
Nginx 측에서도 listen 80 backlog=8192; 처럼 함께 명시해야 의미가 있습니다.
3단계 — TIME_WAIT와 임시 포트
높은 동시 연결, 짧은 keep-alive 환경에서는 TIME_WAIT 상태 소켓이 빠르게 누적됩니다. 임시 포트가 고갈되면 새 연결이 안 됩니다.
# TIME_WAIT 재사용 (NAT 환경에서도 안전)
net.ipv4.tcp_tw_reuse = 1
# FIN_WAIT 짧게
net.ipv4.tcp_fin_timeout = 15
# 임시 포트 범위 확장
net.ipv4.ip_local_port_range = 10240 65535
⚠️ 사용 금지
net.ipv4.tcp_tw_recycle— 4.12 커널에서 제거됨. NAT 환경에서 연결 실패의 원인이 됩니다.
4단계 — 송수신 버퍼
긴 RTT(예: 글로벌 사용자) 환경에서 처리량이 잘 안 나온다면 버퍼 크기가 작은 것일 수 있습니다.
# 최대 소켓 버퍼 (16MB)
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# TCP 자동 튜닝 한계
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# 백로그 큐 (소프트IRQ가 처리 못한 패킷)
net.core.netdev_max_backlog = 5000
대역폭 곱 RTT(BDP)가 8MB를 넘는 경우(예: 100Mbps × 100ms RTT = 1.25MB는 OK이지만 1Gbps × 100ms = 12MB)에는 위 16MB로도 부족할 수 있습니다.
5단계 — 혼잡 제어 알고리즘 (BBR)
BBR은 패킷 손실이 아니라 대역폭과 RTT 측정으로 혼잡을 판단합니다. 한국-일본처럼 짧은 RTT, 또는 글로벌 긴 경로 양쪽 모두에서 cubic 대비 좋은 결과를 보이는 경우가 많습니다.
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
활성 후 확인:
sysctl net.ipv4.tcp_congestion_control
# net.ipv4.tcp_congestion_control = bbr
6단계 — keepalive 짧게
방화벽이나 LB가 idle 연결을 끊는 환경에서는 keepalive를 짧게 잡아 미리 dead connection을 정리합니다.
net.ipv4.tcp_keepalive_time = 600 # 10분 (기본 7200초 = 2시간)
net.ipv4.tcp_keepalive_intvl = 60
net.ipv4.tcp_keepalive_probes = 3
7단계 — SYN Flood / 보안 관련
DDoS 환경에서 동작하는 서버라면 다음 값을 함께 설정합니다.
# SYN Cookie (메모리 고갈 방어)
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 2
# 비정상 패킷 무시
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# IP 스푸핑 방어 (Reverse Path Filter)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# ICMP redirect 무시
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
DDoS 방어 원리에 대한 더 자세한 설명은 이 글을 참고하세요.
8단계 — 애플리케이션 한계 (nofile)
sysctl만 풀어도 애플리케이션 한계가 그대로면 의미가 없습니다.
# /etc/security/limits.conf
* soft nofile 1048576
* hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576
systemd 서비스 단위로도 적용:
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=1048576
sudo systemctl daemon-reload
sudo systemctl restart nginx
확인:
cat /proc/$(pidof nginx)/limits | grep "open files"
한 번에 적용하는 권장 프로필
웹/API 서버의 무난한 시작점입니다. 워크로드에 따라 추가 조정이 필요합니다.
# /etc/sysctl.d/99-server-tune.conf
# === 백로그 / 큐 ===
net.core.somaxconn = 8192
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_max_syn_backlog = 8192
# === TIME_WAIT / 포트 ===
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.ip_local_port_range = 10240 65535
# === 버퍼 ===
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# === 혼잡 제어 ===
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# === keepalive ===
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 60
net.ipv4.tcp_keepalive_probes = 3
# === conntrack ===
net.netfilter.nf_conntrack_max = 1048576
# === 보안 ===
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
sudo sysctl --system
sudo sysctl -p /etc/sysctl.d/99-server-tune.conf
안전한 적용 절차
운영 서버에 한 번에 적용하지 마세요. 다음 절차를 권장합니다.
- 개발/스테이징 서버에서 먼저 동일 워크로드로 부하 테스트(wrk, k6 등)
- 변경 전후 지표 비교 — RPS, 응답 시간, CPU, 메모리, 패킷 드롭
- 운영 서버에 점진 적용 — 한 번에 한두 항목씩, 24시간 관찰
- 롤백 계획 준비 — 적용한 파일을
.disabled로 바꾸면 다음 부팅 시 무효화됨
운영 중 적용은 그라데이션이 핵심입니다. 일이 잘못되면 새벽 3시에 누군가 깨어나야 하므로 느려도 안전한 방법을 택하세요.
측정 도구
튜닝 효과는 반드시 숫자로 확인해야 합니다.
| 도구 | 용도 |
|---|---|
wrk / wrk2 |
HTTP 부하 생성 |
k6 |
시나리오 기반 부하 테스트 |
iperf3 |
순수 네트워크 처리량 측정 |
mpstat / pidstat |
CPU·프로세스별 부하 |
ss -s |
소켓 통계 요약 |
nstat -a |
모든 카운터 dump |
전체적인 모니터링 구성은 Netdata 가이드에 정리해두었습니다.
마무리
커널 튜닝은 “블랙박스에서 손가락 굴리는” 작업이 아니라, 카운터 → 가설 → 측정 → 적용의 루프입니다. 위 권장 프로필을 그대로 가져가도 안전하지만, 패킷 드롭이나 백로그 오버플로 카운터가 증가하지 않는다면 굳이 만질 필요가 없는 경우가 많습니다.
TCP-80.NET의 전용서버는 NIC 인터럽트 분산까지 사전 설정해 출고합니다. 추가 튜닝에 대한 문의는 @tcp80net으로 부탁드립니다.