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

TCP & TLS Underneath — The Layer Cake You Pretend Not to See

~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
"You can ship a career writing HTTP code without ever looking at TCP. Until the day a connection hangs, a TLS handshake fails, or a CDN drops you mid-request. Then you wish you'd looked."

The Layer Cake

Every HTTP request travels through a stack of protocols, each pretending to be a clean abstraction over the one below:

  • HTTP — the message you write (method, URI, headers, body).
  • TLS — optional encryption wrapper. Present for HTTPS, absent for HTTP.
  • TCP — reliable, ordered byte stream. Hides packet loss, retransmits, reorders.
  • IP — best-effort packet delivery between hosts. No ordering, no reliability.
  • Ethernet / Wi-Fi — bits on the wire.

HTTP/3 swaps the middle of the cake: QUIC (which bundles TLS 1.3 and reliability) runs over UDP, skipping TCP entirely. Same HTTP semantics on top; very different transport underneath.

TCP — The Reliability Lie

IP packets get lost, reordered, duplicated, and dropped. TCP's job is to pretend that doesn't happen. It opens a connection with a three-way handshake (SYN → SYN-ACK → ACK), assigns sequence numbers to bytes, retransmits what gets lost, and delivers a clean stream to whoever's reading.

Cost: 1 round trip just to set up a TCP connection before you can even send your first HTTP byte. From Seoul to a US-East server, that's ~150ms of nothing. HTTP/1.1 used to open a new TCP connection per request, which is why old web pages felt sticky-slow. Keep-Alive (Track 5) and HTTP/2 multiplexing fixed it by reusing one connection for many requests.

TLS — The Encryption Wrapper

HTTPS is HTTP wrapped in TLS. The first time you connect, TLS negotiates a cipher suite, exchanges keys, and verifies the server's certificate. After that, every HTTP byte is encrypted on the way out and decrypted on the way in. Your HTTP code doesn't see a difference; it just calls https:// instead of http://.

Cost: 2 round trips for TLS 1.2 (handshake + key exchange), or 1 round trip for TLS 1.3 (handshake and keys combined). TLS 1.3 also supports 0-RTT resumption for return visits. If your API client feels slow on the first request and fast after, TLS handshake is usually the culprit.

Most production HTTP bugs are TCP or TLS bugs in disguise. Mystery hangs, dropped connections, 'works on localhost but not on Tailscale,' intermittent 502s — these are almost never an HTTP issue. They're a connection issue one layer down. Learn to use openssl s_client, tcpdump, and curl -v to peek through the abstraction.

HTTPS = Port 443; HTTP = Port 80

By convention. HTTP goes to TCP port 80 unless you say otherwise; HTTPS goes to TCP port 443. cwkPippa runs on :8000 (FastAPI backend) and :5173 (Vite frontend) on plain HTTP because it's local-only behind Tailscale. The moment you expose anything to the public internet without TLS, every router between you and the user can read it.

HTTP/3 and QUIC

HTTP/3 throws out TCP and rides on QUIC, a UDP-based protocol that does its own reliability + ordering + crypto in one bundle. The win: faster handshake (combined with TLS), no head-of-line blocking at the transport layer, and connection migration (your phone switching from Wi-Fi to LTE without dropping the session). The catch: less universally deployed than HTTP/2 and harder to debug with traditional tools.

Pippa's Confession

When cwkPippa's WebUI hangs for 30 seconds and then dies with "connection refused," 95% of the time the FastAPI backend on :8000 just died. The browser tries HTTP, the TCP SYN goes unanswered, and after the OS timeout you get a brutal error. The HTTP code is innocent. I learned to run lsof -i :8000 before any other diagnostic.

Code

Hand-roll an HTTP request over raw TCP with netcat·bash
# Peek at the TCP handshake to a server
# (nc = netcat; works on macOS and Linux)
nc -v localhost 8000

# What you see:
# Connection to localhost port 8000 [tcp/...] succeeded!
# ^ That single 'succeeded' line is the SYN → SYN-ACK → ACK handshake completing.
# Now type an HTTP request literally:
GET / HTTP/1.1
Host: localhost:8000

# (blank line — press Enter twice)
# The server's response appears.
openssl s_client = TLS x-ray machine for any HTTPS server·bash
# Peek through TLS to a real HTTPS server
openssl s_client -connect creativeworksofknowledge.com:443 -servername creativeworksofknowledge.com

# What you see:
# - Certificate chain (server cert + intermediates + root)
# - Negotiated cipher suite (e.g. TLS_AES_256_GCM_SHA384)
# - Protocol version (TLSv1.3)
# - Then a blank prompt — type an HTTP request like you would in netcat:
GET / HTTP/1.1
Host: creativeworksofknowledge.com

# The HTTP response comes back, fully decrypted by openssl.
Measure the TCP + TLS handshake cost yourself·python
import httpx
import time

# HTTP vs HTTPS — your code is identical; the wire is not.
start = time.perf_counter()
resp = httpx.get('http://localhost:8000/api/health')
print(f'HTTP first request: {(time.perf_counter() - start) * 1000:.1f}ms')

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

# Re-use a connection (keep-alive) — second request should be much faster
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 reused: {(time.perf_counter() - start) * 1000:.1f}ms')

External links

Exercise

Run openssl s_client -connect <any-https-site>:443 -servername <same-site> against a real HTTPS server (try creativeworksofknowledge.com, github.com, your own deployment). Answer three questions from the output: (1) What TLS version did the server agree to? (2) What cipher suite did it pick? (3) How many certificates are in the chain (server cert + intermediates + how many)? Bonus: re-run with -tls1_2 to force TLS 1.2 and watch the handshake change.
Hint
TLS version shows in the 'Protocol' line; cipher suite in the 'Cipher' line; certificate chain in the section starting with 'Certificate chain' (count s: lines for subject = one cert each). Modern servers should give you TLS 1.3 and an AES-GCM cipher. A chain of 3 (server + intermediate + root) is typical.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.