infra
Platform

모듈 맵

[Network] netstat과 ss 명령어로 커넥션 상태(ESTABLISHED 등) 분석

0 / 35 완료

펼치기
0 / 35 완료0%

Networking · 18 / 35

[Network] netstat과 ss 명령어로 커넥션 상태(ESTABLISHED 등) 분석

LISTEN/ESTABLISHED/TIME_WAIT 소켓 상태를 이해하고 서버 부하를 점검합니다

🚨INCIDENT ALERT
HIGH

장애 시간에 서버 CPU는 낮은데 연결 수가 폭증했습니다. LISTEN, ESTABLISHED, TIME_WAIT 상태를 구분하지 못해 서비스가 정말 바쁜지 커넥션이 쌓였는지 판단이 늦어졌습니다.

소켓 상태는 서버의 현재 통신 장부입니다. ss 출력만 읽어도 병목의 방향이 보입니다.

소켓 상태와 연결 모니터링 (netstat / ss)

서버가 느려졌다는 신고를 받았을 때, 혹은 포트가 열려있는데 접속이 안 된다는 문의가 왔을 때 가장 먼저 확인해야 할 것은 소켓 상태입니다. 이 챕터에서는 TCP 소켓의 생명주기와 4가지 핵심 상태를 이해하고, netstat와 ss 명령으로 서버의 연결 현황을 점검하는 방법을 배웁니다.


이번 챕터에서 배울 것
  • 1TCP 소켓 생명주기 — 3-Way Handshake(연결 수립)와 4-Way Handshake(연결 종료)
  • 2핵심 소켓 상태 — LISTEN, ESTABLISHED, TIME_WAIT, CLOSE_WAIT 의미와 발생 원인
  • 3ss 명령 핵심 옵션(-antp, -s, -o)과 netstat 비교
  • 4ESTABLISHED 수로 동시 접속자 파악 및 서버 부하 점검 방법
  • 5CLOSE_WAIT 소켓 누수 탐지 및 코드 버그 진단
  • 6TIME_WAIT 과다 시 커널 파라미터(tcp_tw_reuse, tcp_fin_timeout) 튜닝
실습 환경 준비
ss 명령 설치 확인 (iproute2 패키지 포함)
ss --version
netstat 설치 확인 (net-tools 패키지)
netstat --version || sudo apt-get install -y net-tools
실습용 포트 리스닝 서버 실행 (Python 간이 HTTP 서버)
python3 -m http.server 8080 &
실습 후 리스닝 프로세스 종료
kill $(lsof -ti:8080)
💡개념

TCP 소켓의 생명주기와 4가지 핵심 상태

포트 8080을 사용하는 애플리케이션을 재시작했는데 "Address already in use" 오류가 납니다. 프로세스를 종료했는데도 왜 포트가 잡혀있는지 이해가 안 됩니다. ss -tan으로 확인하면 TIME_WAIT 상태 소켓이 수십 개 쌓여 있습니다. 소켓 생명주기를 모르면 이 상태가 정상인지 문제인지, 얼마나 기다리면 되는지 알 수 없습니다.

TCP 소켓의 생명주기와 4가지 핵심 상태

TCP 연결 수립 — 3-Way Handshake

소켓 상태를 이해하려면 TCP 연결 수립과 종료 과정을 알아야 합니다.

클라이언트                            서버
    │                                  │
    │──── SYN (연결 요청) ────────────▶│  서버: SYN_RECEIVED
    │◀─── SYN-ACK (수락 + 확인) ───────│
    │──── ACK (확인) ─────────────────▶│  양쪽: ESTABLISHED
    │                                  │
    │  ←── 데이터 송수신 ──▶           │

TCP 연결 종료 — 4-Way Handshake

클라이언트(먼저 종료)                  서버
    │                                  │
    │──── FIN ──────────────────────▶│
    │◀─── ACK ───────────────────────│  서버: CLOSE_WAIT
    │◀─── FIN ───────────────────────│  (서버 앱이 close() 호출)
    │──── ACK ──────────────────────▶│  서버: CLOSED
    │                                  │
 클라이언트: TIME_WAIT (2×MSL 대기 후 CLOSED)

4가지 핵심 소켓 상태

LISTEN

서버가 특정 포트에 바인딩되어 클라이언트의 연결 요청을 기다리는 상태입니다.

