이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- TLS 장애를 DNS, TCP, TLS, HTTP, app 문제로 어떻게 분리하는가?
- 인증서 만료, chain 누락, SNI mismatch, Java truststore 문제를 어떤 명령으로 확인하는가?
- 인프라 팀에 TLS 장애를 전달할 때 어떤 증거를 남겨야 하는가?
개요
TLS 디버깅의 핵심은 “HTTPS가 안 된다”를 더 작은 질문으로 쪼개는 것이다. 이름이 풀리는가, TCP 포트가 열리는가, TLS handshake가 되는가, 인증서 검증이 되는가, HTTP 응답이 오는가, 앱 로그에 도달했는가를 순서대로 확인한다.
- DNS와 TCP가 실패하면 TLS는 시작도 못 한다.
- TLS handshake가 실패하면 HTTP status code가 없을 수 있다.
- TLS는 성공했지만 HTTP가 실패하면 proxy, app, upstream, auth 문제로 범위가 좁아진다.
- Java 기반 백엔드는 OS CA bundle과 JVM truststore 차이를 따로 봐야 한다.
원리
TLS 장애는 보통 다음 네 범주 중 하나다.
- protocol/cipher 불일치: client와 server가 공통 TLS version 또는 cipher를 찾지 못한다.
- 인증서 검증 실패: 만료, hostname mismatch, chain 누락, 신뢰되지 않은 CA.
- SNI/ALPN 문제: 잘못된 인증서 선택, HTTP/2 협상 실패.
- 중간 장비 문제: CDN, WAF, corporate proxy, LB, ingress가 TLS를 종료하거나 재서명한다.
이 범주를 나누면 장애 보고가 짧아진다. “TLS 문제”가 아니라 “SNI를 넣으면 정상, 빼면 기본 인증서가 내려온다”처럼 말할 수 있다.
1단계: DNS와 TCP 분리
dig +short api.example.com
nc -vz api.example.com 443
curl -v --connect-timeout 3 https://api.example.com/healthCould not resolve host는 DNS 문제고, Connection timed out이나 Connection refused는 TCP나 방화벽, listener 문제다. curl: (35)처럼 SSL connect 단계에서 실패하면 TLS로 범위가 좁아진다.
2단계: Handshake와 인증서 확인
openssl s_client -connect api.example.com:443 \
-servername api.example.com \
-showcerts \
-alpn h2,http/1.1 </dev/null확인할 항목은 다음과 같다.
Verify return code: 0 (ok)인지subject와issuer가 기대와 맞는지notAfter만료일이 충분히 남았는지subjectAltName에 접속 hostname이 있는지ALPN protocol이 기대한 값인지- SNI를 넣었을 때와 뺐을 때 인증서가 달라지는지
3단계: Java Truststore 확인
브라우저는 정상인데 Spring 백엔드의 외부 API 호출만 실패하면 JVM truststore를 의심한다.
keytool -list -cacerts | grep -i 'my-company'
java -Djavax.net.debug=ssl,handshake \
-jar app.jar운영 container에서는 base image가 가진 CA bundle과 JVM의 cacerts가 기대와 다른 경우가 있다. 사설 CA를 쓰는 내부 API라면 “개발자 노트북에서 된다”는 증거만으로 운영 정상 동작을 보장할 수 없다.
4단계: Termination 구간별로 재현
curl -v https://api.example.com/health
curl -v --resolve api.example.com:443:203.0.113.10 \
https://api.example.com/health
openssl s_client -connect internal-lb.example.local:8443 \
-servername app.internal.example.local </dev/null첫 번째는 사용자가 보는 공개 경로다. 두 번째는 DNS/CDN을 우회하고 특정 edge나 LB IP를 찌른다. 세 번째는 backend TLS를 본다. 세 결과가 다르면 어느 경계에서 달라지는지 인프라 팀과 바로 이야기할 수 있다.
만료일 점검 스크립트
개인 프로젝트나 작은 팀에서는 간단한 스크립트만 있어도 만료 장애를 크게 줄일 수 있다.
#!/usr/bin/env bash
set -euo pipefail
host="${1:?usage: ./check-cert.sh host [port]}"
port="${2:-443}"
end_date=$(
echo | openssl s_client -connect "${host}:${port}" -servername "${host}" 2>/dev/null \
| openssl x509 -noout -enddate \
| cut -d= -f2
)
end_epoch=$(date -j -f "%b %e %T %Y %Z" "$end_date" +%s)
now_epoch=$(date +%s)
days_left=$(( (end_epoch - now_epoch) / 86400 ))
echo "${host}:${port} expires in ${days_left} days (${end_date})"
test "$days_left" -gt 14Linux에서는 date -d "$end_date" +%s처럼 바꿔야 한다. 이 스크립트는 완전한 인증서 검증 도구는 아니지만 만료 알림의 최소 안전망으로 쓸 수 있다.
자주 만나는 특이 사례
- 사내 보안 프록시가 TLS를 재서명해 브라우저는 되지만 서버 런타임은 신뢰하지 못한다.
- 구형 Java 8 런타임이 TLS 1.3 또는 최신 root CA 변화를 따라가지 못한다.
- CDN 인증서는 정상인데 origin 인증서가 만료되어 502/525 계열 오류가 난다.
- Kubernetes ingress secret은 갱신됐지만 controller가 reload하지 않아 이전 인증서를 계속 제공한다.
- 서버 시간이 틀어져 아직 유효하지 않거나 이미 만료된 인증서로 판단한다.
실전 팁
- TLS 오류를 재현할 때는 반드시 hostname과 IP를 함께 기록한다. CDN과 LB는 위치마다 다른 인증서를 줄 수 있다.
- 인증서 갱신 후에는 “파일이 바뀌었다”가 아니라 “외부 endpoint에서 새 인증서가 내려온다”를 검증한다.
-k나--insecure로 임시 우회한 명령을 문서의 정상 절차처럼 남기지 않는다. 우회는 원인 분리용이다.- 장애 중에는 브라우저 스크린샷보다 CLI 출력, request id, edge request id, LB log timestamp가 더 쓸모 있다.
- 운영 문서에는 정상 출력 예시를 남긴다. 그래야 장애 때 무엇이 달라졌는지 바로 비교할 수 있다.
인프라 협업 포인트
- 인프라 팀에는 “어느 hostname에서, 어떤 client에서, 어떤 TLS alert가, 몇 시부터, 어떤 변경 이후 발생했는지”를 전달한다.
- CDN/LB/origin 중 어디 인증서를 보고 있는지 명확히 한다.
- 내부 CA나 mTLS는 발급 팀, 배포 팀, 앱 팀이 서로 다른 경우가 많으므로 소유자를 명시한다.
- 인프라 팀도 앱의 outbound 호출 hostname과 truststore를 볼 수 없으므로, 백엔드가 재현 명령과 런타임 정보를 제공해야 한다.
위험 신호!
- 장애 대응 중
curl -k로 성공한 뒤 원인을 해결했다고 착각한다. - 인증서 자동 갱신은 되어 있지만 실제 서비스 endpoint 검증이 없다.
- Java truststore 변경이 이미지 빌드, 배포, 롤백 절차에 포함되어 있지 않다.
- TLS 오류와 HTTP 5xx를 한 dashboard에만 묶어 두고 계층별로 분리하지 않는다.
확인 질문
확인 질문
curl -k로 성공한다는 것은 무엇을 의미하는가?
- TCP와 TLS 암호화 자체는 진행될 수 있지만 인증서 검증을 우회해야만 성공한다는 뜻이므로, 신뢰 chain이나 hostname 검증 문제를 더 봐야 한다.
- SNI 문제를 확인하는 가장 간단한 비교는 무엇인가?
openssl s_client를-servername포함/미포함으로 각각 실행해 내려오는 인증서가 달라지는지 확인한다.- 브라우저는 정상인데 서버 코드만 TLS 실패한다면 무엇을 봐야 하는가?
- JVM truststore, container CA bundle, 호출 hostname, 사설 CA, 사내 프록시 재서명 여부를 확인한다.