HTTPS 완전 정복 — HTTP에 TLS를 더하면

HTTPS의 동작 원리(TCP+TLS+HTTP), HTTP에서 HTTPS 리다이렉트, HSTS, Certificate Transparency, OCSP Stapling, CAA 레코드 설정까지 실무 위주로 정리합니다.

· 5 min read · PALDYN Team

지난 글에서 인증서와 PKI를 살펴봤습니다. 이제 HTTP와 TLS가 결합해 만들어지는 HTTPS를 전체 흐름과 함께 정리합니다. HTTPS는 단순히 “HTTP + 암호화”가 아니라, 여러 보안 메커니즘이 협력하는 체계입니다.

HTTPS = TCP + TLS + HTTP

HTTPS 연결이 열리는 과정은 세 단계로 나뉩니다.

HTTPS 전체 흐름

Phase 1: TCP 3-Way Handshake (포트 443)
  클라이언트 → SYN → 서버
  서버 → SYN-ACK → 클라이언트
  클라이언트 → ACK → 서버
  (일반 TCP와 동일, 약 0.5 RTT)

Phase 2: TLS 핸드셰이크
  TLS 1.3: 1-RTT (총 연결 비용 1.5 RTT)
  TLS 1.2: 2-RTT (총 연결 비용 2.5 RTT)

Phase 3: 암호화된 HTTP 통신
  GET /path HTTP/1.1 Host: example.com
  → TLS 레코드로 암호화되어 전송

HTTP → HTTPS 전환

서버 리다이렉트

# Nginx: HTTP를 HTTPS로 리다이렉트
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;
    # ...
}
# Apache: HTTPS 리다이렉트
<VirtualHost *:80>
    ServerName example.com
    Redirect permanent / https://example.com/
</VirtualHost>

리다이렉트의 첫 번째 요청 문제

301 리다이렉트 방식은 최초 HTTP 요청이 평문으로 나가는 문제가 있습니다.

공격자가 최초 HTTP 요청을 가로챌 수 있음:
  클라이언트 → http://bank.com → [공격자 MITM]
  공격자 → 응답 조작 (리다이렉트 없이 가짜 페이지)

이 문제를 해결하는 것이 HSTS입니다.

HSTS (HTTP Strict Transport Security)

# Nginx HSTS 설정
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

HSTS가 작동하는 방식:

최초 방문 (HTTPS): 서버 응답에 HSTS 헤더 포함
브라우저: "example.com은 max-age=31536000초 동안 HTTPS만 사용"

이후 방문 (HTTP 시도):
  브라우저: HTTP 요청 자체를 HTTPS로 업그레이드 (서버에 전혀 안 감)
  → MITM 불가능

preload를 추가하면 hstspreload.org에 등록해 브라우저에 미리 내장된 목록에 도메인을 추가할 수 있습니다. 최초 방문부터 HTTPS만 허용됩니다.

HTTPS 보안 강화 메커니즘

HTTPS 보안 강화 메커니즘

Certificate Transparency (CT)

모든 공인 CA는 발급한 인증서를 공개 CT 로그에 기록해야 합니다.

# 도메인의 인증서 CT 로그 검색
# crt.sh 웹 서비스 이용
curl -s "https://crt.sh/?q=example.com&output=json" | \
  python3 -c "
import sys,json
certs = json.load(sys.stdin)
for c in certs[:5]:
    print(c['not_before'][:10], c['common_name'])
"

크롬 브라우저는 CT 로그에 없는 인증서를 신뢰하지 않습니다(ERR_CERTIFICATE_TRANSPARENCY_REQUIRED).

CAA (Certification Authority Authorization)

DNS 레코드로 이 도메인의 인증서를 발급할 수 있는 CA를 제한합니다.

# CAA 레코드 설정 (DNS zone file)
example.com. CAA 0 issue "letsencrypt.org"
example.com. CAA 0 issue "digicert.com"
example.com. CAA 0 issuewild ";"    # 와일드카드 발급 금지
example.com. CAA 0 iodef "mailto:security@example.com"

# 확인
dig CAA example.com

OCSP Stapling

# Nginx OCSP Stapling (TLS 1.3 포함)
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ca-chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 3s;

# 확인
openssl s_client -connect example.com:443 -status \
  < /dev/null 2>/dev/null \
  | grep -A 20 "OCSP Response"

완성된 Nginx HTTPS 설정

server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    # 인증서
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # TLS 버전 및 암호 스위트
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;

    # 세션 재개
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 1.1.1.1 valid=300s;

    # 보안 헤더
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

HTTPS 설정 검증

# SSL Labs 등급 체크 (웹)
# https://www.ssllabs.com/ssltest/

# 명령줄로 빠른 검증
testssl.sh example.com

# TLS 버전·암호 스위트 확인
nmap --script ssl-enum-ciphers -p 443 example.com

# curl로 인증서 상세 확인
curl -v --insecure https://example.com 2>&1 | grep -E "(SSL|TLS|cert)"

# HSTS 헤더 확인
curl -I https://example.com | grep -i strict

HTTP/3 시대의 HTTPS

HTTP/3는 TCP 대신 UDP 기반 QUIC을 사용합니다.

HTTP/1.1: TCP (80/443) → TLS → HTTP
HTTP/2:   TCP (443) → TLS → HTTP/2
HTTP/3:   UDP (443) → QUIC(TLS 1.3 내장) → HTTP/3

포트 443을 UDP로도 열어야 하며, Alt-Svc 헤더로 클라이언트에게 알립니다.

# Nginx HTTP/3 (quic) 지원
listen 443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400';

지난 글: 인증서와 PKI — X.509, CA 체인, 신뢰 구축

다음 글: 방화벽 완전 정복 — 패킷 필터링부터 NGFW까지


읽어주셔서 감사합니다. 😊