서버 프로세스 → bind(포트) → listen() → LISTEN 상태

예: Nginx가 80 포트를 LISTEN → 클라이언트 HTTP 요청 대기 중

ESTABLISHED

3-way handshake 완료 후 양방향 데이터 전송이 가능한 상태입니다. 현재 활성 연결의 수가 ESTABLISHED 소켓 수입니다.

ESTABLISHED 소켓 수 = 현재 서버에 연결된 클라이언트(동시 접속자) 수

TIME_WAIT

TCP 연결 종료 후 일정 시간(기본 60초, 커널 파라미터로 조정) 동안 유지되는 상태입니다.

TIME_WAIT가 필요한 이유:

  1. 마지막 ACK가 유실됐을 때 재전송할 수 있도록 대기
  2. 네트워크에 떠돌아다니는 지연 패킷이 새 연결에 영향을 주지 않도록 방지

TIME_WAIT 소켓 고갈 문제: 대용량 요청을 처리하는 서버에서 TIME_WAIT 소켓이 과도하게 쌓이면 사용 가능한 포트(로컬 포트 범위 1024~65535)가 고갈되어 새 연결을 맺지 못합니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/networking/part4/exam_18 && cd /tmp/networking/part4/exam_18

# 로컬 포트 범위 확인
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768   60999  (약 28,000개)
🔍실행 후 확인할 것
  • 핵심 출력명령 결과에서 성공/실패를 가르는 값을 먼저 확인합니다
  • 대상 식별IP, 포트, 인터페이스, 프로세스명처럼 다음 조치를 결정하는 필드를 봅니다
  • 다음 분기결과가 기대와 다르면 어느 계층을 이어서 점검할지 정합니다

CLOSE_WAIT

상대방이 FIN을 보냈고 내가 ACK로 응답했지만, 내 애플리케이션이 아직 소켓을 close()하지 않은 상태입니다.

CLOSE_WAIT가 누적되는 원인:

  • 애플리케이션 코드에서 소켓을 close()하지 않는 버그
  • 커넥션 풀(connection pool)에서 반납되지 않는 연결
  • 예외 발생 시 finally 블록에서 소켓 정리를 누락

CLOSE_WAIT는 커널이 자동으로 처리하지 않습니다. 오직 애플리케이션이 close()를 호출해야만 LAST_ACK → CLOSED로 전환됩니다.

전체 상태 전이도

CLOSED → LISTEN → SYN_RECEIVED → ESTABLISHED → FIN_WAIT_1
                                                     │
                              TIME_WAIT ←── FIN_WAIT_2 ←── CLOSING
                                │
                             CLOSED

ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED

💡개념

ss와 netstat 명령 비교 및 TIME_WAIT 소켓 관리

장애 상황에서 "지금 이 서버에 열린 연결이 몇 개야?" 를 빠르게 확인해야 합니다. 구형 서버에는 netstat밖에 없고 최신 서버엔 ss만 있습니다. 두 명령어의 출력 형식이 달라서 같은 정보를 보는데 명령어마다 다른 옵션을 외워야 합니다. 그리고 TIME_WAIT 소켓이 수천 개 쌓이면 새 연결이 안 생기는 문제가 발생하는데, 이걸 관리하는 방법을 모르면 재부팅 외에 해결책이 없습니다.

ss와 netstat 명령 비교 및 TIME_WAIT 소켓 관리

ss vs netstat

항목ssnetstat
속도빠름 (커널 직접 조회)느림 (/proc 파일 파싱)
패키지iproute2 (기본 설치)net-tools (별도 설치 필요)
권장RHEL 7+ 기본 도구레거시
대용량수만 개 소켓도 빠름소켓 많으면 느려짐

현대 Linux 배포판에서는 ss를 기본으로 사용하고, 익숙함을 위해 netstat 옵션도 알아두는 것이 좋습니다.

ss 주요 옵션

로컬 터미널
# 기본 — TCP 소켓 전체 (숫자, 프로세스 포함)
ss -antp

# 플래그 설명
# -a: 모든 상태 (LISTEN 포함)
# -n: 숫자로 표시 (DNS/포트 이름 변환 안 함)
# -t: TCP만
# -p: 프로세스 정보 (PID, 프로세스명)
# -u: UDP만
# -l: LISTEN 상태만

