Table of Contents#
- Why This Chapter Sits on Top of Everything Else in This Series
- The TLS 1.2 Handshake, in Full Mechanical Detail
- TLS 1.3 — What Actually Changed, and Why It's Faster
- 0-RTT — the Performance Win With a Real Security Tradeoff
- Post-Quantum Key Exchange — Why It's Already Showing Up in Production Handshakes
- Certificate Chains and PKI — How Trust Actually Gets Established
- Certificate Validation Failures — What Each Error Actually Means
- SNI — How One IP Address Serves Many TLS Certificates
- Certificate Revocation — OCSP, CRLs, and Why Stapling Exists
- Certificate Transparency — Catching Mis-Issued Certificates
- mTLS — Mutual Authentication and Why Service Meshes Default to It
- Certificate Rotation at Scale — the Operational Reality of mTLS
- Cipher Suites — What's Actually Being Negotiated, and What to Configure
- HTTP/1.1 — Where Web Protocol Performance Problems Started
- HTTP/2 — Multiplexing, and Its Own New Problem
- HTTP/3 and QUIC — Moving Multiplexing Below TLS
- QUIC's Other Wins — Connection Migration and Built-In 0-RTT
- Choosing and Rolling Out HTTP Versions in Production
- TLS Termination Performance — Where the CPU Cost Actually Goes
- Full Worked Scenario: Rolling Out mTLS and HTTP/3 for checkout-service
- Common Mistakes and Interview Traps
- Worked Practice Problems
- Summary and What's Next
Why This Chapter Sits on Top of Everything Else in This Series#
Every packet this series has discussed so far — routed by BGP in Part 1, spread across backends by a load
balancer in Part 2, carried through a VPC's subnets and gateways in Part 3 — has, for the overwhelming
majority of real production traffic, actually been encrypted TLS traffic. This chapter is about the layer
that makes that possible: the handshake that establishes a secure channel before a single byte of real
application data moves, the trust model (PKI) that lets a client believe it's actually talking to
checkout-service and not an impostor, and the HTTP protocol evolution that has repeatedly changed how
efficiently that secure channel can actually be used.
None of this is optional infrastructure trivia — a slow or broken TLS handshake adds real, measurable latency to every single new connection a service receives, a misconfigured certificate chain is one of the most common categories of "the service is technically up but no client can reach it" incidents, and the HTTP/1.1-to-HTTP/3 evolution directly shapes decisions this series has already covered from the load-balancing side (Part 2) and will return to from the CDN/edge side (Part 5).
Throughout this chapter, the throughline returns once more to checkout-service, catalog-service, and
inventory-service — this time viewed through the lens of exactly what protects a customer's payment
details in transit, what proves inventory-service is really talking to checkout-service and not an
impostor sitting on the same internal network, and what makes a mobile customer's checkout survive a spotty
cellular handoff without losing their place in the flow.
The TLS 1.2 Handshake, in Full Mechanical Detail#
TLS (Transport Layer Security) establishes three things before any application data flows: that both sides agree on how to encrypt traffic, that the client can verify the server's identity, and that both sides derive a shared secret key neither ever transmits directly. Walking through TLS 1.2's handshake in full — even though TLS 1.3 (next section) has since streamlined it — builds the mental model needed to understand exactly what TLS 1.3 actually removed, and why that removal is safe.
Two full round trips (2-RTT) elapse before any real application data can be sent — every one of those round trips costs real, physical time proportional to the network latency between client and server, paid on every new connection.
The steps that matter operationally: the Certificate message is where the server proves its identity
(covered in depth in the PKI section below), the key-exchange messages are where both sides derive a shared
symmetric key using asymmetric cryptography without ever transmitting that key itself (classically Diffie-
Hellman or its elliptic-curve variant, ECDHE — the "E" specifically meaning ephemeral, a fresh key pair
generated per session rather than reused, which is what gives TLS its forward secrecy property: even if
a server's long-term private key is later compromised, past sessions' traffic — encrypted with a
session-specific ephemeral key that was never retained — stays unrecoverable), and Finished is the first
message actually protected by the newly-derived key, serving as cryptographic proof the handshake itself
wasn't tampered with in transit.
TLS 1.3 — What Actually Changed, and Why It's Faster#
TLS 1.3, finalized in 2018 and now the default on essentially every modern client and server, cuts the
handshake from 2 round trips to 1 — a real, measurable 50-100ms improvement per new connection depending on
network latency, entirely from protocol redesign, with no hardware change required. The core change: TLS
1.3 drastically narrows the set of negotiable cipher suites and key-exchange methods (removing several
older, less secure options TLS 1.2 had to support for backward compatibility) and, critically, lets the
client send its key-share speculatively in the ClientHello itself, guessing which key-exchange group the
server will pick — correctly, the overwhelming majority of the time, since the server-supported set is now
small and predictable — collapsing what previously took two separate round trips into one.
Half the round trips of TLS 1.2 — the server now bundles everything it needs to send into a single response flight, rather than the separate multi-message exchange TLS 1.2 required.
| TLS 1.2 | TLS 1.3 | |
|---|---|---|
| Handshake round trips (new connection) | 2-RTT | 1-RTT |
| Handshake round trips (resumed session) | 1-RTT | 0-RTT (with the tradeoffs covered next) |
| Cipher suite negotiation | Broad, includes legacy/weaker options for compatibility | Narrowed to a small set of modern, forward-secret-only options |
| Forward secrecy | Optional (depends on cipher suite chosen) | Mandatory for every connection |
| Renegotiation (mid-session cipher change) | Supported — also a historical source of real vulnerabilities | Removed entirely |
Tip
Best practice: TLS 1.3 should be the default and TLS 1.2 the only fallback, with anything older (TLS 1.1, TLS 1.0, and definitely SSLv3) disabled entirely on any new service. The narrower, mandatory- forward-secrecy cipher suite set in TLS 1.3 isn't just about speed — it closes multiple real historical vulnerability classes (renegotiation attacks, weak-cipher downgrade attacks) that existed specifically because TLS 1.2's broader negotiable surface gave an attacker more to work with. A service still supporting TLS 1.0/1.1 for legacy client compatibility should treat that as a deliberately accepted, actively tracked risk with a retirement plan, not a permanent default.
0-RTT — the Performance Win With a Real Security Tradeoff#
For a resumed session (a client reconnecting to a server it already has an established session with), TLS 1.3 supports 0-RTT — the client sends its actual application-layer request data (the "early data") in the very first flight, before the handshake has even fully completed, based on a previously-negotiated session resumption key. This eliminates the handshake's round-trip cost entirely for a returning client — genuinely the fastest possible TLS connection establishment.
The tradeoff is a real, specific replay-attack window: because 0-RTT data is sent before the server has had any chance to verify the client isn't simply replaying a previously-captured, valid 0-RTT message, an attacker who intercepts a legitimate 0-RTT flight can resend it, and the server — having no way to distinguish "the real client reconnecting" from "an attacker replaying a captured message" purely from the 0-RTT data itself — may process it again.
Warning
Never accept 0-RTT data for a non-idempotent operation. A GET request for a public, cacheable
resource is a safe candidate for 0-RTT (replaying it just re-fetches the same content, with no side
effect). A POST /checkout/charge request accepted over 0-RTT is genuinely dangerous — a replayed 0-RTT
charge request could double-charge a customer, echoing the exact retry-storm-without-idempotency failure
mode covered from the load-balancing side in Part 2. Production TLS termination points (Envoy, nginx, most
CDNs) support explicitly disabling 0-RTT for specific routes, or require the application layer to
implement its own anti-replay check (a nonce, an idempotency key) before trusting any 0-RTT-delivered
request — the protocol-level convenience doesn't remove the application's own responsibility here.
Post-Quantum Key Exchange — Why It's Already Showing Up in Production Handshakes#
A forward-looking but already-live addition to the TLS 1.3 handshake covered above: a sufficiently powerful future quantum computer could break the classical elliptic-curve key exchange (ECDHE) this chapter has described throughout — not by attacking the symmetric encryption directly, but by solving the mathematical problem ECDHE's security depends on. This matters today, not just once such a computer exists, because of the "harvest now, decrypt later" threat model: an adversary capable of recording encrypted TLS traffic today can simply store it, and decrypt it retroactively once a capable quantum computer exists — meaning any traffic that needs to stay confidential for years (not just seconds) is already at risk from a threat that hasn't fully materialized yet.
Hybrid key exchange is the production-deployed mitigation already in use: a TLS 1.3 handshake performs
both the classical ECDHE exchange and a post-quantum key encapsulation mechanism (ML-KEM, standardized
by NIST as FIPS 203) simultaneously, combining both results into the final shared secret — an attacker
would need to break both the classical and the post-quantum algorithm to recover the session key, so the
hybrid approach is never weaker than classical-only ECDHE, only ever stronger. X25519MLKEM768 — combining
the classical X25519 elliptic curve with ML-KEM-768 — is standardized for TLS 1.3 (RFC 9794) and, as of
2026, already enabled by default in Chrome and Firefox on the client side, and supported by major CDN edges
(Cloudflare among the earliest adopters) on the server side.
The real cost of adopting this today is bandwidth, not CPU: ML-KEM's public keys and ciphertexts are
meaningfully larger than classical ECDHE's — combining both roughly doubles the ClientHello's size,
occasionally pushing it past the size of a single network packet on constrained links, which can require an
extra round trip on an already-slow connection purely due to fragmentation. For most production traffic on
typical broadband/mobile links this is immaterial; it's a real, measurable consideration only for the
narrower case of a genuinely bandwidth- or packet-loss-constrained network path.
Note
Hybrid key exchange protects the confidentiality of the session's data in transit — it does not yet
extend to the certificate's own signature algorithm. As of this writing, a connection negotiating
X25519MLKEM768 for its key exchange is still very likely authenticated with a classical ECDSA or RSA
certificate signature underneath — meaning the data is protected against a future quantum-capable
adversary, but the server identity verification mechanism itself is not yet, since post-quantum
signature algorithms and the CA/certificate ecosystem supporting them are on a slower, still-maturing
adoption curve than key exchange. A platform team evaluating post-quantum readiness needs to track both
pieces separately, not assume enabling hybrid key exchange alone completes the picture.
Certificate Chains and PKI — How Trust Actually Gets Established#
A TLS certificate, by itself, is just a cryptographically signed claim — "this public key belongs to
checkout-service.example.com" — and that claim is only trustworthy if the signer is itself trusted.
Public Key Infrastructure (PKI) is the hierarchical trust model that makes this chain of claims actually
work at internet scale: a small set of root Certificate Authorities (CAs) are pre-trusted (their public
keys ship baked into every operating system and browser), and every certificate a server actually presents
is signed by an intermediate CA, which is itself signed by a root CA — a chain of trust the client
walks and verifies at connection time.
A client verifies this chain bottom-up: does the leaf cert's signature check out against the intermediate's public key, and does the intermediate's own signature check out against a root the client already trusts? Every link must hold, or the whole chain is rejected.
Root CAs deliberately never sign leaf certificates directly — the root's private key is kept in extremely tightly controlled, largely offline conditions specifically because compromising it would be catastrophic (every certificate that root has ever signed becomes suspect); intermediates do the actual day-to-day signing work, and can be revoked/rotated far more easily if one is ever compromised, without touching the root at all.
Automated certificate issuance (Let's Encrypt, and the ACME protocol it popularized) has made short-lived, free, automatically-renewed certificates the modern default for public-facing services — a genuine security improvement over the old model of long-lived (1-2 year), manually-renewed certificates, since a shorter validity window shrinks the damage window if a private key is ever compromised, and automation removes the human-error-driven "the certificate expired and nobody renewed it in time" outage class almost entirely for any service using it correctly.
Tip
Best practice: automate certificate renewal end to end, with alerting on renewal failure, not just on approaching expiry. A calendar reminder to manually renew a certificate is a real, recurring outage waiting to happen the one time it's missed; an automated ACME renewal job that silently fails (a DNS validation record misconfigured, a rate limit hit) and nobody notices until the certificate actually expires is functionally the same failure mode wearing different clothes. The fix in both cases is the same: alert on the automation itself failing, days before the certificate's actual expiry, not just on the expiry date arriving.
Certificate Validation Failures — What Each Error Actually Means#
A production on-call engineer benefits enormously from being able to read a TLS error and immediately know which layer of the chain-of-trust model actually failed, rather than treating every certificate error as one undifferentiated "TLS is broken" signal:
| Error | What actually failed | Common real cause |
|---|---|---|
certificate has expired | The leaf (or an intermediate) cert's validity window has passed | An automated renewal job silently stopped running, or a manually-managed cert was never renewed |
unable to get local issuer certificate | The client can't build a complete chain up to a trusted root | The server is missing an intermediate certificate in what it sends — a very common misconfiguration, since browsers often cache intermediates from prior visits and mask this, while a fresh curl/API client does not |
certificate is not valid for this hostname | The cert's Subject Alternative Name (SAN) list doesn't include the hostname actually being requested | Requesting checkout-service.internal when the cert only covers checkout-service.example.com, or a wildcard cert not covering the specific subdomain pattern in use |
self-signed certificate / unable to verify the first certificate | The chain terminates at a cert not present in the trust store at all | Legitimate for internal-only mTLS with a private CA (next section) — a genuine error for anything meant to be publicly trusted |
certificate revoked | The issuing CA has explicitly revoked this specific certificate before its natural expiry | The private key was known to be compromised, or the certificate was issued in error |
Important
unable to get local issuer certificate is one of the single most common real production TLS incidents,
and it's almost always a server-side misconfiguration, not a client problem — the fix is ensuring the
server sends its full intermediate chain, not just the leaf certificate, on every TLS handshake. A quick
diagnostic: openssl s_client -connect host:443 -showcerts shows exactly which certificates the server is
actually presenting, and a chain missing an intermediate is immediately visible in that output.
SNI — How One IP Address Serves Many TLS Certificates#
Before TLS can send a certificate, it needs to know which certificate to send — a real problem once many
unrelated HTTPS sites are hosted behind the same IP address (the normal case for any CDN, any shared load
balancer, any multi-tenant hosting platform). Server Name Indication (SNI) solves this by having the
client include the requested hostname, in cleartext, in the very first ClientHello message — before any
encryption has been established — letting the server pick and present the correct certificate for that
specific hostname, out of potentially thousands it might be serving on the same IP.
⚠️ A genuine, still-relevant privacy consequence: because SNI is sent unencrypted, any network observer between client and server — an ISP, a corporate network monitoring proxy, a censoring nation-state firewall — can see which hostname a client is connecting to, even though the actual request/response content remains fully encrypted. Encrypted Client Hello (ECH), the IETF's newer mechanism to close this specific gap, encrypts the SNI field itself using a separately, publicly-published key — adoption is still growing as of 2026 (led by major browsers and large CDNs, similar to HTTP/3's own adoption curve), but it isn't universal, and SNI-based hostname visibility remains the practical reality for most traffic today.
Note
This same cleartext-SNI property is precisely what makes L4 TLS passthrough load balancing (covered from the load-balancing side in Part 2) able to route based on hostname without decrypting anything — a passthrough load balancer reads the plaintext SNI field, matches it against a routing table, and forwards the still-fully-encrypted connection to the correct backend, getting hostname-aware routing without ever needing the private key to decrypt.
Certificate Revocation — OCSP, CRLs, and Why Stapling Exists#
A certificate can become untrustworthy before its natural expiry — a private key leaks, a CA discovers it mis-issued a certificate — and the mechanism for communicating "this specific certificate should no longer be trusted, even though it hasn't technically expired yet" is revocation checking, one of TLS's most persistently imperfect corners in practice.
- Certificate Revocation Lists (CRLs): the issuing CA periodically publishes a signed list of every revoked certificate's serial number; a client downloads and checks against it. This scales poorly — the list only grows, and a client checking it on every connection adds real latency and a real dependency on the CA's own CRL-hosting infrastructure being available.
- OCSP (Online Certificate Status Protocol): a client queries the CA's OCSP responder in real time for one specific certificate's status, rather than downloading an entire list — better-scoped than a CRL, but it adds a genuine extra round trip (and a genuine extra point of failure) to every handshake, and — the part that actually undermines it in practice — most client implementations "soft-fail": if the OCSP responder is slow or unreachable, the client proceeds as if the certificate were valid rather than blocking the connection, specifically because a hard-fail-on-unreachable-OCSP-responder policy would turn every CA outage into a mass, unrelated outage for every site using that CA — but this same soft-fail behavior means an attacker capable of blocking OCSP traffic (a real capability for an on-path attacker) can often defeat revocation checking entirely just by making it time out.
- OCSP stapling is the practical fix: the server itself periodically queries its own OCSP responder and "staples" the signed, time-stamped response directly onto the TLS handshake — the client gets the revocation status with zero extra round trips of its own, and the response is itself cryptographically signed by the CA so it can't be forged by the server. This is the modern, recommended default, and most major web servers and load balancers (nginx, Envoy, most managed cloud load balancers) support it as a configuration option worth deliberately enabling rather than leaving off by default.
The client never talks to the OCSP responder at all — the server does that work ahead of time and hands over a pre-signed, verifiable answer as part of the normal handshake flight.
Tip
Best practice: enable OCSP stapling on every public-facing TLS termination point. It closes the soft-fail weakness of client-driven OCSP (the client trusts a stapled, CA-signed response the same way regardless of network conditions, since the server did the actual network round trip well in advance), adds zero extra handshake latency, and is a low-effort, high-value configuration change most teams simply haven't gotten around to turning on rather than one with any real downside.
Certificate Transparency — Catching Mis-Issued Certificates#
The PKI trust model covered earlier has a structural weakness worth naming directly: any of the roughly
few hundred CAs trusted by a major browser's trust store can, in principle, issue a valid certificate for
any domain — including one it was never authorized by the domain owner to issue. A compromised CA, a
social-engineered CA employee, or simple human error at a CA can produce a technically valid, fully
trusted-chain certificate for checkout-service.example.com that checkout-service's own team never
requested and has no way to know exists — until it's used in an actual man-in-the-middle attack.
Certificate Transparency (CT) closes this specific gap by requiring every publicly-trusted certificate to be logged in one of several public, cryptographically-verifiable, append-only CT logs before a major browser will trust it — a browser encountering a certificate with no valid CT log inclusion proof (a "Signed Certificate Timestamp," embedded in the certificate itself or delivered via OCSP/TLS extension) simply refuses to trust it, regardless of how legitimate the issuing CA's own signature otherwise looks.
The public, append-only log is the key property — a mis-issued certificate can't be quietly issued and used without a permanent, publicly-auditable record of it existing, which is exactly what makes external monitoring of the practice below possible.
Tip
Best practice: monitor CT logs for unexpected certificates issued for your own domains, using one of several free public CT-monitoring services (crt.sh being a widely-used example) or a paid security-vendor equivalent. This is a genuinely cheap, high-leverage detective control — it won't prevent a mis-issued certificate from being created in the first place, but it turns "we never found out someone else had a valid cert for our domain" into "we got an alert within minutes of one appearing in a public CT log," which is frequently the actual difference between catching an active attack early and not catching it at all.
mTLS — Mutual Authentication and Why Service Meshes Default to It#
Everything covered so far establishes one-directional trust: the client verifies the server's identity, but the server has no cryptographic proof of who the client is — a server accepting any connection at all, then relying on an application-layer mechanism (an API key, a session cookie, a bearer token) to establish client identity after the TLS handshake already completed. Mutual TLS (mTLS) extends the same handshake to verify identity in both directions: the server additionally requests a certificate from the client, and both sides validate each other's certificate chain before the connection is considered established.
The extra CertificateRequest/client-Certificate exchange is the entire mechanical difference from
standard TLS — everything else about the handshake works identically to the sections above.
This is precisely why service meshes (covered in full in Part 5) default to mTLS for every
service-to-service connection inside the mesh: in a microservices architecture with dozens or hundreds of
internal services, mTLS gives every service cryptographic proof of exactly which other service it's
talking to — not just "a request arrived with a valid-looking bearer token," which says nothing about
whether the network path itself was legitimate. checkout-service calling inventory-service over mTLS
means inventory-service can enforce "only accept requests where the client certificate identifies it as
checkout-service specifically" directly at the network/TLS layer, before any application code even runs —
a strong, hard-to-forge foundation that authorization logic can then build additional policy on top of.
Tip
Best practice: mTLS is the default posture for internal service-to-service traffic in any environment handling sensitive data, not just an option to consider when a specific compliance requirement demands it. The "trusted internal network" assumption — that traffic inside a VPC doesn't need the same authentication rigor as internet-facing traffic — is exactly the assumption Part 3's "defense in depth" callout on re-encryption argued against; mTLS is the concrete mechanism that assumption's replacement runs on.
Certificate Rotation at Scale — the Operational Reality of mTLS#
mTLS at real microservices scale (potentially hundreds of services, thousands of pods, each needing its own certificate) makes manual, human-managed certificate issuance and renewal completely impractical — the operational model that makes it tractable is short-lived, automatically-rotated certificates issued by an internal CA, not the long-lived, publicly-trusted certificates covered in the PKI section above.
- A service mesh's control plane (Istio's Istiod, covered in Part 5) typically runs its own internal CA and issues certificates tied directly to each workload's platform identity (a Kubernetes ServiceAccount, commonly) — automatically, with no human ever manually requesting or installing one.
- Certificate lifetimes are deliberately short — commonly 24 hours or less, sometimes down to under an hour in more aggressive configurations — with automatic re-issuance well before expiry, so a compromised certificate's usable window, if one were ever exfiltrated, is bounded to a small fraction of a day rather than the months-to-years lifetime typical of a public-facing certificate.
- Revocation becomes largely unnecessary as a separate operational concern at this rotation cadence — a compromised workload's certificate naturally expires and is replaced on the normal schedule, and the compromised workload itself (not just its certificate) is what actually needs remediating.
⚠️ From the trenches: a platform team migrating to a service mesh's automatic mTLS underestimated the
clock-synchronization requirement this rotation model depends on — TLS certificate validity windows are
checked against wall-clock time on both ends of a connection, and a node whose system clock had drifted
(a real, if uncommon, failure mode — a misconfigured NTP client on one specific node pool) began rejecting
otherwise-perfectly-valid, freshly-issued short-lived certificates as "not yet valid" or "already expired,"
depending on which direction the drift ran. Because the certificates themselves were correctly issued and
correctly signed, every certificate-chain-level diagnostic the team initially reached for came back clean —
the actual root cause was one layer below TLS entirely, in the affected node's own NTP configuration, and
was only found once someone thought to directly compare date output across nodes rather than continuing
to inspect certificate contents that were never actually the problem.
Cipher Suites — What's Actually Being Negotiated, and What to Configure#
A cipher suite names the specific combination of algorithms a TLS connection actually uses: the key
exchange method, the authentication/signature algorithm, the symmetric encryption cipher, and the message
authentication mechanism — for example, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 under TLS 1.2's naming
convention names ECDHE for key exchange, RSA for authentication, AES-256-GCM for symmetric encryption, and
SHA384 for the hash function. TLS 1.3, per the earlier section, decouples the key-exchange/authentication
choice from the cipher-suite name entirely and narrows the negotiable symmetric-cipher set to a small,
uniformly modern list (TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, and a couple of others) —
a deliberate simplification versus TLS 1.2's much larger, more error-prone negotiable combination space.
| Component | What it does | Modern recommended choice |
|---|---|---|
| Key exchange | Establishes the shared secret | ECDHE (elliptic-curve, ephemeral) — never plain RSA key exchange, which provides no forward secrecy at all |
| Authentication | Proves server (and, for mTLS, client) identity | ECDSA (faster to verify) or RSA (more universally supported) |
| Symmetric cipher | Encrypts the actual data | AES-256-GCM or ChaCha20-Poly1305 (the latter notably faster on CPUs without AES hardware acceleration — common on some mobile/ARM devices) |
| MAC/integrity | Detects tampering | Built into AEAD modes like GCM/Poly1305 — no separate configuration needed on any modern cipher suite |
The single most consequential cipher-suite configuration decision, independent of which specific suite is chosen, is ensuring every enabled option provides forward secrecy — the property, introduced earlier in this chapter's TLS 1.2 handshake walkthrough, that a compromised long-term server key can't retroactively decrypt previously-captured traffic. Plain (non-ephemeral) RSA key exchange, still technically negotiable under TLS 1.2, provides no forward secrecy at all — every session using it is retroactively decryptable the moment the server's private key is ever compromised, no matter how long ago the traffic was captured. TLS 1.3 removes this risk structurally, by simply never offering a non-forward-secret option in the first place — one more concrete reason, beyond raw handshake speed, that this chapter's earlier "TLS 1.3 should be the default" guidance holds.
Note
Most platform teams should not hand-tune cipher suite lists from scratch — Mozilla's publicly maintained SSL Configuration Generator, and the sane, secure-by-default cipher lists shipped by modern load balancers and reverse proxies (Envoy, nginx, every major cloud load balancer), already encode this section's guidance correctly. Manual cipher-suite tuning is real, ongoing security-sensitive work best reserved for a specific, well-understood compliance or legacy-compatibility requirement, not a default exercise for every new service.
HTTP/1.1 — Where Web Protocol Performance Problems Started#
HTTP/1.1, still in wide production use today, sends one request and waits for its complete response before the same TCP connection can be reused for the next request — a strict, serial request/response model. Browsers historically worked around this by opening multiple parallel TCP connections to the same host (6 was the long-standing common browser limit) — real parallelism, but at the cost of each connection independently paying its own full TLS handshake overhead (covered above) and its own separate TCP slow-start ramp-up, both genuinely wasteful when multiplied across 6 connections to the same server.
HTTP/1.1's "Head-of-Line (HOL) blocking" describes this same serialization constraint from the opposite angle: on any one connection, a slow response to an early request blocks every later request queued behind it on that same connection, even if the later requests' responses were ready faster — real, measurable wasted time on every page or API response with more than one dependent resource.
HTTP/1.1 pipelining — allowing a client to send several requests back-to-back on one connection without waiting for each response before sending the next — was standardized as a partial fix, but responses still had to come back strictly in the order requested (the connection-level HOL-blocking problem persisted regardless), and enough real-world middleboxes and servers implemented pipelining incorrectly (silently reordering, or outright breaking, pipelined responses) that virtually no major browser ever enabled it by default in practice. This is a useful, concrete historical lesson worth internalizing beyond just this one protocol detail: a protocol feature that's technically standardized but unreliable in the real-world deployed ecosystem is, for practical production purposes, not actually available — which is precisely why HTTP/2's multiplexing (below) was designed as a clean-break wire-format change requiring an explicit protocol upgrade, rather than another incremental extension layered onto HTTP/1.1's existing text-based wire format the way pipelining had been.
HTTP/2 — Multiplexing, and Its Own New Problem#
HTTP/2 solves HTTP/1.1's application-layer head-of-line blocking directly: many independent request/response streams can be interleaved over a single TCP connection, each stream's frames tagged with a stream ID so the receiver can reassemble them correctly regardless of arrival order — no more needing 6 parallel connections just to get real concurrency, and no more one slow response blocking every other request queued on the same connection at the application layer.
The problem HTTP/2 didn't — and structurally couldn't — solve: head-of-line blocking moved down to the transport layer instead of disappearing. TCP itself guarantees strictly ordered, reliable byte delivery — if one packet is lost, TCP must hold every subsequent packet (even ones belonging to a completely different, otherwise-unaffected HTTP/2 stream) until the lost one is retransmitted and arrives, because TCP has no concept of "these bytes belong to independent, resumable streams" — that's purely an HTTP/2 abstraction layered on top of a transport that doesn't know it exists.
The exact problem HTTP/2 was designed to eliminate reappears one layer down — a single lost packet anywhere in the connection stalls every multiplexed stream, not just the one the lost packet belonged to.
This is precisely the motivation for HTTP/3's transport-layer redesign, covered next — as long as multiplexing runs on top of TCP, transport-layer head-of-line blocking is architecturally unavoidable, no matter how well the application layer is designed above it.
HTTP/3 and QUIC — Moving Multiplexing Below TLS#
HTTP/3 replaces TCP with QUIC — a new transport protocol built on top of UDP — specifically to solve HTTP/2's transport-layer head-of-line-blocking problem by making streams a first-class transport-level concept, not just an application-layer abstraction bolted onto a transport that doesn't understand them. Because QUIC itself knows about independent streams, a lost packet belonging to one stream only blocks that stream's delivery — every other stream multiplexed on the same QUIC connection continues delivering normally, genuinely solving the problem HTTP/2 could only push down a layer.
| HTTP/1.1 | HTTP/2 | HTTP/3 (QUIC) | |
|---|---|---|---|
| Transport | TCP | TCP | UDP (with QUIC providing TCP-like reliability per stream) |
| Multiplexing | None — serial per connection | Yes, application layer | Yes, transport layer |
| Head-of-line blocking | Application-layer (severe) | Transport-layer (TCP-driven) | Effectively eliminated — per-stream loss isolation |
| TLS relationship | Separate layer, TLS wraps TCP | Separate layer, TLS wraps TCP | TLS 1.3 is built directly into QUIC — no separate handshake layering |
| Connection establishment | TCP handshake, then TLS handshake (2 separate round-trip costs) | Same as HTTP/1.1 | 0-1 combined round trips — QUIC's handshake and TLS 1.3's handshake happen together |
QUIC building TLS 1.3 directly into the transport, rather than layering it on top the way TCP+TLS does, is itself a meaningful part of the latency win — QUIC's own connection-establishment handshake and TLS 1.3's cryptographic handshake are combined into the same exchange, rather than paying for a TCP handshake and then a separate TLS handshake on top of it, each with their own round trip.
QUIC's Other Wins — Connection Migration and Built-In 0-RTT#
Two further QUIC properties, beyond eliminating transport-layer head-of-line blocking, matter enough in production to call out specifically:
- Connection migration: a TCP connection is identified by a 4-tuple (source IP, source port, destination IP, destination port) — a client's IP address changing mid-connection (switching from WiFi to cellular, a genuinely common event on mobile) breaks the TCP connection entirely, forcing a full reconnect. QUIC identifies a connection by a connection ID independent of the underlying IP/port — the same QUIC connection can survive a client's network change transparently, with no reconnect and no lost in-flight state, a real and immediately noticeable improvement for any mobile-heavy traffic pattern.
- 0-RTT is a native, first-class part of QUIC's own handshake (not a TLS-specific bolt-on the way it is for TCP+TLS 1.3) — the same replay-attack tradeoff from earlier in this chapter still applies identically, and the same rule (never accept 0-RTT for non-idempotent operations) still holds regardless of which transport is carrying it.
Note
As of 2026, HTTP/3 traffic share sits roughly in the 20-40% range depending on measurement methodology (W3Techs, Cloudflare, and other measurement sources disagree somewhat on exact figures, but agree on the broad trend) — no longer experimental, but also not yet the default majority protocol for all web traffic. Adoption is led disproportionately by CDNs and large platforms (Cloudflare, major browsers, large content platforms) rather than the median self-hosted origin server, which is the practical reason Part 5's CDN chapter is where HTTP/3 deployment realistically starts for most teams — fronting an origin with a CDN that already speaks HTTP/3 is, in practice, a faster path to real adoption than upgrading every individual origin server's own stack.
Choosing and Rolling Out HTTP Versions in Production#
A production service almost never picks exactly one HTTP version — it negotiates the best one both client
and server actually support, per connection, via ALPN (Application-Layer Protocol Negotiation, itself part
of the TLS handshake's ClientHello/ServerHello exchange). A well-configured modern web server or load
balancer advertises support for HTTP/1.1, HTTP/2, and HTTP/3 simultaneously, and each connecting client
negotiates the best version it itself supports — an old client falls back cleanly to HTTP/1.1 with zero
special handling required, while a modern browser gets HTTP/3's full benefit automatically.
# Checking which protocol version a server actually negotiated
curl -v --http3 https://checkout.example.com/ 2>&1 | grep -i "using http"
# ALPN negotiation is visible directly in a TLS handshake capture
openssl s_client -connect checkout.example.com:443 -alpn h2,http/1.1HTTP/3's transport being UDP, not TCP, is a genuinely real operational deployment wrinkle — some corporate firewalls, older network middleboxes, and restrictive network environments block or heavily rate-limit UDP traffic on non-standard ports far more aggressively than they'd ever block TCP port 443, since UDP has historically been associated with less HTTP-adjacent traffic (VoIP, gaming, DNS). A client on such a network genuinely can't reach an HTTP/3-only endpoint at all — which is exactly why ALPN-negotiated fallback to HTTP/2 or HTTP/1.1 (both still TCP-based) matters as a real reachability safety net, not just a compatibility nicety for old browsers.
There's a genuine bootstrapping problem worth understanding: ALPN negotiation happens during the TLS
handshake, but HTTP/3 runs over QUIC/UDP while HTTP/1.1 and HTTP/2 run over TCP — meaning a client can't
simply "negotiate HTTP/3 within a TCP connection" the way it negotiates between HTTP/1.1 and HTTP/2. The
actual mechanism: a client's first request to a server happens over TCP (HTTP/1.1 or HTTP/2, negotiated
normally via ALPN), and the server includes an Alt-Svc response header advertising that it also
supports HTTP/3 on a given UDP port — the client then opportunistically attempts a QUIC connection for
subsequent requests, falling back silently to the TCP-based connection already in hand if the QUIC attempt
fails or times out. This is why a client's very first request to a previously-unvisited HTTP/3-capable
server is never itself HTTP/3 — it necessarily starts on TCP, and only upgrades once it learns the server
supports QUIC via that first response's Alt-Svc header.
TLS Termination Performance — Where the CPU Cost Actually Goes#
TLS is not free — the asymmetric cryptography used during the handshake (the key exchange, the certificate signature verification) is genuinely CPU-expensive relative to the symmetric encryption used for the bulk of a connection's actual data transfer afterward. This has a direct, practical consequence for capacity planning at real scale: a service handling many short-lived connections (each paying the expensive handshake cost) has a meaningfully different CPU profile than one handling fewer, longer-lived connections (where the handshake cost is amortized over much more actual data transfer).
| Cost driver | Relative expense | Mitigation |
|---|---|---|
| Asymmetric handshake (key exchange, cert verification) | High, paid once per new connection | Session resumption (TLS session tickets), connection reuse/keep-alive, HTTP/2's single-connection multiplexing reducing total connection count |
| Symmetric encryption (the actual data transfer) | Low, especially with hardware AES-NI acceleration on nearly all modern CPUs | Generally not a meaningful optimization target — already cheap |
| Certificate chain verification (client-side) | Moderate, grows with chain length and signature algorithm choice | Keep chains reasonably short; ECDSA certificates verify faster than RSA at equivalent security levels |
Tip
Best practice: dedicate TLS termination to infrastructure actually built for it (a load balancer, Envoy, a CDN edge) rather than terminating TLS in application code directly, both for the operational reasons covered throughout Part 2 (centralizing cert management, health checks, routing) and for this chapter's own reason: purpose-built termination points are generally better-tuned for session resumption and connection reuse than a typical application framework's default TLS stack, meaningfully reducing the aggregate handshake cost paid across a high-connection-volume production fleet.
One more real, easy-to-overlook cost multiplier this table doesn't fully capture: an mTLS deployment (covered earlier in this chapter) roughly doubles the per-connection asymmetric-crypto cost relative to standard one-directional TLS, since both sides now perform full certificate-chain verification instead of just the client verifying the server. At the scale of a service mesh handling internal traffic between dozens of services — where mTLS is the recommended default per this chapter's own guidance — this is a real, budgeted capacity-planning line item, not a rounding error, and is the concrete reason production service meshes lean so heavily on the short-lived-certificate, sidecar-local verification model covered in the certificate-rotation section: a sidecar proxy sitting directly next to each workload absorbs this doubled handshake cost once, centrally, rather than requiring every individual application process to implement and pay for its own mTLS stack independently.
Full Worked Scenario: Rolling Out mTLS and HTTP/3 for checkout-service#
checkout-service's platform team undertakes two related but distinct protocol upgrades in the same
quarter: enabling mTLS for all internal service-to-service traffic (following a security review after the
platform's growth to over 40 internal services made the "trusted internal network" assumption feel
increasingly shaky), and enabling HTTP/3 on the public-facing API, driven by mobile app telemetry showing a
meaningful fraction of customers on unstable cellular connections experiencing repeated dropped connections
mid-checkout.
The mTLS rollout, applying this chapter's certificate-rotation section directly: the team deployed a service mesh (full architecture covered in Part 5) specifically for its automated internal CA and short-lived certificate issuance, rather than attempting to hand-roll certificate management across 40+ services. The rollout was staged — mTLS enabled in "permissive" mode first (accepting both mTLS and plaintext connections simultaneously, logging which services hadn't yet upgraded) for several weeks, only switching to "strict" mode (mTLS required, plaintext rejected) once telemetry confirmed every internal service was successfully completing the mTLS handshake. Skipping the permissive-mode staging step, which an earlier, smaller pilot rollout on a less critical set of services had done, had caused a real short outage when one legacy service — running on an older sidecar proxy version that didn't yet support the mesh's current certificate format — was simply unable to establish any connection at all the moment strict mode was enabled, with no advance warning, because nothing had been watching for exactly this signal during a permissive-mode observation window.
The HTTP/3 rollout, applying the ALPN-negotiated fallback pattern from earlier: HTTP/3 was enabled on the public ALB's replacement — a CDN-fronted architecture (covered in full in Part 5) chosen specifically because it already supported HTTP/3 natively, rather than requiring the team to build QUIC support into their own origin infrastructure directly. Mobile app telemetry after rollout showed the specific metric the team was chasing — mid-checkout connection drops during a network handoff (WiFi to cellular) — fell by over 70%, a direct, measurable result of QUIC's connection migration property covered earlier in this chapter, letting an in-progress checkout session survive a network change that would have previously forced a full TCP+TLS reconnect and, for a session with any client-side in-flight state not yet persisted server-side, a real risk of losing that state entirely.
Common Mistakes and Interview Traps#
| Mistake | Why it's wrong | What to say instead |
|---|---|---|
| "TLS 1.3 removed the handshake round trip entirely" | TLS 1.3 reduced a new connection's handshake from 2-RTT to 1-RTT — only a resumed session with 0-RTT skips the round trip, and even that carries a real replay-attack tradeoff | 1-RTT for new connections, 0-RTT (with caveats) only for session resumption |
| "mTLS and TLS are fundamentally different protocols" | mTLS is standard TLS with one additional exchange (a CertificateRequest/client-Certificate pair) — the rest of the handshake is identical | mTLS is standard TLS extended for bidirectional identity verification, not a separate protocol |
| "HTTP/2 solved head-of-line blocking" | HTTP/2 solved application-layer HOL blocking but introduced (or rather, exposed) transport-layer HOL blocking, since TCP itself still enforces strict ordering | HTTP/2 moved the problem down a layer; HTTP/3's QUIC transport is what actually eliminates it |
| "A self-signed certificate error always means something is broken" | A self-signed root is completely legitimate and expected for an internal mTLS deployment using a private CA | The error is only a real problem for a certificate that's supposed to chain to a publicly trusted root and doesn't |
| "HTTP/3 is strictly better, so it should replace HTTP/2 everywhere immediately" | UDP-based QUIC traffic is blocked or degraded on some real-world networks (restrictive corporate/institutional firewalls); ALPN fallback exists precisely because universal reachability isn't guaranteed | Deploy HTTP/3 alongside HTTP/2 fallback via ALPN negotiation, never as the sole supported protocol |
| "Enabling post-quantum hybrid key exchange means the connection is now fully quantum-resistant" | Hybrid key exchange protects the session's data confidentiality; the certificate's own signature (proving server identity) is very likely still a classical algorithm, which a quantum-capable adversary could eventually forge | Track key-exchange and signature post-quantum readiness as two separate, independently-maturing pieces, not one combined checkbox |
Worked Practice Problems#
Problem 1: A client reports unable to get local issuer certificate when connecting to a service, but
the exact same URL works fine in a web browser. What's the most likely explanation, and how would you
confirm it?
Answer: The server is very likely not sending its full intermediate certificate chain — only the leaf
certificate. Browsers frequently mask this specific misconfiguration because they cache and reuse
intermediate certificates they've seen from other sites signed by the same CA, effectively completing the
chain themselves behind the scenes; a fresh client (a curl command, a different application's TLS
library, a browser that's never seen that specific intermediate before) has no such cache and correctly
reports the incomplete chain as an error. openssl s_client -connect host:443 -showcerts reveals exactly
which certificates the server is actually sending, confirming whether the intermediate is present.
Problem 2: A team wants to reduce TLS handshake overhead for a public API serving a very high volume of short, independent requests from many different, mostly-new clients (not a small set of long-lived, frequently-reconnecting clients). Session resumption is proposed as the fix. Will it actually help much here, and why or why not?
Answer: Session resumption's benefit is specifically for a returning client reconnecting within the session ticket's validity window — it does nothing for a genuinely new client's first connection, which still pays the full handshake cost regardless. For a workload dominated by many distinct, mostly-new clients rather than a smaller set of frequently-reconnecting ones, session resumption will show limited aggregate benefit. The more impactful optimizations for this specific traffic pattern are the ones that reduce the number of handshakes needed per logical client interaction (HTTP/2 or HTTP/3's multiplexing, so one connection serves many requests instead of needing a new connection per request) and reducing the handshake's own cost (ECDSA certificates over RSA, keeping the certificate chain short) — session resumption is the right tool for a different traffic shape than the one described here.
Problem 3: An internal service mesh's mTLS rollout is being planned. A team proposes issuing 1-year validity certificates to reduce the operational overhead of frequent rotation. What's the argument against this, given the mesh already has automated certificate issuance in place?
Answer: With automated issuance and rotation already built into the mesh's control plane, the operational overhead argument for long-lived certificates mostly disappears — the whole point of a service mesh's internal CA is that rotation happens automatically, with no human toil either way. Given that, a long validity window only adds risk with no corresponding operational benefit: if a workload's private key is ever compromised, a 1-year certificate gives an attacker up to a full year of valid, trusted access before natural expiry, versus the hours-to-a-day window a short-lived certificate provides. Since the automation already eliminates the reason short-lived certificates would otherwise be operationally painful, there's no remaining argument in favor of the longer validity period.
Problem 4: A security team is evaluating whether enabling hybrid post-quantum key exchange
(X25519MLKEM768) on checkout-service's public API is worth prioritizing this quarter, given the
certificate authentication itself will still use classical ECDSA signatures either way. What's the actual
risk this specific change addresses, and does the remaining classical certificate signature undermine the
benefit?
Answer: No — the two protections address genuinely different threats, and enabling one without the other
still provides real, meaningful value. Hybrid key exchange protects the confidentiality of the session's
encrypted data against a future "harvest now, decrypt later" attack — an adversary recording encrypted
checkout traffic today gains nothing from later breaking classical ECDHE if ML-KEM also has to be broken
independently. This has nothing to do with the certificate's signature algorithm, which instead protects
against a different threat — a quantum-capable adversary forging a fake certificate to impersonate the
server. For a service like checkout-service, where transaction data captured today could remain
sensitive for years, enabling hybrid key exchange now closes the harvest-now-decrypt-later exposure
immediately, even though the separate certificate-forgery risk remains open until post-quantum signature
algorithms mature and see wider CA/ecosystem adoption — worth prioritizing on its own merits, not
contingent on the other piece being solved first.
Summary and What's Next#
TLS is the layer that makes every other mechanism in this series trustworthy — the handshake mechanics, the
PKI trust model, and mTLS's extension of that trust to both directions are what let checkout-service and
its dependencies actually verify who they're talking to, not just that a connection was successfully
routed and load-balanced. The HTTP/1.1-to-HTTP/3 evolution, meanwhile, is the story of steadily removing
artificial serialization from a protocol that fundamentally wants to be concurrent — first at the
application layer, then discovering the same problem one layer down, and finally rebuilding the transport
itself to close the gap for good.
Two threads from this chapter are worth carrying forward explicitly. First, trust in this whole stack is never a single control — it's PKI's chain of trust, backstopped by revocation checking for when a certificate goes bad early, backstopped again by Certificate Transparency for catching an issuance that should never have happened at all, the same layered-defense pattern this series has returned to in every chapter so far. Second, protocol evolution here has consistently traded a small amount of new complexity (QUIC's own connection-migration state, hybrid key exchange's doubled key-share size) for removing a larger amount of structural inefficiency — a pattern worth recognizing the next time a "simpler" older protocol looks appealing purely for its simplicity, without accounting for what that simplicity was actually costing in practice.
Part 5, the final chapter of this series, brings CDN and edge networking together with the service mesh data plane this chapter has referenced throughout — anycast-fronted CDN caching (building directly on Part 1's anycast mechanics), GeoDNS-based traffic steering, and the full mechanics of how a service mesh's Envoy sidecars (introduced in Part 2) enforce the mTLS-everywhere posture this chapter just covered, end to end across an entire microservices platform.