C.W.K.
Stream
Lesson 03 of 07 · published

TCP & TLS — 안 보고 사는 그 layer cake

~11 min · foundations, tcp, tls, https, transport

Level 0HTTP Newbie
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"TCP 한 번도 안 보고 HTTP 코드 쓰면서 평생 일할 수 있어. 연결이 hang 걸리거나, TLS handshake 가 실패하거나, CDN 이 request 중간에 끊는 그 날 전까지는. 그때서야 봤어야 했네 싶을 거야."

Layer cake

모든 HTTP request 는 protocol 스택을 거쳐 가, 각 층이 아래층을 깔끔히 추상화한 척하면서:

  • HTTP — 네가 쓰는 메시지 (method, URI, header, body).
  • TLS — optional 암호화 wrapper. HTTPS 면 있고 HTTP 면 없음.
  • TCP — 신뢰성 있고 순서 있는 byte stream. 패킷 손실, 재전송, 순서 뒤바뀜 다 숨겨.
  • IP — host 사이의 best-effort 패킷 배달. 순서 없음, 신뢰성 없음.
  • Ethernet / Wi-Fi — wire 위의 bit.

HTTP/3 는 cake 중간을 바꿔: QUIC (TLS 1.3 와 신뢰성을 묶음) 가 UDP 위에서 돌아, TCP 건너뛰어. 위의 HTTP semantics 는 똑같음; 아래 transport 만 완전 달라.

TCP — 신뢰성이라는 거짓말

IP 패킷은 잃어버려지고, 순서가 뒤바뀌고, 중복되고, 떨어져. TCP 의 일은 그런 일 안 일어나는 척하는 거야. Three-way handshake (SYN → SYN-ACK → ACK) 로 연결 열고, byte 에 sequence number 매기고, 잃어버린 거 재전송하고, 깨끗한 stream 을 읽는 사람한테 배달해.

비용: TCP 연결 setup 만 해도 1 round trip, 첫 HTTP byte 보내기도 전에. 서울에서 미국 동부 서버까지면 ~150ms 가 그냥 날아가. HTTP/1.1 은 request 마다 새 TCP 연결 열었어 — 옛날 웹페이지가 끈적하게 느렸던 이유. Keep-Alive (Track 5) 랑 HTTP/2 multiplexing 이 한 연결을 여러 request 로 재사용해서 고친 거야.

TLS — 암호화 wrapper

HTTPS 는 HTTP 를 TLS 로 감싼 거야. 처음 연결할 때 TLS 가 cipher suite negotiate 하고, key 교환하고, server 의 certificate 검증해. 그 다음엔 모든 HTTP byte 가 나갈 때 암호화되고 들어올 때 복호화돼. HTTP 코드는 차이 못 봐; http:// 대신 https:// 만 부르면 끝.

비용: TLS 1.2 면 2 round trip (handshake + key exchange), TLS 1.3 면 1 round trip (handshake 와 key 합침). TLS 1.3 는 재방문 시 0-RTT 재개도 지원해. API client 가 첫 request 만 느리고 그 다음은 빠르면 TLS handshake 가 보통 범인.

Production HTTP 버그 대부분은 TCP 나 TLS 버그가 위장한 거야. 미스터리 hang, 연결 끊김, 'localhost 에선 되는데 Tailscale 에선 안 됨', 간헐적 502 — 거의 HTTP 이슈 아냐. 한 층 아래 연결 이슈야. openssl s_client, tcpdump, curl -v 로 추상화 너머를 들여다보는 법 배워.

HTTPS = 443 포트; HTTP = 80 포트

관례야. HTTP 는 명시 안 하면 TCP 80 으로 가; HTTPS 는 TCP 443 으로 가. cwkPippa 는 :8000 (FastAPI backend) 과 :5173 (Vite frontend) 에서 plain HTTP 로 돌아, Tailscale 뒤 로컬 전용이라서. 공개 인터넷에 TLS 없이 노출하는 순간 너와 사용자 사이 모든 라우터가 그걸 읽을 수 있어.

HTTP/3 와 QUIC