# LISTEN 포트만 확인
ss -tlnp

# UDP 소켓 확인
ss -ulnp

# 요약 통계
ss -s

netstat 주요 옵션

로컬 터미널
# 리스닝 포트 + UDP + 프로세스
netstat -tulnp

# 플래그 설명
# -t: TCP
# -u: UDP
# -l: LISTEN 상태만
# -n: 숫자로 표시
# -p: 프로세스 정보

# 전체 소켓 (ESTABLISHED 포함)
netstat -antp

TIME_WAIT 소켓 고갈 대응 — 커널 파라미터

로컬 터미널
# 현재 TIME_WAIT 소켓 수 확인
ss -antp | grep TIME-WAIT | wc -l

# 또는 ss -s 요약에서 확인
ss -s
# Total: 1245
# TCP:   1189 (estab 45, closed 1100, orphaned 0, timewait 1100)

TIME_WAIT 소켓 고갈 방지를 위한 커널 파라미터:

로컬 터미널
# 현재 설정 확인
sysctl net.ipv4.tcp_tw_reuse
sysctl net.ipv4.tcp_fin_timeout

# net.ipv4.tcp_tw_reuse: TIME_WAIT 소켓을 새 연결에 재사용 허용
# 1로 설정하면 TIME_WAIT 상태의 포트를 outbound 연결에 재사용
sudo sysctl -w net.ipv4.tcp_tw_reuse=1

# net.ipv4.tcp_fin_timeout: FIN_WAIT_2 타임아웃(초), 기본 60
sudo sysctl -w net.ipv4.tcp_fin_timeout=30

# 영구 적용 (/etc/sysctl.conf 또는 /etc/sysctl.d/)
echo "net.ipv4.tcp_tw_reuse = 1" | sudo tee -a /etc/sysctl.d/99-tcp-tuning.conf
echo "net.ipv4.tcp_fin_timeout = 30" | sudo tee -a /etc/sysctl.d/99-tcp-tuning.conf
sudo sysctl -p /etc/sysctl.d/99-tcp-tuning.conf

참고: net.ipv4.tcp_tw_recycle은 커널 4.12에서 제거됐습니다. 사용하지 마세요.


ss와 netstat로 소켓 상태 현황 점검

기본 소켓 상태 확인

로컬 터미널
# 1. LISTEN 중인 TCP 포트 확인 (어떤 서비스가 어느 포트를 점유했나)
ss -tlnp

# 출력 예시
# State    Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
# LISTEN   0       128     0.0.0.0:22          0.0.0.0:*          users:(("sshd",pid=1234,fd=3))
# LISTEN   0       511     0.0.0.0:80          0.0.0.0:*          users:(("nginx",pid=5678,fd=6))
# LISTEN   0       128     127.0.0.1:3306      0.0.0.0:*          users:(("mysqld",pid=9012,fd=23))
# LISTEN   0       511     [::]:443            [::]:*             users:(("nginx",pid=5678,fd=7))

# 확인 포인트
# - 0.0.0.0: 모든 인터페이스에서 수신
# - 127.0.0.1: 로컬 루프백만 (외부에서 접근 불가)
# - [::]: IPv6 와일드카드
# - Recv-Q: 수신 큐에 쌓인 바이트 수 (큰 값이면 처리 지연)

전체 소켓 상태 현황 파악

로컬 터미널
# 모든 TCP 소켓 목록
ss -antp

# 상태별 소켓 수 집계
ss -antp | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn

# 출력 예시
#    1100 TIME-WAIT
#      45 ESTAB
#      10 LISTEN
#       3 CLOSE-WAIT
#       1 FIN-WAIT-1

동시 접속자 수 확인

로컬 터미널
# ESTABLISHED 소켓 수 = 현재 활성 연결 수
ss -antp | grep ESTAB | wc -l

# 특정 포트의 연결 수 (예: HTTP 80포트)
ss -antp | grep ':80' | grep ESTAB | wc -l

# 연결 중인 원격 IP별 집계 (어떤 IP에서 많이 붙어있나)
ss -antp | grep ESTAB | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20

# netstat으로 동일하게 확인
netstat -antp | grep ESTABLISHED | wc -l

실시간 모니터링

로컬 터미널
# 1초마다 소켓 현황 갱신
watch -n 1 'ss -s'

