Skip to content
Published on

HTTP/HTTPS 트러블슈팅 완벽 가이드 - 실무에서 바로 쓰는 디버깅 기법

Authors
  • Name
    Twitter

들어가며

운영 환경에서 HTTP/HTTPS 관련 문제가 발생하면, 단순히 "안 돼요"라는 보고만으로는 원인을 파악하기 어렵다. 네트워크 레이어, TLS 핸드셰이크, 애플리케이션 로직, 로드밸런서 설정 등 여러 계층에서 문제가 발생할 수 있기 때문이다.

이 글에서는 실무에서 자주 만나는 HTTP/HTTPS 문제를 체계적으로 진단하고 해결하는 방법을 다룬다.

1. HTTP 상태 코드 - 디버깅 관점에서의 재해석

1xx (정보 응답)

실무에서 자주 간과되지만 중요한 상태 코드가 있다.

100 Continue    → 클라이언트가 큰 요청 본문을 보내기 전, 서버가 수락 의사를 표시
101 SwitchingWebSocket 업그레이드 시 반드시 확인해야 하는 코드
103 Early Hints → 브라우저 preload 최적화에 활용

2xx (성공)

200 OK          → 가장 기본. 하지만 본문이 비어 있으면 의심해야 한다
201 CreatedPOSTLocation 헤더 확인 필수
204 No ContentDELETE 응답으로 적합. 본문이 있으면 버그
206 PartialRange 요청 시. 대용량 파일 다운로드 디버깅에 핵심

3xx (리다이렉션) - 함정이 많은 영역

301 Moved Permanently  → 브라우저가 캐시함. 잘못 설정하면 복구가 어렵다
302 Found              → 임시 리다이렉션. SEO 관점에서 301과 혼동 주의
303 See OtherPOSTGET으로 리다이렉트할 때 사용
307 Temporary Redirect → 메서드를 유지하는 리다이렉트
308 Permanent Redirect → 메서드를 유지하는 영구 리다이렉트

4xx (클라이언트 에러) - 디버깅 핵심

# 400 Bad Request 디버깅
curl -v -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name": "test"' # 잘못된 JSON - 닫는 괄호 누락

# 401 vs 403 차이 이해
# 401: 인증 자체가 안 됨 (토큰 없음/만료)
# 403: 인증은 됐으나 권한이 없음

# 405 Method Not Allowed
curl -v -X PATCH https://api.example.com/users/1
# Allow 헤더에서 허용된 메서드 확인

# 429 Too Many Requests
# Retry-After 헤더와 X-RateLimit-* 헤더 확인
curl -v https://api.example.com/data 2>&1 | grep -i "rate\|retry"

5xx (서버 에러) - 가장 긴급한 상황

500 Internal Server Error → 서버 로그 확인이 최우선
502 Bad Gateway           → 업스트림 서버 응답 불가 (프록시 뒤 서버 문제)
503 Service Unavailable   → 서버 과부하 또는 점검 중
504 Gateway Timeout       → 업스트림 서버 응답 시간 초과

2. TLS/SSL 핸드셰이크 트러블슈팅

TLS 핸드셰이크 과정 이해

Client                                Server
  |                                     |
  |--- ClientHello (지원 암호화, SNI) --->|
  |                                     |
  |<-- ServerHello (선택된 암호화) ------|
  |<-- Certificate (인증서 체인) --------|
  |<-- ServerKeyExchange ---------------|
  |<-- ServerHelloDone -----------------|
  |                                     |
  |--- ClientKeyExchange -------------->|
  |--- ChangeCipherSpec --------------->|
  |--- Finished ----------------------->|
  |                                     |
  |<-- ChangeCipherSpec ----------------|
  |<-- Finished ------------------------|
  |                                     |
  |====== 암호화된 통신 시작 ============|

openssl을 이용한 핸드셰이크 디버깅

# 기본 연결 테스트
openssl s_client -connect example.com:443 -servername example.com

# TLS 버전 명시 테스트
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