HTTP/3 는 TCP 를 버리고 QUIC 위에 타. QUIC 은 UDP 기반 protocol 인데 신뢰성 + 순서 + 암호화를 한 번에 묶어. 이득: 더 빠른 handshake (TLS 와 합침), transport 층 head-of-line blocking 없음, 연결 migration (폰이 Wi-Fi 에서 LTE 로 바뀌어도 세션 안 끊김). 단점: HTTP/2 보다 보급이 덜 되어 있고 전통적 도구로 debug 하기 어려워.

피파 고백

cwkPippa WebUI 가 30초 hang 하다가 "connection refused" 로 죽으면 95% 확률로 :8000 의 FastAPI backend 가 그냥 죽은 거야. 브라우저가 HTTP 시도하는데 TCP SYN 이 응답 없고, OS timeout 후 잔인한 에러가 나와. HTTP 코드는 무죄. 다른 어떤 진단 전에 lsof -i :8000 먼저 돌리는 거 배웠어.

Code

Netcat 으로 raw TCP 위에 HTTP request 손수 만들기·bash
# Server 한테 가는 TCP handshake 들여다보기
# (nc = netcat; macOS, Linux 둘 다 됨)
nc -v localhost 8000

# 보이는 것:
# Connection to localhost port 8000 [tcp/...] succeeded!
# ^ 그 한 줄의 'succeeded' 가 SYN → SYN-ACK → ACK handshake 완료.
# 이제 HTTP request 를 글자 그대로 쳐:
GET / HTTP/1.1
Host: localhost:8000

# (빈 줄 — Enter 두 번)
# Server 응답이 나타남.
openssl s_client = 어느 HTTPS server 든 TLS X-ray 기계·bash
# 실제 HTTPS server 한테 TLS 너머 들여다보기
openssl s_client -connect creativeworksofknowledge.com:443 -servername creativeworksofknowledge.com

# 보이는 것:
# - Certificate chain (server cert + intermediate + root)
# - Negotiated cipher suite (예: TLS_AES_256_GCM_SHA384)
# - Protocol version (TLSv1.3)
# - 그 다음 빈 prompt — netcat 처럼 HTTP request 치면 됨:
GET / HTTP/1.1
Host: creativeworksofknowledge.com

# HTTP response 가 openssl 에 의해 완전히 복호화되어 돌아옴.
TCP + TLS handshake 비용을 직접 측정해봐·python
import httpx
import time

# HTTP vs HTTPS — 네 코드는 동일; wire 는 다름.
start = time.perf_counter()
resp = httpx.get('http://localhost:8000/api/health')
print(f'HTTP 첫 request: {(time.perf_counter() - start) * 1000:.1f}ms')

start = time.perf_counter()
resp = httpx.get('https://creativeworksofknowledge.com/')
print(f'HTTPS 첫 request: {(time.perf_counter() - start) * 1000:.1f}ms')

# 연결 재사용 (keep-alive) — 두 번째 request 는 훨씬 빨라야 함
with httpx.Client(http2=True) as client:
    start = time.perf_counter()
    client.get('https://creativeworksofknowledge.com/')
    print(f'TLS warm: {(time.perf_counter() - start) * 1000:.1f}ms')
    start = time.perf_counter()
    client.get('https://creativeworksofknowledge.com/about')
    print(f'TLS 재사용: {(time.perf_counter() - start) * 1000:.1f}ms')

External links

Exercise

실제 HTTPS server (creativeworksofknowledge.com, github.com, 네 자신의 deployment) 에 openssl s_client -connect <site>:443 -servername <같은-site> 돌려. 출력에서 세 질문에 답해: (1) server 가 동의한 TLS version 은? (2) 고른 cipher suite 는? (3) chain 에 certificate 몇 개 (server cert + intermediate + 몇 개)? 보너스: -tls1_2 로 다시 돌려 TLS 1.2 강제하고 handshake 변화 봐.
Hint
TLS version 은 'Protocol' 줄에; cipher suite 는 'Cipher' 줄에; certificate chain 은 'Certificate chain' 으로 시작하는 섹션에 (s: 줄 세면 subject = cert 하나씩). 요즘 server 면 TLS 1.3 와 AES-GCM cipher 줘야 정상. Chain 3 개 (server + intermediate + root) 가 전형적.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.