# ESTABLISHED와 TIME_WAIT만 실시간 추적
watch -n 2 'echo "ESTABLISHED: $(ss -antp | grep ESTAB | wc -l)  TIME_WAIT: $(ss -antp | grep TIME-WAIT | wc -l)"'

# 특정 프로세스의 소켓만 확인 (예: nginx PID 5678)
ss -antp | grep 'pid=5678'

TIME_WAIT 고갈 상황 점검과 커널 파라미터 튜닝

TIME_WAIT 상황 진단

로컬 터미널
# 1. TIME_WAIT 소켓 수 확인
ss -s
# TCP:   2100 (estab 50, closed 2010, orphaned 0, timewait 2010)
# timewait 값이 수천~수만이면 고갈 위험

# 2. 로컬 포트 가용 범위 확인
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768   60999
# 최대 사용 가능한 포트: 60999 - 32768 = 28,231개

# 3. 현재 사용 중인 포트 수
ss -antp | wc -l

# 4. 에러 카운터 확인 (포트 고갈 발생 시 증가)
netstat -s | grep -i "failed\|overflow\|reuse"

커널 파라미터 단계별 튜닝

위험 명령어

이 명령은 시스템 설정 파일을 변경합니다. 기존 파일 백업과 적용 후 검증 방법을 준비하지 않으면 재부팅이나 서비스 재시작 때 장애가 반복될 수 있습니다.

로컬 터미널
# 현재 설정 전체 확인
sysctl -a | grep tcp | grep -E "tw_reuse|fin_timeout|port_range|max_syn"

# --- 단계 1: FIN_WAIT_2 타임아웃 단축 ---
# 기본 60초 → 30초로 단축
sudo sysctl -w net.ipv4.tcp_fin_timeout=30
echo "net.ipv4.tcp_fin_timeout = 30" | sudo tee /etc/sysctl.d/99-tcp.conf

# --- 단계 2: TIME_WAIT 소켓 재사용 허용 ---
# Outbound 연결에서 TIME_WAIT 소켓 재사용 (클라이언트 역할 서버에 적용)
sudo sysctl -w net.ipv4.tcp_tw_reuse=1
echo "net.ipv4.tcp_tw_reuse = 1" | sudo tee -a /etc/sysctl.d/99-tcp.conf

# --- 단계 3: 로컬 포트 범위 확장 ---
# 기본 32768-60999 → 1024-65535로 확장 (약 64,511개)
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"
echo "net.ipv4.ip_local_port_range = 1024 65535" | sudo tee -a /etc/sysctl.d/99-tcp.conf

# 설정 적용
sudo sysctl -p /etc/sysctl.d/99-tcp.conf

# 적용 확인
sysctl net.ipv4.tcp_tw_reuse net.ipv4.tcp_fin_timeout net.ipv4.ip_local_port_range

효과 검증

로컬 터미널
# 튜닝 전후 비교를 위해 현재 TIME_WAIT 수 기록
BEFORE=$(ss -antp | grep TIME-WAIT | wc -l)
echo "튜닝 전 TIME_WAIT: $BEFORE"

# ... 파라미터 설정 ...

# 60초 후 다시 확인
sleep 60
AFTER=$(ss -antp | grep TIME-WAIT | wc -l)
echo "튜닝 후 TIME_WAIT: $AFTER"

CLOSE_WAIT 소켓 누수 탐지와 원인 분석

CLOSE_WAIT 소켓 존재 확인

로컬 터미널
# CLOSE_WAIT 소켓 확인
ss -antp | grep CLOSE-WAIT

# 출력 예시
# CLOSE-WAIT  1   0   10.0.0.1:8080   192.168.1.100:54321  users:(("java",pid=12345,fd=42))
# CLOSE-WAIT  1   0   10.0.0.1:8080   192.168.1.101:54322  users:(("java",pid=12345,fd=43))
# CLOSE-WAIT  1   0   10.0.0.1:8080   192.168.1.102:54323  users:(("java",pid=12345,fd=44))

# 개수 집계
CLOSE_WAIT_COUNT=$(ss -antp | grep CLOSE-WAIT | wc -l)
echo "CLOSE_WAIT 소켓 수: $CLOSE_WAIT_COUNT"

# 10개 이상이면 누수 의심
if [ "$CLOSE_WAIT_COUNT" -gt 10 ]; then
  echo "경고: CLOSE_WAIT 소켓 누수 의심!"
