Skip to content

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

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

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

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

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

1xx (정보 응답)

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

100 Continue → 클라이언트가 큰 요청 본문을 보내기 전, 서버가 수락 의사를 표시

101 Switching → WebSocket 업그레이드 시 반드시 확인해야 하는 코드

103 Early Hints → 브라우저 preload 최적화에 활용

2xx (성공)

200 OK → 가장 기본. 하지만 본문이 비어 있으면 의심해야 한다

201 Created → POST 후 Location 헤더 확인 필수

204 No Content → DELETE 응답으로 적합. 본문이 있으면 버그

206 Partial → Range 요청 시. 대용량 파일 다운로드 디버깅에 핵심

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

301 Moved Permanently → 브라우저가 캐시함. 잘못 설정하면 복구가 어렵다

302 Found → 임시 리다이렉션. SEO 관점에서 301과 혼동 주의

303 See Other → POST 후 GET으로 리다이렉트할 때 사용

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`로 각 단계별 시간 측정

- 간헐적 문제는 자동화된 모니터링 스크립트로 포착

- 프록시 관련 문제는 각 홉에서 직접 테스트하여 병목 구간 특정

현재 단락 (1/315)

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

작성 글자: 0원문 글자: 13,741작성 단락: 0/315