# 인증서 체인 전체 출력
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | \
  openssl x509 -noout -subject -issuer -dates

# SNI(Server Name Indication) 문제 디버깅
openssl s_client -connect 1.2.3.4:443 -servername mysite.com

# 특정 암호화 스위트 테스트
openssl s_client -connect example.com:443 -cipher ECDHE-RSA-AES256-GCM-SHA384

자주 발생하는 TLS 에러와 해결법

# 에러: SSL_ERROR_HANDSHAKE_FAILURE
# 원인: 클라이언트와 서버 간 지원하는 암호화 스위트가 없음
# 해결: 서버의 cipher suite 확인
nmap --script ssl-enum-ciphers -p 443 example.com

# 에러: CERTIFICATE_VERIFY_FAILED
# 원인: 인증서 체인 불완전 또는 루트 CA 미신뢰
# 진단:
openssl s_client -connect example.com:443 2>&1 | grep "verify"

# 에러: hostname mismatch
# 원인: 인증서의 CN/SAN이 요청 도메인과 불일치
openssl s_client -connect example.com:443 2>/dev/null | \
  openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

3. 인증서 체인 검증 문제

인증서 체인 구조

Root CA (자체 서명, OS/브라우저에 내장)
  └── Intermediate CA (Root CA가 서명)
        └── Server Certificate (Intermediate CA가 서명)

체인 검증 스크립트

#!/bin/bash
# 인증서 체인 검증 스크립트
DOMAIN=$1

echo "=== 인증서 체인 검증: $DOMAIN ==="

# 서버에서 인증서 체인 가져오기
echo | openssl s_client -connect ${DOMAIN}:443 -servername ${DOMAIN} \
  -showcerts 2>/dev/null | \
  awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/{ print }' > /tmp/chain.pem

# 각 인증서 정보 출력
csplit -f /tmp/cert- -b '%02d.pem' /tmp/chain.pem \
  '/BEGIN CERTIFICATE/' '{*}' 2>/dev/null

for cert in /tmp/cert-*.pem; do
  [ -s "$cert" ] || continue
  echo "--- Certificate: $cert ---"
  openssl x509 -in "$cert" -noout \
    -subject -issuer -dates -fingerprint 2>/dev/null
  echo ""
done

# 체인 검증
openssl verify -verbose -CAfile /etc/ssl/certs/ca-certificates.crt \
  /tmp/chain.pem 2>&1

일반적인 인증서 문제

# 1. 인증서 만료 확인
echo | openssl s_client -connect example.com:443 2>/dev/null | \
  openssl x509 -noout -dates

# 2. 중간 인증서 누락 확인
# SSL Labs 테스트 (가장 확실한 방법)
# https://www.ssllabs.com/ssltest/

# 3. 자체 서명 인증서 사용 시 curl
curl --cacert /path/to/ca.crt https://internal.example.com

# 4. 인증서 갱신 후 적용 확인
echo | openssl s_client -connect example.com:443 2>/dev/null | \
  openssl x509 -noout -serial -fingerprint

4. curl 디버깅 기법

기본 디버깅 옵션

# -v (verbose): 가장 기본적인 디버깅
curl -v https://api.example.com/health

# -vv: 더 상세한 출력
curl -vv https://api.example.com/health

# --trace: 바이트 단위 전체 통신 덤프
curl --trace /tmp/curl-trace.log https://api.example.com/health

# --trace-ascii: 가독성 좋은 ASCII 덤프
curl --trace-ascii /tmp/curl-trace.txt https://api.example.com/health

# --trace-time: 타임스탬프 포함
curl --trace-time --trace-ascii - https://api.example.com/health

고급 디버깅 기법

# DNS 우회 (--resolve): 특정 IP로 요청 보내기
# 배포 전 새 서버 테스트에 유용
curl --resolve api.example.com:443:10.0.1.50 \
  https://api.example.com/health