fi

누수 프로세스 특정

로컬 터미널
# CLOSE_WAIT 소켓을 가진 프로세스 확인
ss -antp | grep CLOSE-WAIT | grep -oP 'pid=\K[0-9]+' | sort | uniq -c | sort -rn

# 해당 PID의 프로세스 정보 확인
ps aux | grep 12345

# 해당 프로세스의 열린 파일/소켓 수 확인
ls /proc/12345/fd | wc -l
# 수천 개면 소켓 누수 확인

# 어떤 소켓들인지 확인
ls -la /proc/12345/fd | grep socket

애플리케이션 레벨 확인

Java Spring Boot 애플리케이션의 커넥션 풀 상태 확인 예시:

로컬 또는 서버
# Actuator가 활성화된 경우 커넥션 풀 통계 조회
curl -s http://localhost:8080/actuator/metrics/hikaricp.connections.active
curl -s http://localhost:8080/actuator/metrics/hikaricp.connections.idle

# JVM 힙 덤프 → 소켓 객체 분석 (심층 분석 시)
jmap -dump:format=b,file=/tmp/heap.hprof 12345

임시 해결책 (서비스 재시작 없이)

위험 명령어

이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.

서버 터미널
# CLOSE_WAIT 소켓을 강제로 닫는 방법은 없음 (커널 설계상)
# 유일한 해결책: 애플리케이션이 close()를 호출하도록 수정

# 임시: 해당 프로세스 재시작 (소켓 모두 정리됨)
systemctl restart myapp

# 또는 graceful reload가 지원되면
kill -HUP 12345

증상

로컬 터미널
# 서버에서 확인: 80 포트 LISTEN 중
ss -tlnp | grep :80
# LISTEN   0   511   127.0.0.1:80   0.0.0.0:*   users:(("nginx",pid=5678))

# 외부에서 접속 시 실패
curl http://서버IP/
# curl: (7) Failed to connect to 서버IP port 80: Connection refused

원인 분석

원인: 127.0.0.1에만 바인딩됨

출력에서 Local Address127.0.0.1:80이므로 루프백 인터페이스에만 바인딩되어 있습니다. 외부 IP에서는 접근할 수 없습니다.

로컬 터미널
# 어떤 IP에 바인딩되어 있는지 정확히 확인
ss -tlnp | awk '{print $4}' | grep -v "Local"
# 127.0.0.1:80    ← 외부 접근 불가
# 0.0.0.0:80      ← 모든 인터페이스에서 접근 가능
# [::]:80         ← IPv6 와일드카드 (IPv4 포함되기도 함)

해결 방법

Nginx 설정 수정 — worker_connections와 keepalive 값을 조정합니다.

Nginx
# /etc/nginx/nginx.conf 또는 /etc/nginx/conf.d/default.conf
server {
    # 변경 전
    listen 127.0.0.1:80;

    # 변경 후 — 모든 인터페이스
    listen 0.0.0.0:80;
    # 또는 그냥
    listen 80;
}
위험 명령어

이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.

로컬 터미널
# 설정 검증 후 리로드
nginx -t && systemctl reload nginx

# 변경 확인
ss -tlnp | grep :80
# LISTEN   0   511   0.0.0.0:80   0.0.0.0:*   users:(("nginx",pid=5678))

방화벽도 함께 확인 — 포트가 열려 있어도 방화벽이 막으면 LISTEN 상태가 의미 없습니다.

위험 명령어

이 명령은 방화벽 정책을 변경해 현재 접속 중인 세션이나 운영 트래픽에 즉시 영향을 줄 수 있습니다. 적용 전 허용 대상 IP·포트와 롤백 명령을 확인하세요.

로컬 터미널
# firewalld
firewall-cmd --list-all | grep 80

# iptables
iptables -L INPUT -n | grep 80

# 방화벽이 차단 중이면 허용
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --reload

증상

로컬 터미널
# 서버 로그
[ERROR] bind: address already in use
[ERROR] connect: Cannot assign requested address

# ss로 확인
ss -s
# TCP:   32000 (estab 50, closed 31900, orphaned 0, timewait 31900)
# timewait이 3만 개를 넘어섬

클라이언트 역할을 하는 서버(예: 내부 API 서버가 DB나 외부 API를 반복 호출)에서 자주 발생합니다.

