인증서를 갱신했다는 알림은 성공으로 끝났는데, 사용자는 여전히 만료된 인증서 경고를 봅니다. 포트 443은 열려 있고 nginx도 떠 있지만 새 인증서를 읽지 못한 상태였습니다.
HTTPS 장애는 인증서, 포트, TLS 협상, 웹서버 reload가 함께 맞아야 해결됩니다.
HTTPS와 TLS — curl 인증서 에러와 443 장애 디버깅
HTTPS 장애는 항상 급하게 옵니다. 서비스가 갑자기 503이 뜨거나, API 연동이 안 된다는 연락이 옵니다. 원인이 인증서 만료인지, 포트가 막힌 건지, TLS 버전 불일치인지 — 5분 안에 판단할 수 있어야 합니다. curl과 openssl로 충분합니다.
TLS 동작 원리를 깊게 알 필요 없습니다. "어떤 에러가 왜 나는지", "어떤 명령으로 확인하는지"만 알면 대부분의 HTTPS 장애를 해결할 수 있습니다.
- 1HTTP와 HTTPS의 차이와 TLS가 추가하는 보안 요소를 설명할 수 있다
- 2TLS 핸드셰이크로 연결이 맺어지는 과정을 이해할 수 있다
- 3인증서 구조(CA, 체인, 만료일)를 파악하고 확인할 수 있다
- 4openssl s_client로 서버 인증서를 직접 점검할 수 있다
- 5만료·자체서명·핸드셰이크 실패·포트 차단·인증서 체인 끊김 등 실무 에러 5종을 진단할 수 있다
HTTP vs HTTPS — TLS가 하는 일
로그인 API를 만들고 배포했는데 보안 담당자가 "HTTPS 안 쓰면 안 된다"고 합니다. 인증서를 발급받으려니 Let's Encrypt, CSR, PEM 파일 등 낯선 용어가 쏟아집니다. 또 curl https://api.internal이 "certificate verify failed"로 막히는데 왜 안 되는지 알 수 없습니다. TLS가 어떻게 동작하는지 이해하지 못하면 인증서 문제가 생길 때마다 그냥 -k 옵션으로 검증을 끄게 됩니다.
HTTP는 평문(Plain Text)으로 데이터를 전송합니다. HTTPS는 HTTP 위에 TLS(Transport Layer Security) 레이어를 추가합니다.
확대
TLS가 보장하는 것 3가지: 각 속성이 실제로 막는 위협을 함께 이해해야 합니다.
| 속성 | 의미 |
|---|---|
| 기밀성(Confidentiality) | 중간에서 읽을 수 없음 |
| 무결성(Integrity) | 전송 중 변조 감지 |
| 인증(Authentication) | 서버가 진짜 그 서버인지 확인 |
실습 디렉토리를 만든 뒤 HTTP와 HTTPS 응답 헤더를 각각 확인합니다. HTTP는 HTTPS로 리다이렉트하고 HTTPS는 보안 헤더를 포함합니다.
# 실습 디렉토리 준비
mkdir -p /tmp/networking/part3/exam_16 && cd /tmp/networking/part3/exam_16
# HTTP와 HTTPS 응답 헤더 비교
curl -I http://example.com # Location: https://... (리다이렉트)
curl -I https://example.com # 200 OK + 보안 헤더들
curl -I http://example.com- HTTP 응답에서 Location: https:// 헤더 먼저 확인 → 그 다음 응답 코드(301/302) 확인 — Location 없이 200이면 이 서버는 HTTPS 강제 리다이렉트를 하지 않는 구성
- HTTPS 응답에서 Strict-Transport-Security 헤더 존재 여부 확인: max-age=31536000 이상이면 1년 이상 HTTPS 강제, 없으면 HSTS 미설정
- HTTP는 301/302 응답코드, HTTPS는 200 OK — 둘 다 200이면 HTTP도 직접 서비스 중(보안 위험), HTTP가 타임아웃이면 80포트 차단 의심
TLS 핸드셰이크 — 연결 맺는 과정
HTTPS 연결은 데이터를 바로 주고받기 전에 **핸드셰이크(Handshake)**라는 준비 과정을 거칩니다. 처음 만나는 두 사람이 명함을 교환하고 신분을 확인한 뒤 대화를 시작하는 것과 같습니다. 이 과정에서 세 가지 일이 일어납니다: 어떤 암호화 방식을 쓸지 협상하고, 서버가 인증서로 자신의 신원을 증명하며, 암호화에 쓸 키를 안전하게 교환합니다. 이 준비 단계가 완료된 뒤에야 실제 데이터가 암호화되어 전송됩니다.
CA(Certificate Authority, 인증 기관)는 이 과정에서 핵심 역할을 합니다. 서버가 "나는 진짜 example.com입니다"라고 주장할 때, 클라이언트가 그 주장을 믿을 수 있어야 합니다. CA는 이를 보증하는 공증인입니다. Let's Encrypt, DigiCert, GlobalSign 같은 CA가 서버 신원을 검증하고 인증서에 디지털 서명을 합니다. 브라우저는 시스템에 내장된 신뢰 CA 목록(Root CA Store)에 있는 CA가 서명한 인증서만 신뢰합니다. 이것이 CA 신뢰 체계입니다.
아래는 TLS 핸드셰이크 전체 흐름입니다:
확대
인증서 검증 3가지 조건:
- 신뢰할 수 있는 CA(Certificate Authority)가 서명했는가?
- 인증서가 만료되지 않았는가?
- 인증서의 도메인이 접속하는 도메인과 일치하는가?
TLS 핸드셰이크가 실제로 오가는 순서 — ClientHello부터 암호화 HTTP까지 6단계
TLS 핸드셰이크 단계별 — 무엇이 어떤 순서로 오가고, 어디서 깨지나
핸드셰이크가 "암호 협상·인증서 검증·키 교환"이라는 건 알아도, 어떤 메시지가 어떤 순서로 오가는지를 그리지 못하면 curl: (60)이나 handshake failure 같은 에러가 어느 단계에서 났는지 짚을 수 없습니다. 다행히 curl -v와 openssl s_client가 뱉는 로그가 정확히 이 순서를 따라가므로, 흐름을 알면 어느 줄까지 나왔는지만 보고 단계를 특정할 수 있습니다. TCP 443 연결이 맺어진 다음부터 시작합니다.
[클라이언트] [서버]
│ (먼저 TCP 3-way handshake로 443 연결)
│
① ClientHello ─────▶ 지원 TLS버전·암호군(cipher suite) 목록
│ + SNI(접속 도메인) + 키 공유 후보(ECDHE)
│
② ◀───── ServerHello 선택한 버전·암호군 확정
│ ◀───── Certificate 인증서 체인(leaf + 중간 CA)
│ ◀───── 서버 키 공유 + 서버 Finished
│
③ 인증서 검증 (클라이언트가 로컬에서 수행)
│ 체인이 신뢰 Root까지 이어지나 / 유효기간 / 도메인(SAN) 일치 / 폐기(OCSP·CRL)
│
④ 키 교환 완료 → 양쪽이 같은 세션키 산출 (ECDHE, 개인키는 전송 안 함)
│
⑤ (클라이언트) Finished ─────▶ 지금까지 주고받은 값의 검증치 교환
│
⑥ ===== 이후 HTTP가 세션키로 대칭 암호화되어 오감 =====
▼
[암호화된 HTTP] GET / HTTP/2 ... (도청·변조 불가)
TLS 1.3은 ①에 키 공유 후보를 미리 실어 보내 왕복 한 번(1-RTT)에 끝납니다(1.2는 2-RTT).
각 단계에서 무슨 일이 일어나고, 막히면 어떤 에러인가:
| 단계 | 하는 일 | 여기서 막히면 |
|---|---|---|
| ① ClientHello | 클라이언트가 지원 TLS버전·암호군 목록 + SNI(도메인)를 평문으로 제시 | 공통 버전·암호군 없음 → handshake failure·Connection reset. SNI 누락 → 서버가 기본 vhost 인증서를 줌 |
| ② ServerHello + Certificate | 서버가 버전·암호군을 확정하고 인증서 체인을 제시 | 중간 CA 누락(leaf만 배포) → unable to get local issuer certificate |
| ③ 인증서 검증 | 체인·유효기간·도메인(SAN)·폐기 여부를 클라이언트가 로컬 검증 | 만료 → certificate has expired(60) / 자체서명 → self signed(60) / SAN 불일치 → no alternative certificate subject name matches |
| ④ 키 교환(ECDHE) | 임시 키로 세션키를 합의. 개인키는 전송하지 않아 과거 트래픽도 안전(전방향 비밀성) | 이 단계 자체 에러는 드묾 — 실패는 대개 ①②③에서 먼저 남 |
| ⑤ Finished | 지금까지의 핸드셰이크 값 검증치를 교환해 위·변조 여부 확인 | 중간자(MITM)가 값을 바꿨으면 검증 실패 → 연결 abort |
| ⑥ 암호화 HTTP | 합의한 세션키로 대칭 암호화해 HTTP를 송수신 | 여기서 멈추면 TLS가 아니라 HTTP·앱 문제 |
즉 브라우저에 자물쇠가 뜬다는 건 ①~⑤가 모두 성공해 암호화·인증·무결성이 확보됐다는 뜻입니다. MITM(중간자)이 막히는 핵심도 이 순서에 있습니다 — ③에서 "신뢰 CA가 서명한, 그 도메인의, 안 만료된" 인증서만 통과시키고, ④에서 개인키를 보내지 않고도 세션키를 합의하며, ⑤의 Finished로 핸드셰이크 자체가 변조되지 않았음을 확인하기 때문입니다. 진단할 때는 curl -v·openssl s_client가 이 순서대로 로그를 내므로 어느 줄까지 나왔는지로 단계를 특정합니다 — SSL connection using TLSv1.3까지 갔는데 verify에서 실패하면 ③(인증서 문제), ServerHello 전에 reset이면 ①(버전·암호군 불일치)입니다. 단, openssl s_client는 -servername 도메인으로 SNI를 직접 줘야 ②에서 서버가 올바른 인증서를 고른다는 점을 잊지 마세요(안 주면 기본 vhost 인증서가 나와 엉뚱한 진단을 하게 됩니다).
curl verbose 모드로 TLS 핸드셰이크 세부 과정을 확인합니다. 어떤 TLS 버전이 협상됐는지, 인증서 발급자(issuer)와 만료일이 무엇인지 볼 수 있습니다.
# curl -v로 핸드셰이크 과정 확인
curl -v https://example.com 2>&1 | grep -E "TLS|SSL|certificate|subject|expire"
curl -v https://example.com 2>&1 | grep -E 'TLS|SSL|certificate|subject|expire'- TLS 버전 먼저 확인: TLSv1.3이면 최신, TLSv1.2이면 허용 범위, TLSv1.1 이하이면 보안 취약 — 그 다음 expire date 확인
- expire date(Not After) 기준: 30일 이내이면 갱신 준비 알림, 7일 이내이면 즉시 갱신, 이미 과거 날짜이면 curl: (60) SSL certificate problem: certificate has expired 에러 발생
- SSL certificate verify ok 있고 expire date가 미래이고 subject가 접속 도메인과 일치 → 인증서 정상; 셋 중 하나라도 불일치하면 TLS 연결 실패
openssl s_client — 인증서 직접 점검
HTTPS 접속이 안 되는데 원인이 인증서 만료인지, 체인 문제인지, 도메인 불일치인지 알 수 없습니다. 브라우저는 "안전하지 않은 연결"이라는 오류만 보여줄 뿐입니다. openssl s_client로 직접 TLS 핸드셰이크를 해보면 인증서의 모든 정보를 즉시 확인할 수 있습니다.
실습: HTTPS 장애 5단계 진단 플로우
포트 차단인지 인증서 문제인지 구분하려면 TCP 연결 자체부터 확인합니다. nc가 성공해야 TLS 진단으로 넘어갈 수 있습니다.
# 1단계: 포트부터 확인 (가장 먼저)
nc -zv example.com 443
# Connection to example.com 443 port [tcp/https] succeeded! → 포트 열림
# nc: Connection refused → 443 차단
nc -zv example.com 443- nc 출력에서 "succeeded" 단어 먼저 확인 → 있으면 TCP 연결 정상이므로 TLS 진단(openssl s_client)으로 진행
- 응답 시간 기준: 즉각 "Connection refused" = 포트 REJECT(방화벽이 RST 반환); 3초 이상 대기 후 "timed out" = 방화벽 DROP(패킷 소멸)
- nc 성공(succeeded)이고 openssl s_client가 실패하면 → TCP는 열렸지만 TLS 협상 실패; nc도 실패이면 방화벽 또는 서버 미기동 문제
openssl s_client로 서버와 TLS 핸드셰이크를 맺고 인증서의 유효기간을 파싱합니다. notAfter 필드가 현재 날짜보다 과거면 만료된 인증서입니다.
# 만료일만 빠르게 확인
openssl s_client -connect example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -dates
# 출력:
# notBefore=Jan 1 00:00:00 2024 GMT
# notAfter=Dec 31 23:59:59 2024 GMT ← 이 날짜가 만료일
openssl s_client -connect example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -dates- notBefore → notAfter 순서로 확인 — notBefore가 현재보다 미래이면 인증서가 아직 유효하지 않음(시스템 시간 오류 의심), notAfter가 과거이면 만료
- notAfter 날짜와 오늘 차이: 30일 이상 남으면 정상, 30일 이내 = 갱신 준비 필요, 0일 = 오늘 만료, 음수 = 이미 만료 — certbot renew 후 반드시 nginx reload 실행
- verify return:1 이고 notAfter가 미래 → 인증서 체인 및 유효기간 정상; verify return:0 이면 체인 검증 실패 — 중간 CA 인증서 누락 의심
인증서의 subject/SAN(Subject Alternative Name)이 접속 도메인과 일치하는지, 그리고 중간 CA 체인이 완전한지 확인합니다.
# 4단계: 도메인-인증서 일치 확인
openssl s_client -connect example.com:443 </dev/null 2>/dev/null \
| openssl x509 -noout -subject -subjectAltName
# 5단계: 체인 완전성 확인
openssl s_client -connect example.com:443 -showcerts </dev/null 2>&1 \
| grep "Certificate chain"
openssl s_client -connect example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -subject -subjectAltName- subject CN 먼저 확인 → 그 다음 subjectAltName의 DNS 목록 확인 — CN과 접속 도메인이 달라도 SAN에 포함되면 유효(모던 브라우저는 CN 무시하고 SAN만 확인)
- SAN 항목 수 기준: DNS:*.example.com 와일드카드가 있으면 서브도메인 전체 커버, 없으면 접속 도메인이 정확히 포함되어야 — 누락 시 curl: (51) SSL: no alternative certificate subject name matches
- Certificate chain depth 0(서버) + depth 1(중간 CA) + depth 2(루트 CA) 모두 있으면 체인 완전; depth 1이 없으면 중간 CA 누락 → 일부 클라이언트에서 "unable to get local issuer certificate" 발생
인증서 만료
이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.
# 증상
curl: (60) SSL certificate problem: certificate has expired
More details here: https://curl.haxx.se/docs/sslcerts.html
# 원인: 서버 TLS 인증서 유효기간 초과
# 진단: 실제 만료일 확인
openssl s_client -connect api.example.com:443 </dev/null 2>/dev/null \
| openssl x509 -noout -dates
# notAfter=Apr 10 00:00:00 2026 GMT → 이미 지난 날짜
# 해결 (Let's Encrypt 사용 시)
sudo certbot renew --force-renewal
sudo systemctl reload nginx # ← 이 단계를 빠뜨리면 안 됨
# 자동 갱신 cron 확인 (보통 certbot이 설정)
cat /etc/cron.d/certbot
# 또는
systemctl list-timers | grep certbot
# 갱신 후 적용 확인
openssl s_client -connect api.example.com:443 </dev/null 2>/dev/null \
| openssl x509 -noout -dates
자체 서명 인증서
# 증상: 내부 서버, 개발 환경에서 자주 발생
curl: (60) SSL certificate problem: self signed certificate
# 원인: 공인 CA가 아닌 자체 서명 인증서 사용
# 임시 우회 (개발/테스트만)
curl -k https://dev.internal.company.com/api
# 올바른 해결: 자체 CA 인증서를 신뢰 목록에 추가
# curl의 경우
curl --cacert /etc/ssl/internal-ca.crt https://dev.internal.company.com/api
# 시스템 전체 신뢰 (Ubuntu)
sudo cp internal-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
TLS 핸드셰이크 실패
# 증상
curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to example.com:443
# 원인: TLS 버전 또는 암호화 스위트 불일치
# 진단: 버전별 테스트
openssl s_client -connect example.com:443 -tls1_2 </dev/null 2>&1 | grep "Protocol"
openssl s_client -connect example.com:443 -tls1_3 </dev/null 2>&1 | grep "Protocol"
# 서버가 지원하는 TLS 버전 확인
nmap --script ssl-enum-ciphers -p 443 example.com
# nginx에서 TLS 버전 설정 (최신 권고)
# /etc/nginx/nginx.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
심화 — SNI: 하나의 IP·443에 여러 인증서가 공존하는 원리
심화: SNI(Server Name Indication) — 서버가 '어떤 인증서를 줄지' 고르는 기준
한 대의 서버가 IP 하나·443 포트 하나로 수십 개 도메인을 서비스하는 건 흔한 일입니다. 그런데 인증서는 도메인마다 다릅니다. 서버는 핸드셰이크의 어느 시점에, 무엇을 보고 '이 접속엔 이 도메인 인증서를 줘야겠다'고 판단할까요? 그 답이 SNI이고, 이걸 모르면 진단 도구가 엉뚱한 인증서를 보여줄 때 원인을 못 찾습니다.
- SNI는 ClientHello에 담깁니다: 클라이언트는 핸드셰이크 첫 메시지인 ClientHello에 '나는 api.example.com에 접속하려 한다'는 도메인 이름을 SNI 확장으로 담아 보냅니다. 서버는 그 값을 보고 해당 도메인의 인증서를 골라 Certificate 메시지로 제시합니다. 즉 인증서 선택은 데이터를 주고받기 한참 전, 핸드셰이크 초입에 결정됩니다.
- SNI는 (아직) 평문입니다: ClientHello는 암호화 키를 합의하기 전이라 평문입니다. 그래서 SNI에 담긴 도메인 이름도 네트워크 경로에서 그대로 보입니다. tcpdump로 ClientHello를 잡으면 접속 도메인이 눈에 보이고, 이 노출을 없애려 나중에 ECH(Encrypted Client Hello)가 나왔습니다.
- SNI가 없으면 기본(default) 인증서가 나옵니다: 아주 오래된 클라이언트나, SNI를 명시하지 않은 진단 도구는 SNI를 안 보냅니다. 그러면 서버는 '어느 도메인인지 모르니' 기본 가상호스트의 인증서를 제시합니다. 접속 도메인과 다른 인증서가 나와 도메인 불일치처럼 보이지만, 실제 인증서는 멀쩡한 경우가 많습니다.
- curl은 자동, openssl은 수동: curl은 URL의 호스트명으로 SNI를 자동 전송합니다. 반면 openssl s_client는
-servername을 줘야 SNI를 보냅니다. 이 차이가 '브라우저·curl은 되는데 openssl로는 이상한 인증서가 나온다'의 정체입니다.
정리하면, 여러 인증서가 한 IP를 공유할 수 있는 건 SNI 덕분이고, 인증서를 점검할 땐 반드시 접속할 도메인을 SNI로 명시해야 서버가 '진짜 그 도메인 인증서'를 보여줍니다.
상황: 특정 도메인의 인증서 만료일을 점검하려고 openssl s_client -connect api.example.com:443으로 붙었더니, subject에 전혀 다른 도메인(예: 서버의 기본 사이트)이 찍혀 나옵니다. 도메인 불일치라고 판단해 인증서를 재발급하려다, 정작 브라우저와 curl로는 api.example.com이 정상 접속되는 걸 보고 혼란에 빠집니다.
원인: 그 서버는 IP 하나·443 포트 하나에 여러 도메인 인증서를 올린 가상호스트 구성입니다. 서버는 ClientHello의 SNI를 보고 인증서를 고르는데, openssl s_client는 -servername을 주지 않으면 SNI를 아예 넣지 않습니다. SNI가 없으니 서버는 어느 도메인인지 몰라 기본(default) 가상호스트의 인증서를 제시한 것입니다. 인증서 자체는 정상이고, 진단 도구가 '다른 인증서'를 보고 있었을 뿐입니다.
진단: -servername을 붙여 다시 확인합니다.
# SNI 없이(잘못된 방법) — 기본 vhost 인증서가 나옴
openssl s_client -connect api.example.com:443 </dev/null 2>/dev/null \
| openssl x509 -noout -subject
# SNI 명시(올바른 방법) — 진짜 그 도메인 인증서가 나옴
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject
-servername 유무에 따라 반환되는 subject가 달라지면 SNI 문제로 확정입니다.
해결: 인증서를 점검할 땐 항상 -servername 접속도메인을 붙여, curl·브라우저와 같은 조건(SNI 전송)으로 확인합니다. 만약 실제로 SNI를 안 보내는 구형 클라이언트(오래된 안드로이드·자바 등)에서만 오류가 난다면, 그건 서버가 아니라 클라이언트가 SNI 미전송이라는 뜻이므로, 서버의 기본 vhost를 해당 도메인 인증서로 지정하거나 도메인별로 IP를 분리해야 합니다(DNS 동작 과정과 A, CNAME, TXT 레코드 분석으로 도메인이 어느 IP를 가리키는지도 함께 확인).
실무에서 HTTPS 문제를 만나는 순간
Let's Encrypt 자동 갱신 설정: certbot renew를 cron으로 등록하면 인증서 만료를 예방할 수 있습니다.
이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.
# certbot 설치 및 nginx 인증서 발급
sudo certbot --nginx -d example.com -d www.example.com
# 갱신 테스트 (실제 갱신 안 함)
sudo certbot renew --dry-run
# 갱신 후 자동 nginx 리로드 (crontab 또는 deploy-hook)
# /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/bash
systemctl reload nginx
# 만료 30일 전 알림 모니터링 스크립트
EXPIRY=$(openssl s_client -connect $DOMAIN:443 </dev/null 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
echo "$DOMAIN: $DAYS_LEFT days remaining"
내부 서비스 mTLS (Mutual TLS): 클라이언트도 인증서를 제시하는 양방향 TLS 설정입니다.
# 마이크로서비스 간 통신에서 클라이언트 인증서로 양방향 인증
curl --cert client.crt --key client.key \
--cacert ca.crt \
https://internal-service.company.com/api
AWS/클라우드 환경: ACM을 쓰면 인증서 발급과 갱신이 자동화됩니다.
# ACM(AWS Certificate Manager)로 인증서 관리 시
# 갱신은 자동이지만 ALB/CloudFront 연결 상태 확인
aws acm describe-certificate --certificate-arn arn:aws:... \
| jq '.Certificate.NotAfter'
# Security Group에서 443 인바운드 확인
aws ec2 describe-security-groups --group-ids sg-xxx \
| jq '.SecurityGroups[].IpPermissions[] | select(.FromPort==443)'
명령어·단축키 빠른 참조
이 모듈에서 다룬 HTTPS/TLS 진단 명령(openssl s_client·curl·nc)을 실전 옵션과 함께 모았습니다.
| 명령어 | 용도 | 자주 쓰는 예 |
|---|---|---|
openssl s_client -connect host:443 | 서버와 TLS 핸드셰이크·인증서 수신 | -servername host 로 SNI 명시(없으면 기본 vhost 인증서) |
… x509 -noout -dates | 인증서 유효기간(만료일) 확인 | openssl s_client -connect host:443 </dev/null 2>/dev/null | openssl x509 -noout -dates |
… x509 -noout -subject -subjectAltName | CN·SAN 도메인 일치 확인 | 접속 도메인이 SAN(DNS:) 목록에 있는지 대조 |
openssl s_client … -showcerts | 인증서 체인 완전성 확인 | … -showcerts | grep "Certificate chain" (중간 CA 누락 탐지) |
openssl s_client … -tls1_2/-tls1_3 | TLS 버전별 협상 테스트 | 핸드셰이크 실패 시 어느 버전에서 되는지 특정 |
openssl x509 … -checkend | 만료 임박 여부 종료코드 판정 | … -checkend $((30*86400)) (30일 내 만료면 cron 알림) |
curl -v https://host | TLS 핸드셰이크·인증서 로그 | curl -v https://host 2>&1 | grep -E 'TLS|subject|expire' |
curl -k / --insecure | 인증서 검증 우회(개발 전용) | 프로덕션 금지 — 대신 curl --cacert ca.crt |
curl --cert/--key/--cacert | mTLS 양방향 인증 호출 | curl --cert client.crt --key client.key --cacert ca.crt https://svc/api |
nc -zv host 443 | 443 포트 도달(L4) 분리 확인 | succeeded면 TLS(L6)로, refused/timeout이면 포트 차단 |
nmap --script ssl-enum-ciphers | 서버 지원 TLS 버전·암호군 열거 | nmap --script ssl-enum-ciphers -p 443 host |
certbot renew | Let's Encrypt 인증서 갱신 | sudo certbot renew --dry-run 후 갱신, 반드시 systemctl reload nginx |
관련 모듈로 더 깊이:
- curl 명령어로 HTTP 상태 코드 및 헤더 분석하기 — TLS 위에서 오가는 HTTP 요청·응답을 curl로 직접 들여다보는 법
- telnet과 nc(netcat) 명령어로 L4 포트 통신 상태 점검 — 443 포트 자체가 열려 있는지부터 끊어서 확인하는 법
- 인바운드/아웃바운드 포트 접속 장애 실전 디버깅 — 443 인바운드가 보안 그룹에서 막혔을 때 진단하는 법
다음 모듈에서는 Connection Timeout과 Read Timeout의 차이와 실전 디버깅 방법을 다룹니다.