# 연결 시간 측정
curl -o /dev/null -s -w "\
  DNS Lookup:    %{time_namelookup}s\n\
  TCP Connect:   %{time_connect}s\n\
  TLS Handshake: %{time_appconnect}s\n\
  Start Transfer: %{time_starttransfer}s\n\
  Total Time:    %{time_total}s\n\
  HTTP Code:     %{http_code}\n\
  Download Size: %{size_download} bytes\n" \
  https://api.example.com/health

# 리다이렉트 추적
curl -v -L --max-redirs 10 https://example.com 2>&1 | grep "< Location"

# 특정 HTTP 버전 강제
curl --http1.1 https://api.example.com/health
curl --http2 https://api.example.com/health
curl --http3 https://api.example.com/health

# 프록시 경유 디버깅
curl -v --proxy http://proxy.internal:8080 https://api.example.com/health

# 클라이언트 인증서 사용
curl --cert /path/to/client.crt --key /path/to/client.key \
  https://mtls.example.com/api

curl 실전 디버깅 원라이너

# 연속 요청으로 응답 시간 모니터링
for i in $(seq 1 10); do
  echo -n "요청 $i: "
  curl -o /dev/null -s -w "%{http_code} %{time_total}s" \
    https://api.example.com/health
  echo ""
  sleep 1
done

# 헤더만 빠르게 확인
curl -I https://api.example.com/health

# POST 요청 디버깅
curl -v -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"name": "test", "email": "test@example.com"}' \
  2>&1 | grep -E "^[<>*]"

5. 502/503/504 에러 트러블슈팅

502 Bad Gateway

# 원인: 업스트림 서버가 유효하지 않은 응답을 반환

# 1. 업스트림 서버 직접 확인
curl -v http://upstream-server:8080/health

# 2. Nginx 에러 로그 확인
tail -f /var/log/nginx/error.log | grep "502\|upstream"

# 3. 업스트림 서버 포트 리스닝 확인
ss -tlnp | grep 8080
netstat -tlnp | grep 8080

# 4. Nginx 설정 점검
# proxy_pass에서 업스트림 주소가 올바른지 확인
nginx -T | grep -A5 "upstream"

Nginx 502 해결을 위한 설정 조정:

upstream backend {
    server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
    server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

server {
    location /api/ {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_502 http_503;
        proxy_connect_timeout 5s;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;

        # 버퍼 크기 조정 (큰 응답 헤더 처리)
        proxy_buffer_size 16k;
        proxy_buffers 4 32k;
        proxy_busy_buffers_size 64k;
    }
}

503 Service Unavailable

# 원인 분석
# 1. 서버 과부하 확인
top -bn1 | head -5
free -m
df -h

# 2. 연결 수 확인
ss -s
ss -tn state established | wc -l

# 3. 프로세스 상태 확인
systemctl status nginx
systemctl status your-app

# 4. 리소스 제한 확인
ulimit -n  # 파일 디스크립터 수
cat /proc/sys/net/core/somaxconn  # 소켓 백로그

504 Gateway Timeout

# 원인: 업스트림 서버 응답 시간 초과

# 1. 타임아웃 단계별 진단
curl -o /dev/null -s -w "connect: %{time_connect}s\nttfb: %{time_starttransfer}s\ntotal: %{time_total}s\n" \
  https://api.example.com/slow-endpoint

# 2. 느린 쿼리 확인 (DB 원인인 경우)
# MySQL
SHOW PROCESSLIST;
# PostgreSQL
SELECT pid, now() - pg_stat_activity.query_start AS duration, query
FROM pg_stat_activity
WHERE state = 'active' AND now() - query_start > interval '5 seconds';

# 3. 타임아웃 설정 조정
# Nginx: proxy_read_timeout 120s;
# HAProxy: timeout server 120s

6. CORS 문제 해결

CORS 작동 원리

브라우저                          서버
  |                                |
  |--- Preflight (OPTIONS) ------->|
  |    Origin: https://app.com     |
  |    Access-Control-Request-Method: POST
  |                                |
  |<-- 200 OK --------------------|
  |    Access-Control-Allow-Origin: https://app.com
  |    Access-Control-Allow-Methods: POST, GET
  |    Access-Control-Allow-Headers: Content-Type
  |    Access-Control-Max-Age: 86400
  |                                |
  |--- 실제 요청 (POST) ----------->|
  |    Origin: https://app.com     |
  |                                |
  |<-- 200 OK --------------------|
  |    Access-Control-Allow-Origin: https://app.com

CORS 디버깅

# Preflight 요청 시뮬레이션
curl -v -X OPTIONS https://api.example.com/data \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type, Authorization"

# 응답에서 CORS 헤더 확인
curl -v https://api.example.com/data \
  -H "Origin: https://app.example.com" \
  2>&1 | grep -i "access-control"

Nginx에서 CORS 설정

location /api/ {
    # Preflight 요청 처리
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '$http_origin' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Max-Age' 86400;
        add_header 'Content-Length' 0;
        return 204;
    }

    # 실제 요청에도 CORS 헤더 추가
    add_header 'Access-Control-Allow-Origin' '$http_origin' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;

    proxy_pass http://backend;
}