원인

로컬 터미널
# 로컬 포트 가용 범위 확인
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768   60999  → 최대 28,231개

# TIME_WAIT 소켓이 28,231개를 넘으면 포트 고갈
ss -antp | grep TIME-WAIT | wc -l
# 28500   ← 초과!

연결을 짧게 끊고 새로 맺는 패턴(HTTP keep-alive 미사용, 커넥션 풀 없음)에서 TIME_WAIT가 누적됩니다.

해결 방법

단기 해결 — 커널 파라미터 튜닝 — TIME_WAIT 소켓이 쌓이는 것을 줄이는 즉각적 조치입니다.

위험 명령어

이 명령은 시스템 설정 파일을 변경합니다. 기존 파일 백업과 적용 후 검증 방법을 준비하지 않으면 재부팅이나 서비스 재시작 때 장애가 반복될 수 있습니다.

로컬 터미널
# TIME_WAIT 소켓 재사용
sudo sysctl -w net.ipv4.tcp_tw_reuse=1

# FIN_WAIT_2 타임아웃 단축
sudo sysctl -w net.ipv4.tcp_fin_timeout=30

# 포트 범위 확장
sudo sysctl -w net.ipv4.ip_local_port_range="10000 65535"

# 영구 저장
cat << 'EOF' | sudo tee /etc/sysctl.d/99-tcp-tuning.conf
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 10000 65535
EOF
sudo sysctl -p /etc/sysctl.d/99-tcp-tuning.conf

근본적 해결 — 애플리케이션 개선 — Connection Pool을 쓰면 소켓 생성 빈도 자체가 줄어듭니다.

로컬 또는 서버
# HTTP 클라이언트에서 Keep-Alive 활성화 (연결 재사용)
# curl의 경우
curl --keepalive-time 60 http://internal-api/endpoint

# 애플리케이션 레벨에서 Connection Pool 사용:
# Python requests.Session, Java HikariCP, Node.js http.Agent(keepAlive:true)

모니터링 알림 설정

로컬 터미널
#!/bin/bash
# /usr/local/bin/check-timewait.sh

THRESHOLD=20000
COUNT=$(ss -antp | grep TIME-WAIT | wc -l)

if [ "$COUNT" -gt "$THRESHOLD" ]; then
  echo "TIME_WAIT 경고: 현재 ${COUNT}개 (임계값: ${THRESHOLD})" \
    | mail -s "[ALERT] TIME_WAIT 고갈 위험" admin@example.com
fi
로컬 터미널
# crontab -e
*/5 * * * * /usr/local/bin/check-timewait.sh

💼
실무 맥락
현업 패턴

용량 계획과 임계값 설정

운영 서버에서 소켓 상태를 정기적으로 수집하면 서버 용량 계획의 근거가 됩니다.

로컬 터미널
#!/bin/bash
# /usr/local/bin/socket-metrics.sh
# Prometheus textfile collector 형식으로 메트릭 출력

OUTPUT_FILE="/var/lib/node_exporter/textfile_collector/socket_stats.prom"

ESTAB=$(ss -antp | grep -c ESTAB)
TIMEWAIT=$(ss -antp | grep -c TIME-WAIT)
CLOSEWAIT=$(ss -antp | grep -c CLOSE-WAIT)
LISTEN=$(ss -tlnp | tail -n +2 | wc -l)

cat > "$OUTPUT_FILE" << EOF
# HELP socket_established_total Current ESTABLISHED TCP connections
# TYPE socket_established_total gauge
socket_established_total $ESTAB

# HELP socket_timewait_total Current TIME_WAIT TCP sockets
# TYPE socket_timewait_total gauge
socket_timewait_total $TIMEWAIT

# HELP socket_closewait_total Current CLOSE_WAIT TCP sockets
# TYPE socket_closewait_total gauge
socket_closewait_total $CLOSEWAIT

# HELP socket_listen_total Current LISTEN ports
# TYPE socket_listen_total gauge
socket_listen_total $LISTEN
EOF

실무 점검 체크리스트

서버 신규 오픈 전 점검:

로컬 터미널
# 1. 의도한 포트만 LISTEN 중인지 확인 (불필요한 포트 차단)
ss -tlnp
# → 모든 LISTEN 포트가 서비스 목적에 부합하는지 리뷰