7. 리다이렉트 루프 디버깅

# 리다이렉트 체인 추적
curl -v -L --max-redirs 20 https://example.com 2>&1 | \
  grep -E "^< (HTTP|Location)" | head -30

# 결과 예시 (무한 루프):
# < HTTP/1.1 301 Moved Permanently
# < Location: https://www.example.com/
# < HTTP/1.1 301 Moved Permanently
# < Location: https://example.com/
# < HTTP/1.1 301 Moved Permanently
# < Location: https://www.example.com/   ← 루프!

# 흔한 원인:
# 1. HTTP → HTTPS 리다이렉트 + HTTPS → HTTP 리다이렉트 충돌
# 2. www ↔ non-www 리다이렉트 충돌
# 3. 로드밸런서의 SSL termination + 앱의 HTTPS 강제

# 해결: X-Forwarded-Proto 헤더 활용
# Nginx에서 백엔드로 프로토콜 정보 전달
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

8. HTTP/2 및 HTTP/3 디버깅

HTTP/2 디버깅

# HTTP/2 지원 확인
curl -v --http2 https://example.com 2>&1 | grep "ALPN"

# HTTP/2 프레임 레벨 디버깅 (nghttp2)
nghttp -v https://example.com

# HTTP/2 멀티플렉싱 확인
nghttp -v -m 10 https://example.com/style.css \
  https://example.com/script.js \
  https://example.com/image.png

# HTTP/2 서버 푸시 확인
nghttp -v --stat https://example.com 2>&1 | grep "push"

# h2c (HTTP/2 Cleartext) 테스트
curl -v --http2-prior-knowledge http://localhost:8080/health

HTTP/3 (QUIC) 디버깅

# HTTP/3 지원 확인
curl --http3 -v https://example.com 2>&1 | head -20

# Alt-Svc 헤더 확인 (HTTP/3 광고)
curl -sI https://example.com | grep -i "alt-svc"
# 예: alt-svc: h3=":443"; ma=86400

# QUIC 연결 디버깅 (quiche 도구)
# 0-RTT 연결 테스트
curl --http3 -v --connect-to example.com:443:server-ip:443 \
  https://example.com

HTTP/2와 HTTP/3의 일반적인 문제

# 1. HTTP/2의 HEAD-OF-LINE blocking 확인
# HTTP/2는 TCP 레이어에서 HOL blocking 발생 가능
# 해결: HTTP/3(QUIC) 사용 검토

# 2. HTTP/2 GOAWAY 프레임 디버깅
nghttp -v https://example.com 2>&1 | grep "GOAWAY"

# 3. HPACK 헤더 압축 문제
# 큰 헤더를 가진 요청에서 HPACK 동적 테이블 크기 확인
nghttp -v --header-table-size=65536 https://example.com