# 2. 0.0.0.0 vs 127.0.0.1 바인딩 적절성 확인
# → DB(3306), Redis(6379)는 127.0.0.1에만 바인딩되어야 함

# 3. CLOSE_WAIT 소켓 없음 확인
ss -antp | grep CLOSE-WAIT
# → 배포 직후 CLOSE_WAIT가 0이어야 정상

# 4. 동시 접속자 수 베이스라인 측정
ss -antp | grep -c ESTAB

장애 대응 시나리오별 확인 명령

장애 신고확인 명령
"서버 접속이 안 돼요"`ss -tlnp
"서버가 느려요"`ss -antp
"주기적으로 연결이 끊겨요"`ss -antp
"새벽마다 장애가 나요"ss -s로 TIME_WAIT 추이 모니터링
"특정 IP에서만 접속 안 돼요"`ss -antp

자격증 및 면접 포인트

  • RHCE / LFCS: ssnetstat 명령을 이용한 포트 점검은 필수 실기 항목
  • CKA (Kubernetes 관리자): Pod 간 통신 문제 디버깅에 ss를 사용
  • 백엔드 개발 면접: TIME_WAIT 원인과 tcp_tw_reuse 튜닝, CLOSE_WAIT 코드 버그 진단 능력
  • 시스템 설계 면접: ESTABLISHED 소켓 수로 서버 용량을 추정하고 스케일아웃 시점을 결정하는 근거

핵심 명령어 정리

명령설명
ss -tlnpLISTEN 중인 TCP 포트와 프로세스
ss -antp모든 TCP 소켓 상태
ss -s소켓 요약 통계
ss -antp | grep ESTAB | wc -l현재 ESTABLISHED 소켓 수
ss -antp | grep CLOSE-WAITCLOSE_WAIT 소켓 목록
netstat -tulnpLISTEN 포트와 프로세스 (레거시)
sysctl net.ipv4.tcp_tw_reuseTIME_WAIT 재사용 설정 확인
sysctl net.ipv4.tcp_fin_timeoutFIN_WAIT_2 타임아웃 확인

정리

  • LISTEN은 연결 대기, ESTABLISHED는 활성 연결, TIME_WAIT는 종료 후 대기, CLOSE_WAIT는 앱이 close()를 아직 호출하지 않은 상태입니다.
  • ss -antp는 모든 TCP 소켓 상태를 빠르게 확인하는 현대적 도구입니다.
  • ESTABLISHED 소켓 수가 서버의 실제 동시 접속자 수입니다.
  • TIME_WAIT 소켓 폭증은 tcp_tw_reuse=1 설정과 Keep-Alive/Connection Pool 사용으로 해결합니다.
  • CLOSE_WAIT 누수는 커널이 자동 해결하지 않으므로 반드시 코드에서 소켓 close()를 보장해야 합니다.

지식 확인

퀴즈 — 5문제

Q1

배포 직후 클라이언트에서 'Connection refused' 오류가 발생합니다. 서버에서 `ss -antp | grep 8080`을 실행했더니 아무것도 출력되지 않았습니다. 이 결과가 의미하는 것은 무엇입니까?

Q2

TIME_WAIT 상태가 발생하는 이유는 무엇입니까?

Q3

ss -antp 명령에서 각 플래그가 의미하는 것을 올바르게 설명한 것은 무엇입니까?

Q4

CLOSE_WAIT 상태 소켓이 대량으로 쌓이는 주된 원인은 무엇입니까?

Q5

현재 서버의 ESTABLISHED 소켓 수를 빠르게 확인하는 명령은 무엇입니까?

0 / 5 답변

🧪 실습으로 확인하기

포트는 열렸다는데 왜 안 되지? — ss/netstat/telnet으로 TCP 진단

초급

"포트 8080 열었는데요?"와 "왜 안 돼요?" 사이의 간극을 메우는 실습. ss로 바인딩 상태를 확인하고, telnet/nc으로 원격 연결을 테스트하고, iptables 방화벽을 진단하고, 바인딩 주소(0.0.0.0 vs 127.0.0.1)까지 수정하는 4단계 TCP 포트 진단 플로우를 완성한다.

35📋 4단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

networking중급 · 30
[Network] wget/curl로 외부 연동 방화벽 차단 여부 판별법
Networking 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점