# 4. 스트림 동시성 제한
# SETTINGS_MAX_CONCURRENT_STREAMS 확인
nghttp -v https://example.com 2>&1 | grep "MAX_CONCURRENT"

9. 로드밸런서 및 리버스 프록시 트러블슈팅

헬스체크 실패 디버깅

# 1. 직접 헬스체크 엔드포인트 확인
curl -v http://backend-server:8080/health

# 2. 로드밸런서에서 보는 것과 동일한 조건으로 테스트
curl -v -H "Host: api.example.com" \
  --resolve api.example.com:80:10.0.1.10 \
  http://api.example.com/health

# 3. HAProxy 상태 확인
echo "show stat" | socat stdio /var/run/haproxy/admin.sock | \
  awk -F',' '{print $1, $2, $18, $19}'

# 4. Nginx 업스트림 상태 (nginx-module-vts 사용 시)
curl http://localhost/status/format/json | jq '.upstreamZones'

세션 고정(Sticky Session) 문제

# 쿠키 기반 세션 확인
curl -v -c cookies.txt https://app.example.com/login
curl -v -b cookies.txt https://app.example.com/dashboard

# 같은 백엔드로 라우팅되는지 확인
for i in $(seq 1 5); do
  curl -s -b cookies.txt https://app.example.com/api/server-id
  echo ""
done

X-Forwarded-* 헤더 디버깅

# 프록시 체인 확인
curl -v https://api.example.com/debug-headers 2>&1

# 예상되는 헤더:
# X-Forwarded-For: client-ip, proxy1-ip, proxy2-ip
# X-Forwarded-Proto: https
# X-Forwarded-Host: api.example.com
# X-Real-IP: client-ip

# 프록시가 원본 IP를 올바르게 전달하는지 테스트
curl -H "X-Forwarded-For: 1.2.3.4" https://api.example.com/my-ip

10. 실전 디버깅 시나리오

시나리오 1: 간헐적 502 에러

#!/bin/bash
# 간헐적 502 모니터링 스크립트
URL="https://api.example.com/health"
LOG_FILE="/tmp/502-monitor.log"

echo "=== 502 모니터링 시작: $(date) ===" >> $LOG_FILE

while true; do
  RESPONSE=$(curl -s -o /dev/null -w "%{http_code}|%{time_total}|%{time_connect}|%{time_starttransfer}" $URL)
  HTTP_CODE=$(echo $RESPONSE | cut -d'|' -f1)
  TOTAL_TIME=$(echo $RESPONSE | cut -d'|' -f2)
  CONNECT_TIME=$(echo $RESPONSE | cut -d'|' -f3)
  TTFB=$(echo $RESPONSE | cut -d'|' -f4)

  if [ "$HTTP_CODE" != "200" ]; then
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$TIMESTAMP] HTTP $HTTP_CODE | Total: ${TOTAL_TIME}s | Connect: ${CONNECT_TIME}s | TTFB: ${TTFB}s" >> $LOG_FILE

    # 502 발생 시 상세 정보 수집
    if [ "$HTTP_CODE" = "502" ]; then
      echo "  Detailed trace:" >> $LOG_FILE
      curl -v $URL 2>> $LOG_FILE
      echo "---" >> $LOG_FILE
    fi
  fi

  sleep 5
done

시나리오 2: TLS 인증서 만료 모니터링

#!/bin/bash
# 인증서 만료 모니터링 스크립트
DOMAINS=(
  "api.example.com"
  "app.example.com"
  "admin.example.com"
)
WARNING_DAYS=30

for domain in "${DOMAINS[@]}"; do
  EXPIRY=$(echo | openssl s_client -connect ${domain}:443 -servername ${domain} 2>/dev/null | \
    openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)

  if [ -z "$EXPIRY" ]; then
    echo "[ERROR] $domain: 연결 실패"
    continue
  fi

  EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$EXPIRY" +%s 2>/dev/null)
  NOW_EPOCH=$(date +%s)
  DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

  if [ $DAYS_LEFT -lt 0 ]; then
    echo "[CRITICAL] $domain: 인증서 만료됨! ($EXPIRY)"
  elif [ $DAYS_LEFT -lt $WARNING_DAYS ]; then
    echo "[WARNING] $domain: ${DAYS_LEFT}일 후 만료 ($EXPIRY)"
  else
    echo "[OK] $domain: ${DAYS_LEFT}일 남음 ($EXPIRY)"
  fi
done

시나리오 3: 전체 HTTP 통신 종합 진단

#!/bin/bash
# HTTP 종합 진단 스크립트
URL=${1:-"https://example.com"}
echo "============================================"
echo "HTTP 종합 진단: $URL"
echo "시간: $(date)"
echo "============================================"

# 1. DNS 확인
echo -e "\n[1] DNS 확인"
DOMAIN=$(echo $URL | awk -F[/:] '{print $4}')
dig +short $DOMAIN A
dig +short $DOMAIN AAAA

# 2. 포트 연결 확인
echo -e "\n[2] TCP 연결 확인"
nc -zv $DOMAIN 443 2>&1

# 3. TLS 정보
echo -e "\n[3] TLS 정보"
echo | openssl s_client -connect ${DOMAIN}:443 -servername ${DOMAIN} 2>/dev/null | \
  grep -E "Protocol|Cipher|Verify"

# 4. 인증서 정보
echo -e "\n[4] 인증서 정보"
echo | openssl s_client -connect ${DOMAIN}:443 -servername ${DOMAIN} 2>/dev/null | \
  openssl x509 -noout -subject -issuer -dates 2>/dev/null

# 5. HTTP 응답
echo -e "\n[5] HTTP 응답"
curl -s -o /dev/null -w "\
  HTTP Code:      %{http_code}\n\
  DNS Lookup:     %{time_namelookup}s\n\
  TCP Connect:    %{time_connect}s\n\
  TLS Handshake:  %{time_appconnect}s\n\
  TTFB:           %{time_starttransfer}s\n\
  Total Time:     %{time_total}s\n\
  Download Size:  %{size_download} bytes\n\
  HTTP Version:   %{http_version}\n\
  Remote IP:      %{remote_ip}\n" $URL

# 6. 응답 헤더
echo -e "\n[6] 주요 응답 헤더"
curl -sI $URL | grep -iE "^(server|x-|content-type|cache-control|strict|location|set-cookie)"

# 7. 보안 헤더 확인
echo -e "\n[7] 보안 헤더 검사"
HEADERS=$(curl -sI $URL)
for header in "Strict-Transport-Security" "X-Content-Type-Options" "X-Frame-Options" "Content-Security-Policy" "X-XSS-Protection"; do
  if echo "$HEADERS" | grep -qi "$header"; then
    echo "  [O] $header"
  else
    echo "  [X] $header (누락)"
  fi
done

echo -e "\n============================================"
echo "진단 완료"
echo "============================================"

마치며

HTTP/HTTPS 트러블슈팅은 계층적 접근이 핵심이다. DNS, TCP 연결, TLS 핸드셰이크, HTTP 프로토콜, 애플리케이션 로직 순서로 문제를 좁혀 나가면 대부분의 문제를 효율적으로 해결할 수 있다.

여기서 소개한 도구와 기법을 활용해서 운영 환경에서 발생하는 문제를 체계적으로 진단하는 습관을 기르자. 특히 curl -w 포맷 문자열과 openssl s_client는 반드시 익혀두면 실무에서 큰 도움이 된다.

핵심 정리:

  • 상태 코드는 단서일 뿐이다 - 반드시 응답 본문과 헤더를 함께 확인
  • TLS 문제는 openssl s_client로 시작
  • 타이밍 문제는 curl -w로 각 단계별 시간 측정
  • 간헐적 문제는 자동화된 모니터링 스크립트로 포착
  • 프록시 관련 문제는 각 홉에서 직접 테스트하여 병목 구간 특정